grafana/public/app/features/explore/state/datasource.ts
Kristina f18a02149a
Correlations: Create paginated API (#65241)
* Add pagination params and apply to sql

* Create getCorrelationsResponse that returns metadata

* Set up pagination, change correlations fetch to only get source datasource correlations

* Move correlations from root to pane, only fetch correlations for one datasource when initialized or datasource is changed

* Fix tests

* Fix remaining tests

* Use functional component to handle state

* Remove unneeded mocks, fix tests

* Change perPage to limit

* Fix Go Tests

* Fix linter

* Remove parameter

* Account for mixed datasources

* Delete unused hook

* add source UID filter to API, start backing out front end hook changes

* add source IDs to API, use when loading or changing datasource

* Fix prettier

* Mock correlations response

* Get correlations for all datasources in mixed scenario

* Add documentation for new parameters

* Attempt to fix swagger

* Fix correlations page

* add swagger and openapi docs

* Add mocks to failing test

* Change API for consistency, remove extra hooks and unused function

* Add max to limit and re-gen api docs

* Move the page to the previous page if deleting all the rows on the page

* Only fetch if remove does not have value

* Change page to a reference hook

* Fix documentation, a test and some logic thinking page could be 0
2023-07-05 09:37:17 -05:00

120 lines
3.9 KiB
TypeScript

// Libraries
import { AnyAction, createAction } from '@reduxjs/toolkit';
import { DataSourceApi, HistoryItem } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime';
import { DataSourceRef } from '@grafana/schema';
import { RefreshPicker } from '@grafana/ui';
import { stopQueryState } from 'app/core/utils/explore';
import { getCorrelationsBySourceUIDs } from 'app/features/correlations/utils';
import { ExploreItemState, ThunkResult } from 'app/types';
import { loadSupplementaryQueries } from '../utils/supplementaryQueries';
import { saveCorrelationsAction } from './explorePane';
import { importQueries, runQueries } from './query';
import { changeRefreshInterval } from './time';
import { createEmptyQueryResponse, getDatasourceUIDs, loadAndInitDatasource } from './utils';
//
// Actions and Payloads
//
/**
* Updates datasource instance before datasource loading has started
*/
export interface UpdateDatasourceInstancePayload {
exploreId: string;
datasourceInstance: DataSourceApi;
history: HistoryItem[];
}
export const updateDatasourceInstanceAction = createAction<UpdateDatasourceInstancePayload>(
'explore/updateDatasourceInstance'
);
//
// Action creators
//
/**
* Loads a new datasource identified by the given name.
*/
export function changeDatasource(
exploreId: string,
datasource: string | DataSourceRef,
options?: { importQueries: boolean }
): ThunkResult<Promise<void>> {
return async (dispatch, getState) => {
const orgId = getState().user.orgId;
const { history, instance } = await loadAndInitDatasource(orgId, datasource);
const currentDataSourceInstance = getState().explore.panes[exploreId]!.datasourceInstance;
reportInteraction('explore_change_ds', {
from: (currentDataSourceInstance?.meta?.mixed ? 'mixed' : currentDataSourceInstance?.type) || 'unknown',
to: instance.meta.mixed ? 'mixed' : instance.type,
exploreId,
});
dispatch(
updateDatasourceInstanceAction({
exploreId,
datasourceInstance: instance,
history,
})
);
const queries = getState().explore.panes[exploreId]!.queries;
const datasourceUIDs = getDatasourceUIDs(instance.uid, queries);
const correlations = await getCorrelationsBySourceUIDs(datasourceUIDs);
dispatch(saveCorrelationsAction({ exploreId: exploreId, correlations: correlations.correlations || [] }));
if (options?.importQueries) {
await dispatch(importQueries(exploreId, queries, currentDataSourceInstance, instance));
}
if (getState().explore.panes[exploreId]!.isLive) {
dispatch(changeRefreshInterval({ exploreId, refreshInterval: RefreshPicker.offOption.value }));
}
// Exception - we only want to run queries on data source change, if the queries were imported
if (options?.importQueries) {
dispatch(runQueries({ exploreId }));
}
};
}
//
// Reducer
//
/**
* Reducer for an Explore area, to be used by the global Explore reducer.
*/
// Redux Toolkit uses ImmerJs as part of their solution to ensure that state objects are not mutated.
// ImmerJs has an autoFreeze option that freezes objects from change which means this reducer can't be migrated to createSlice
// because the state would become frozen and during run time we would get errors because flot (Graph lib) would try to mutate
// the frozen state.
// https://github.com/reduxjs/redux-toolkit/issues/242
export const datasourceReducer = (state: ExploreItemState, action: AnyAction): ExploreItemState => {
if (updateDatasourceInstanceAction.match(action)) {
const { datasourceInstance, history } = action.payload;
// Custom components
stopQueryState(state.querySubscription);
return {
...state,
datasourceInstance,
graphResult: null,
tableResult: null,
logsResult: null,
supplementaryQueries: loadSupplementaryQueries(),
queryResponse: createEmptyQueryResponse(),
queryKeys: [],
history,
};
}
return state;
};