Commit 66cdc187 by Andrej Ocenas Committed by GitHub

Refactor/Explore: Inline datasource actions into initialisation (#28953)

* Inline datasource actions into initialisation

* Fix datasource change

* Fix rich history

* Fix test

* Move rich history init to Wrapper

* Remove console log

* TS fixes

Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com>
parent 2c898ab1
......@@ -164,7 +164,6 @@ export class UnConnectedExploreToolbar extends PureComponent<Props> {
isLive,
isPaused,
originPanelId,
datasourceLoading,
containerWidth,
onChangeTimeZone,
} = this.props;
......@@ -217,7 +216,6 @@ export class UnConnectedExploreToolbar extends PureComponent<Props> {
onChange={this.onChangeDatasource}
datasources={getExploreDatasources()}
current={this.getSelectedDatasource()}
showLoading={datasourceLoading === true}
hideTextValue={showSmallDataSourcePicker}
/>
</div>
......@@ -342,7 +340,6 @@ const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps
isPaused,
originPanelId,
queries,
datasourceLoading,
containerWidth,
} = exploreItem;
......@@ -362,7 +359,6 @@ const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps
originPanelId,
queries,
syncedTimes,
datasourceLoading: datasourceLoading ?? undefined,
containerWidth,
};
};
......
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import Wrapper from './Wrapper';
import { configureStore } from '../../store/configureStore';
import { Provider } from 'react-redux';
......@@ -73,6 +73,12 @@ describe('Wrapper', () => {
...query,
});
expect(store.getState().explore.richHistory[0]).toMatchObject({
datasourceId: '1',
datasourceName: 'loki',
queries: [{ expr: '{ label="value"}' }],
});
// We called the data source query method once
expect(datasources.loki.query).toBeCalledTimes(1);
expect((datasources.loki.query as Mock).mock.calls[0][0]).toMatchObject({
......@@ -107,6 +113,7 @@ describe('Wrapper', () => {
(datasources.loki.query as Mock).mockReturnValueOnce(makeLogsQueryResponse());
// Wait for rendering the logs
await screen.findByText(/custom log line/i);
await screen.findByText(`loki Editor input: { label="value"}`);
(datasources.elastic.query as Mock).mockReturnValueOnce(makeMetricsQueryResponse());
store.dispatch(
......@@ -117,19 +124,25 @@ describe('Wrapper', () => {
);
// Editor renders the new query
await screen.findByText(`loki Editor input: other query`);
await screen.findByText(`elastic Editor input: other query`);
// Renders graph
await screen.findByText(/Graph/i);
});
it('handles changing the datasource manually', async () => {
const { datasources } = setup();
const query = { left: JSON.stringify(['now-1h', 'now', 'loki', { expr: '{ label="value"}' }]) };
const { datasources } = setup({ query });
(datasources.loki.query as Mock).mockReturnValueOnce(makeLogsQueryResponse());
// Wait for rendering the editor
await screen.findByText(/Editor/i);
await changeDatasource('elastic');
await screen.findByText('elastic Editor input:');
expect(datasources.elastic.query).not.toBeCalled();
expect(store.getState().location.query).toEqual({
orgId: '1',
left: JSON.stringify(['now-1h', 'now', 'elastic', {}]),
});
});
it('opens the split pane', async () => {
......@@ -154,8 +167,10 @@ describe('Wrapper', () => {
(datasources.elastic.query as Mock).mockReturnValueOnce(makeLogsQueryResponse());
// Make sure we render the logs panel
const logsPanels = await screen.findAllByText(/^Logs$/i);
expect(logsPanels.length).toBe(2);
await waitFor(() => {
const logsPanels = screen.getAllByText(/^Logs$/i);
expect(logsPanels.length).toBe(2);
});
// Make sure we render the log line
const logsLines = await screen.findAllByText(/custom log line/i);
......@@ -195,7 +210,10 @@ function setup(options?: SetupOptions): { datasources: { [name: string]: DataSou
window.localStorage.clear();
// Create this here so any mocks are recreated on setup and don't retain state
const defaultDatasources: DatasourceSetup[] = [makeDatasourceSetup(), makeDatasourceSetup({ name: 'elastic' })];
const defaultDatasources: DatasourceSetup[] = [
makeDatasourceSetup(),
makeDatasourceSetup({ name: 'elastic', id: 2 }),
];
const dsSettings = options?.datasources || defaultDatasources;
......@@ -235,18 +253,18 @@ function setup(options?: SetupOptions): { datasources: { [name: string]: DataSou
return { datasources: fromPairs(dsSettings.map(d => [d.api.name, d.api])) };
}
function makeDatasourceSetup({ name = 'loki' }: { name?: string } = {}): DatasourceSetup {
function makeDatasourceSetup({ name = 'loki', id = 1 }: { name?: string; id?: number } = {}): DatasourceSetup {
const meta: any = {
info: {
logos: {
small: '',
},
},
id: '1',
id: id.toString(),
};
return {
settings: {
id: 1,
id,
uid: name,
type: 'logs',
name,
......
......@@ -6,12 +6,14 @@ import { StoreState } from 'app/types';
import { ExploreId } from 'app/types/explore';
import { CustomScrollbar, ErrorBoundaryAlert } from '@grafana/ui';
import { resetExploreAction } from './state/main';
import { resetExploreAction, richHistoryUpdatedAction } from './state/main';
import Explore from './Explore';
import { getRichHistory } from '../../core/utils/richHistory';
interface WrapperProps {
split: boolean;
resetExploreAction: typeof resetExploreAction;
richHistoryUpdatedAction: typeof richHistoryUpdatedAction;
}
export class Wrapper extends Component<WrapperProps> {
......@@ -19,6 +21,11 @@ export class Wrapper extends Component<WrapperProps> {
this.props.resetExploreAction({});
}
componentDidMount() {
const richHistory = getRichHistory();
this.props.richHistoryUpdatedAction({ richHistory });
}
render() {
const { split } = this.props;
......@@ -48,6 +55,7 @@ const mapStateToProps = (state: StoreState) => {
const mapDispatchToProps = {
resetExploreAction,
richHistoryUpdatedAction,
};
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(Wrapper));
import {
loadDatasource,
loadDatasourcePendingAction,
loadDatasourceReadyAction,
updateDatasourceInstanceAction,
datasourceReducer,
} from './datasource';
import { updateDatasourceInstanceAction, datasourceReducer } from './datasource';
import { ExploreId, ExploreItemState } from 'app/types';
import { thunkTester } from 'test/core/thunk/thunkTester';
import { DataQuery, DataSourceApi } from '@grafana/data';
import { createEmptyQueryResponse } from './utils';
import { reducerTester } from '../../../../test/core/redux/reducerTester';
describe('loading datasource', () => {
describe('when loadDatasource thunk is dispatched', () => {
describe('and all goes fine', () => {
it('then it should dispatch correct actions', async () => {
const exploreId = ExploreId.left;
const name = 'some-datasource';
const initialState = { explore: { [exploreId]: { requestedDatasourceName: name } } };
const mockDatasourceInstance = {
testDatasource: () => {
return Promise.resolve({ status: 'success' });
},
name,
init: jest.fn(),
meta: { id: 'some id' },
};
const dispatchedActions = await thunkTester(initialState)
.givenThunk(loadDatasource)
.whenThunkIsDispatched(exploreId, mockDatasourceInstance);
expect(dispatchedActions).toEqual([
loadDatasourcePendingAction({
exploreId,
requestedDatasourceName: mockDatasourceInstance.name,
}),
loadDatasourceReadyAction({ exploreId, history: [] }),
]);
});
});
describe('and user changes datasource during load', () => {
it('then it should dispatch correct actions', async () => {
const exploreId = ExploreId.left;
const name = 'some-datasource';
const initialState = { explore: { [exploreId]: { requestedDatasourceName: 'some-other-datasource' } } };
const mockDatasourceInstance = {
testDatasource: () => {
return Promise.resolve({ status: 'success' });
},
name,
init: jest.fn(),
meta: { id: 'some id' },
};
const dispatchedActions = await thunkTester(initialState)
.givenThunk(loadDatasource)
.whenThunkIsDispatched(exploreId, mockDatasourceInstance);
expect(dispatchedActions).toEqual([
loadDatasourcePendingAction({
exploreId,
requestedDatasourceName: mockDatasourceInstance.name,
}),
]);
});
});
});
});
describe('Explore item reducer', () => {
describe('changing datasource', () => {
describe('when updateDatasourceInstanceAction is dispatched', () => {
describe('and datasourceInstance supports graph, logs, table and has a startpage', () => {
it('then it should set correct state', () => {
const StartPage = {};
const datasourceInstance = {
meta: {
metrics: true,
logs: true,
},
components: {
ExploreStartPage: StartPage,
},
} as DataSourceApi;
const queries: DataQuery[] = [];
const queryKeys: string[] = [];
const initialState: ExploreItemState = ({
datasourceInstance: null,
queries,
queryKeys,
} as unknown) as ExploreItemState;
const expectedState: any = {
datasourceInstance,
queries,
queryKeys,
graphResult: null,
logsResult: null,
tableResult: null,
latency: 0,
loading: false,
queryResponse: createEmptyQueryResponse(),
};
reducerTester<ExploreItemState>()
.givenReducer(datasourceReducer, initialState)
.whenActionIsDispatched(updateDatasourceInstanceAction({ exploreId: ExploreId.left, datasourceInstance }))
.thenStateShouldEqual(expectedState);
});
});
});
describe('Datasource reducer', () => {
it('should handle set updateDatasourceInstanceAction correctly', () => {
const StartPage = {};
const datasourceInstance = {
meta: {
metrics: true,
logs: true,
},
components: {
ExploreStartPage: StartPage,
},
} as DataSourceApi;
const queries: DataQuery[] = [];
const queryKeys: string[] = [];
const initialState: ExploreItemState = ({
datasourceInstance: null,
queries,
queryKeys,
} as unknown) as ExploreItemState;
const expectedState: any = {
datasourceInstance,
queries,
queryKeys,
graphResult: null,
logsResult: null,
tableResult: null,
latency: 0,
loading: false,
queryResponse: createEmptyQueryResponse(),
};
const result = datasourceReducer(
initialState,
updateDatasourceInstanceAction({ exploreId: ExploreId.left, datasourceInstance, history: [] })
);
expect(result).toMatchObject(expectedState);
});
});
// Libraries
import { AnyAction, createAction, PayloadAction } from '@reduxjs/toolkit';
import { AnyAction, createAction } from '@reduxjs/toolkit';
import { RefreshPicker } from '@grafana/ui';
import { DataSourceApi, HistoryItem } from '@grafana/data';
import store from 'app/core/store';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { lastUsedDatasourceKeyForOrgId, stopQueryState } from 'app/core/utils/explore';
import { stopQueryState } from 'app/core/utils/explore';
import { ExploreItemState, ThunkResult } from 'app/types';
import { ExploreId } from 'app/types/explore';
import { getExploreDatasources } from './selectors';
import { importQueries, runQueries } from './query';
import { changeRefreshInterval } from './time';
import { createEmptyQueryResponse, makeInitialUpdateState } from './utils';
import { createEmptyQueryResponse, loadAndInitDatasource, makeInitialUpdateState } from './utils';
//
// Actions and Payloads
//
/**
* Display an error when no datasources have been configured
*/
export interface LoadDatasourceMissingPayload {
exploreId: ExploreId;
}
export const loadDatasourceMissingAction = createAction<LoadDatasourceMissingPayload>('explore/loadDatasourceMissing');
/**
* Start the async process of loading a datasource to display a loading indicator
*/
export interface LoadDatasourcePendingPayload {
exploreId: ExploreId;
requestedDatasourceName: string;
}
export const loadDatasourcePendingAction = createAction<LoadDatasourcePendingPayload>('explore/loadDatasourcePending');
/**
* Datasource loading was completed.
*/
export interface LoadDatasourceReadyPayload {
exploreId: ExploreId;
history: HistoryItem[];
}
export const loadDatasourceReadyAction = createAction<LoadDatasourceReadyPayload>('explore/loadDatasourceReady');
/**
* Updates datasource instance before datasource loading has started
*/
export interface UpdateDatasourceInstancePayload {
exploreId: ExploreId;
datasourceInstance: DataSourceApi;
history: HistoryItem[];
}
export const updateDatasourceInstanceAction = createAction<UpdateDatasourceInstancePayload>(
'explore/updateDatasourceInstance'
......@@ -67,35 +39,28 @@ export function changeDatasource(
options?: { importQueries: boolean }
): ThunkResult<void> {
return async (dispatch, getState) => {
let newDataSourceInstance: DataSourceApi;
if (!datasourceName) {
newDataSourceInstance = await getDatasourceSrv().get();
} else {
newDataSourceInstance = await getDatasourceSrv().get(datasourceName);
}
const currentDataSourceInstance = getState().explore[exploreId].datasourceInstance;
const queries = getState().explore[exploreId].queries;
const orgId = getState().user.orgId;
const { history, instance } = await loadAndInitDatasource(orgId, datasourceName);
const currentDataSourceInstance = getState().explore[exploreId].datasourceInstance;
dispatch(
updateDatasourceInstanceAction({
exploreId,
datasourceInstance: newDataSourceInstance,
datasourceInstance: instance,
history,
})
);
const queries = getState().explore[exploreId].queries;
if (options?.importQueries) {
await dispatch(importQueries(exploreId, queries, currentDataSourceInstance, newDataSourceInstance));
await dispatch(importQueries(exploreId, queries, currentDataSourceInstance, instance));
}
if (getState().explore[exploreId].isLive) {
dispatch(changeRefreshInterval(exploreId, RefreshPicker.offOption.value));
}
await dispatch(loadDatasource(exploreId, newDataSourceInstance, orgId));
// Exception - we only want to run queries on data source change, if the queries were imported
if (options?.importQueries) {
dispatch(runQueries(exploreId));
......@@ -103,72 +68,6 @@ export function changeDatasource(
};
}
/**
* 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 async dispatch => {
const exploreDatasources = getExploreDatasources();
if (exploreDatasources.length >= 1) {
await dispatch(changeDatasource(exploreId, datasourceName, { importQueries: true }));
} else {
dispatch(loadDatasourceMissingAction({ exploreId }));
}
};
}
/**
* Datasource loading was successfully completed.
*/
export const loadDatasourceReady = (
exploreId: ExploreId,
instance: DataSourceApi,
orgId: number
): PayloadAction<LoadDatasourceReadyPayload> => {
const historyKey = `grafana.explore.history.${instance.meta?.id}`;
const history = store.getObject(historyKey, []);
// Save last-used datasource
store.set(lastUsedDatasourceKeyForOrgId(orgId), instance.name);
return loadDatasourceReadyAction({
exploreId,
history,
});
};
/**
* Main action to asynchronously load a datasource. Dispatches lots of smaller actions for feedback.
*/
export const loadDatasource = (exploreId: ExploreId, instance: DataSourceApi, orgId: number): ThunkResult<void> => {
return async (dispatch, getState) => {
const datasourceName = instance.name;
// Keep ID to track selection
dispatch(loadDatasourcePendingAction({ exploreId, requestedDatasourceName: datasourceName }));
if (instance.init) {
try {
instance.init();
} catch (err) {
console.error(err);
}
}
if (datasourceName !== getState().explore[exploreId].requestedDatasourceName) {
// User already changed datasource, discard results
return;
}
dispatch(loadDatasourceReady(exploreId, instance, orgId));
};
};
//
// Reducer
//
......@@ -183,7 +82,7 @@ export const loadDatasource = (exploreId: ExploreId, instance: DataSourceApi, or
// https://github.com/reduxjs/redux-toolkit/issues/242
export const datasourceReducer = (state: ExploreItemState, action: AnyAction): ExploreItemState => {
if (updateDatasourceInstanceAction.match(action)) {
const { datasourceInstance } = action.payload;
const { datasourceInstance, history } = action.payload;
// Custom components
stopQueryState(state.querySubscription);
......@@ -199,32 +98,7 @@ export const datasourceReducer = (state: ExploreItemState, action: AnyAction): E
loading: false,
queryKeys: [],
originPanelId: state.urlState && state.urlState.originPanelId,
};
}
if (loadDatasourceMissingAction.match(action)) {
return {
...state,
datasourceMissing: true,
datasourceLoading: false,
update: makeInitialUpdateState(),
};
}
if (loadDatasourcePendingAction.match(action)) {
return {
...state,
datasourceLoading: true,
requestedDatasourceName: action.payload.requestedDatasourceName,
};
}
if (loadDatasourceReadyAction.match(action)) {
const { history } = action.payload;
return {
...state,
history,
datasourceLoading: false,
datasourceMissing: false,
logsHighlighterExpressions: undefined,
update: makeInitialUpdateState(),
......
......@@ -102,8 +102,8 @@ describe('refreshExplore', () => {
.givenThunk(refreshExplore)
.whenThunkIsDispatched(exploreId);
const initializeExplore = dispatchedActions[1] as PayloadAction<InitializeExplorePayload>;
const { type, payload } = initializeExplore;
const initializeExplore = dispatchedActions.find(action => action.type === initializeExploreAction.type);
const { type, payload } = initializeExplore as PayloadAction<InitializeExplorePayload>;
expect(type).toEqual(initializeExploreAction.type);
expect(payload.containerWidth).toEqual(containerWidth);
......@@ -144,7 +144,7 @@ describe('refreshExplore', () => {
});
});
describe('Explore item reducer', () => {
describe('Explore pane reducer', () => {
describe('changing dedup strategy', () => {
describe('when changeDedupStrategyAction is dispatched', () => {
it('then it should set correct dedup strategy in state', () => {
......
......@@ -6,26 +6,33 @@ import { queryReducer } from './query';
import { datasourceReducer } from './datasource';
import { timeReducer } from './time';
import { historyReducer } from './history';
import { makeExplorePaneState, makeInitialUpdateState } from './utils';
import { makeExplorePaneState, makeInitialUpdateState, loadAndInitDatasource, createEmptyQueryResponse } from './utils';
import { createAction, PayloadAction } from '@reduxjs/toolkit';
import { EventBusExtended, DataQuery, ExploreUrlState, LogLevel, LogsDedupStrategy, TimeRange } from '@grafana/data';
import {
EventBusExtended,
DataQuery,
ExploreUrlState,
LogLevel,
LogsDedupStrategy,
TimeRange,
HistoryItem,
DataSourceApi,
} from '@grafana/data';
import {
clearQueryKeys,
ensureQueries,
generateNewKeyAndAddRefIdIfMissing,
getTimeRangeFromUrl,
} from 'app/core/utils/explore';
import { getRichHistory } from 'app/core/utils/richHistory';
// Types
import { ThunkResult } from 'app/types';
import { getTimeZone } from 'app/features/profile/state/selectors';
import { updateLocation } from '../../../core/actions';
import { serializeStateToUrlParam } from '@grafana/data/src/utils/url';
import { richHistoryUpdatedAction } from './main';
import { runQueries, setQueriesAction } from './query';
import { loadExploreDatasourcesAndSetDatasource } from './datasource';
import { updateTime } from './time';
import { toRawTimeRange } from '../utils/time';
import { getExploreDatasources } from './selectors';
//
// Actions and Payloads
......@@ -72,6 +79,8 @@ export interface InitializeExplorePayload {
eventBridge: EventBusExtended;
queries: DataQuery[];
range: TimeRange;
history: HistoryItem[];
datasourceInstance?: DataSourceApi;
originPanelId?: number | null;
}
export const initializeExploreAction = createAction<InitializeExplorePayload>('explore/initializeExplore');
......@@ -122,7 +131,17 @@ export function initializeExplore(
originPanelId?: number | null
): ThunkResult<void> {
return async (dispatch, getState) => {
dispatch(loadExploreDatasourcesAndSetDatasource(exploreId, datasourceName));
const exploreDatasources = getExploreDatasources();
let instance = undefined;
let history: HistoryItem[] = [];
if (exploreDatasources.length >= 1) {
const orgId = getState().user.orgId;
const loadResult = await loadAndInitDatasource(orgId, datasourceName);
instance = loadResult.instance;
history = loadResult.history;
}
dispatch(
initializeExploreAction({
exploreId,
......@@ -131,11 +150,15 @@ export function initializeExplore(
queries,
range,
originPanelId,
datasourceInstance: instance,
history,
})
);
dispatch(updateTime({ exploreId }));
const richHistory = getRichHistory();
dispatch(richHistoryUpdatedAction({ richHistory }));
if (instance) {
dispatch(runQueries(exploreId));
}
};
}
......@@ -149,20 +172,9 @@ export const stateSave = (): ThunkResult<void> => {
const orgId = getState().user.orgId.toString();
const replace = left && left.urlReplaced === false;
const urlStates: { [index: string]: string } = { orgId };
const leftUrlState: ExploreUrlState = {
datasource: left.datasourceInstance!.name,
queries: left.queries.map(clearQueryKeys),
range: toRawTimeRange(left.range),
};
urlStates.left = serializeStateToUrlParam(leftUrlState, true);
urlStates.left = serializeStateToUrlParam(getUrlStateFromPaneState(left), true);
if (split) {
const rightUrlState: ExploreUrlState = {
datasource: right.datasourceInstance!.name,
queries: right.queries.map(clearQueryKeys),
range: toRawTimeRange(right.range),
};
urlStates.right = serializeStateToUrlParam(rightUrlState, true);
urlStates.right = serializeStateToUrlParam(getUrlStateFromPaneState(right), true);
}
dispatch(updateLocation({ query: urlStates, replace }));
......@@ -259,7 +271,7 @@ export const paneReducer = (state: ExploreItemState = makeExplorePaneState(), ac
}
if (initializeExploreAction.match(action)) {
const { containerWidth, eventBridge, queries, range, originPanelId } = action.payload;
const { containerWidth, eventBridge, queries, range, originPanelId, datasourceInstance, history } = action.payload;
return {
...state,
containerWidth,
......@@ -270,6 +282,11 @@ export const paneReducer = (state: ExploreItemState = makeExplorePaneState(), ac
queryKeys: getQueryKeys(queries, state.datasourceInstance),
originPanelId,
update: makeInitialUpdateState(),
datasourceInstance,
history,
datasourceMissing: !datasourceInstance,
queryResponse: createEmptyQueryResponse(),
logsHighlighterExpressions: undefined,
};
}
......@@ -290,3 +307,13 @@ export const paneReducer = (state: ExploreItemState = makeExplorePaneState(), ac
return state;
};
function getUrlStateFromPaneState(pane: ExploreItemState): ExploreUrlState {
return {
// It can happen that if we are in a split and initial load also runs queries we can be here before the second pane
// is initialized so datasourceInstance will be still undefined.
datasource: pane.datasourceInstance?.name || pane.urlState!.datasource,
queries: pane.queries.map(clearQueryKeys),
range: toRawTimeRange(pane.range),
};
}
......@@ -33,7 +33,7 @@ describe('running queries', () => {
const initialState = {
explore: {
[exploreId]: {
datasourceInstance: 'test-datasource',
datasourceInstance: { name: 'testDs' },
initialized: true,
loading: true,
querySubscription: unsubscribable,
......
......@@ -342,7 +342,7 @@ export const runQueries = (exploreId: ExploreId): ThunkResult<void> => {
liveStreaming: live,
};
const datasourceName = exploreItemState.requestedDatasourceName;
const datasourceName = datasourceInstance.name;
const timeZone = getTimeZone(getState().user);
const transaction = buildQueryTransaction(queries, queryOptions, range, scanning, timeZone);
......
import { EventBusExtended, DefaultTimeRange, LoadingState, LogsDedupStrategy, PanelData } from '@grafana/data';
import {
EventBusExtended,
DefaultTimeRange,
LoadingState,
LogsDedupStrategy,
PanelData,
DataSourceApi,
HistoryItem,
} from '@grafana/data';
import { ExploreItemState, ExploreUpdateState } from 'app/types/explore';
import { getDatasourceSrv } from '../../plugins/datasource_srv';
import store from '../../../core/store';
import { lastUsedDatasourceKeyForOrgId } from '../../../core/utils/explore';
export const DEFAULT_RANGE = {
from: 'now-6h',
......@@ -20,8 +31,6 @@ export const makeInitialUpdateState = (): ExploreUpdateState => ({
export const makeExplorePaneState = (): ExploreItemState => ({
containerWidth: 0,
datasourceInstance: null,
requestedDatasourceName: null,
datasourceLoading: null,
datasourceMissing: false,
history: [],
queries: [],
......@@ -57,3 +66,25 @@ export const createEmptyQueryResponse = (): PanelData => ({
series: [],
timeRange: DefaultTimeRange,
});
export async function loadAndInitDatasource(
orgId: number,
datasourceName?: string
): Promise<{ history: HistoryItem[]; instance: DataSourceApi }> {
const instance = await getDatasourceSrv().get(datasourceName);
if (instance.init) {
try {
instance.init();
} catch (err) {
// TODO: should probably be handled better
console.error(err);
}
}
const historyKey = `grafana.explore.history.${instance.meta?.id}`;
const history = store.getObject(historyKey, []);
// Save last-used datasource
store.set(lastUsedDatasourceKeyForOrgId(orgId), instance.name);
return { history, instance };
}
......@@ -59,14 +59,6 @@ export interface ExploreItemState {
*/
datasourceInstance?: DataSourceApi | null;
/**
* Current data source name or null if default
*/
requestedDatasourceName: string | null;
/**
* True if the datasource is loading. `null` if the loading has not started yet.
*/
datasourceLoading: boolean | null;
/**
* True if there is no datasource to be selected.
*/
datasourceMissing: boolean;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment