| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736 |
- import _ from 'lodash';
- import {
- ensureQueries,
- getQueryKeys,
- parseUrlState,
- DEFAULT_UI_STATE,
- generateNewKeyAndAddRefIdIfMissing,
- sortLogsResult,
- stopQueryState,
- refreshIntervalToSortOrder,
- instanceOfDataQueryError,
- } from 'app/core/utils/explore';
- import { ExploreItemState, ExploreState, ExploreId, ExploreUpdateState, ExploreMode } from 'app/types/explore';
- import { LoadingState } from '@grafana/data';
- import { DataQuery, PanelData } from '@grafana/ui';
- import {
- HigherOrderAction,
- ActionTypes,
- testDataSourcePendingAction,
- testDataSourceSuccessAction,
- testDataSourceFailureAction,
- splitCloseAction,
- SplitCloseActionPayload,
- loadExploreDatasources,
- historyUpdatedAction,
- changeModeAction,
- setUrlReplacedAction,
- scanStopAction,
- queryStartAction,
- changeRangeAction,
- addQueryRowAction,
- changeQueryAction,
- changeSizeAction,
- changeRefreshIntervalAction,
- clearQueriesAction,
- highlightLogsExpressionAction,
- initializeExploreAction,
- updateDatasourceInstanceAction,
- loadDatasourceMissingAction,
- loadDatasourcePendingAction,
- loadDatasourceReadyAction,
- modifyQueriesAction,
- removeQueryRowAction,
- scanStartAction,
- setQueriesAction,
- toggleTableAction,
- queriesImportedAction,
- updateUIStateAction,
- toggleLogLevelAction,
- changeLoadingStateAction,
- resetExploreAction,
- queryEndedAction,
- queryStreamUpdatedAction,
- QueryEndedPayload,
- } from './actionTypes';
- import { reducerFactory, ActionOf } from 'app/core/redux';
- import { updateLocation } from 'app/core/actions/location';
- import { LocationUpdate } from '@grafana/runtime';
- import TableModel from 'app/core/table_model';
- import { isLive } from '@grafana/ui/src/components/RefreshPicker/RefreshPicker';
- import { PanelQueryState, toDataQueryError } from '../../dashboard/state/PanelQueryState';
- import { ResultProcessor } from '../utils/ResultProcessor';
- export const DEFAULT_RANGE = {
- from: 'now-6h',
- to: 'now',
- };
- // Millies step for helper bar charts
- const DEFAULT_GRAPH_INTERVAL = 15 * 1000;
- export const makeInitialUpdateState = (): ExploreUpdateState => ({
- datasource: false,
- queries: false,
- range: false,
- mode: false,
- ui: false,
- });
- /**
- * Returns a fresh Explore area state
- */
- export const makeExploreItemState = (): ExploreItemState => ({
- StartPage: undefined,
- containerWidth: 0,
- datasourceInstance: null,
- requestedDatasourceName: null,
- datasourceError: null,
- datasourceLoading: null,
- datasourceMissing: false,
- exploreDatasources: [],
- history: [],
- queries: [],
- initialized: false,
- queryIntervals: { interval: '15s', intervalMs: DEFAULT_GRAPH_INTERVAL },
- range: {
- from: null,
- to: null,
- raw: DEFAULT_RANGE,
- },
- absoluteRange: {
- from: null,
- to: null,
- },
- scanning: false,
- scanRange: null,
- showingGraph: true,
- showingTable: true,
- loading: false,
- queryKeys: [],
- urlState: null,
- update: makeInitialUpdateState(),
- latency: 0,
- supportedModes: [],
- mode: null,
- isLive: false,
- urlReplaced: false,
- queryState: new PanelQueryState(),
- queryResponse: createEmptyQueryResponse(),
- });
- export const createEmptyQueryResponse = (): PanelData => ({
- state: LoadingState.NotStarted,
- request: null,
- series: [],
- legacy: null,
- error: null,
- });
- /**
- * Global Explore state that handles multiple Explore areas and the split state
- */
- export const initialExploreItemState = makeExploreItemState();
- export const initialExploreState: ExploreState = {
- split: null,
- left: initialExploreItemState,
- right: initialExploreItemState,
- };
- /**
- * Reducer for an Explore area, to be used by the global Explore reducer.
- */
- export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemState)
- .addMapper({
- filter: addQueryRowAction,
- mapper: (state, action): ExploreItemState => {
- const { queries } = state;
- const { index, query } = action.payload;
- // Add to queries, which will cause a new row to be rendered
- const nextQueries = [...queries.slice(0, index + 1), { ...query }, ...queries.slice(index + 1)];
- return {
- ...state,
- queries: nextQueries,
- logsHighlighterExpressions: undefined,
- queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
- };
- },
- })
- .addMapper({
- filter: changeQueryAction,
- mapper: (state, action): ExploreItemState => {
- const { queries } = state;
- const { query, index } = action.payload;
- // Override path: queries are completely reset
- const nextQuery: DataQuery = generateNewKeyAndAddRefIdIfMissing(query, queries, index);
- const nextQueries = [...queries];
- nextQueries[index] = nextQuery;
- return {
- ...state,
- queries: nextQueries,
- queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
- };
- },
- })
- .addMapper({
- filter: changeSizeAction,
- mapper: (state, action): ExploreItemState => {
- const containerWidth = action.payload.width;
- return { ...state, containerWidth };
- },
- })
- .addMapper({
- filter: changeModeAction,
- mapper: (state, action): ExploreItemState => {
- const mode = action.payload.mode;
- return { ...state, mode };
- },
- })
- .addMapper({
- filter: changeRefreshIntervalAction,
- mapper: (state, action): ExploreItemState => {
- const { refreshInterval } = action.payload;
- const live = isLive(refreshInterval);
- const sortOrder = refreshIntervalToSortOrder(refreshInterval);
- const logsResult = sortLogsResult(state.logsResult, sortOrder);
- if (isLive(state.refreshInterval) && !live) {
- stopQueryState(state.queryState, 'Live streaming stopped');
- }
- return {
- ...state,
- refreshInterval,
- queryResponse: {
- ...state.queryResponse,
- state: live ? LoadingState.Streaming : LoadingState.NotStarted,
- },
- isLive: live,
- loading: live,
- logsResult,
- };
- },
- })
- .addMapper({
- filter: clearQueriesAction,
- mapper: (state): ExploreItemState => {
- const queries = ensureQueries();
- stopQueryState(state.queryState, 'Queries cleared');
- return {
- ...state,
- queries: queries.slice(),
- graphResult: null,
- tableResult: null,
- logsResult: null,
- showingStartPage: Boolean(state.StartPage),
- queryKeys: getQueryKeys(queries, state.datasourceInstance),
- queryResponse: createEmptyQueryResponse(),
- loading: false,
- };
- },
- })
- .addMapper({
- filter: highlightLogsExpressionAction,
- mapper: (state, action): ExploreItemState => {
- const { expressions } = action.payload;
- return { ...state, logsHighlighterExpressions: expressions };
- },
- })
- .addMapper({
- filter: initializeExploreAction,
- mapper: (state, action): ExploreItemState => {
- const { containerWidth, eventBridge, queries, range, mode, ui } = action.payload;
- return {
- ...state,
- containerWidth,
- eventBridge,
- range,
- mode,
- queries,
- initialized: true,
- queryKeys: getQueryKeys(queries, state.datasourceInstance),
- ...ui,
- update: makeInitialUpdateState(),
- };
- },
- })
- .addMapper({
- filter: updateDatasourceInstanceAction,
- mapper: (state, action): ExploreItemState => {
- const { datasourceInstance } = action.payload;
- // Capabilities
- const supportsGraph = datasourceInstance.meta.metrics;
- const supportsLogs = datasourceInstance.meta.logs;
- let mode = state.mode || ExploreMode.Metrics;
- const supportedModes: ExploreMode[] = [];
- if (supportsGraph) {
- supportedModes.push(ExploreMode.Metrics);
- }
- if (supportsLogs) {
- supportedModes.push(ExploreMode.Logs);
- }
- if (supportedModes.length === 1) {
- mode = supportedModes[0];
- }
- // Custom components
- const StartPage = datasourceInstance.components.ExploreStartPage;
- stopQueryState(state.queryState, 'Datasource changed');
- return {
- ...state,
- datasourceInstance,
- graphResult: null,
- tableResult: null,
- logsResult: null,
- latency: 0,
- queryResponse: createEmptyQueryResponse(),
- loading: false,
- StartPage,
- showingStartPage: Boolean(StartPage),
- queryKeys: [],
- supportedModes,
- mode,
- };
- },
- })
- .addMapper({
- filter: loadDatasourceMissingAction,
- mapper: (state): ExploreItemState => {
- return {
- ...state,
- datasourceMissing: true,
- datasourceLoading: false,
- update: makeInitialUpdateState(),
- };
- },
- })
- .addMapper({
- filter: loadDatasourcePendingAction,
- mapper: (state, action): ExploreItemState => {
- return {
- ...state,
- datasourceLoading: true,
- requestedDatasourceName: action.payload.requestedDatasourceName,
- };
- },
- })
- .addMapper({
- filter: loadDatasourceReadyAction,
- mapper: (state, action): ExploreItemState => {
- const { history } = action.payload;
- return {
- ...state,
- history,
- datasourceLoading: false,
- datasourceMissing: false,
- logsHighlighterExpressions: undefined,
- update: makeInitialUpdateState(),
- };
- },
- })
- .addMapper({
- filter: modifyQueriesAction,
- mapper: (state, action): ExploreItemState => {
- const { queries } = state;
- const { modification, index, modifier } = action.payload;
- let nextQueries: DataQuery[];
- if (index === undefined) {
- // Modify all queries
- nextQueries = queries.map((query, i) => {
- const nextQuery = modifier({ ...query }, modification);
- return generateNewKeyAndAddRefIdIfMissing(nextQuery, queries, i);
- });
- } else {
- // Modify query only at index
- nextQueries = queries.map((query, i) => {
- if (i === index) {
- const nextQuery = modifier({ ...query }, modification);
- return generateNewKeyAndAddRefIdIfMissing(nextQuery, queries, i);
- }
- return query;
- });
- }
- return {
- ...state,
- queries: nextQueries,
- queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
- };
- },
- })
- .addMapper({
- filter: queryStartAction,
- mapper: (state): ExploreItemState => {
- return {
- ...state,
- latency: 0,
- queryResponse: {
- ...state.queryResponse,
- state: LoadingState.Loading,
- error: null,
- },
- loading: true,
- update: makeInitialUpdateState(),
- };
- },
- })
- .addMapper({
- filter: removeQueryRowAction,
- mapper: (state, action): ExploreItemState => {
- const { queries, queryKeys } = state;
- const { index } = action.payload;
- if (queries.length <= 1) {
- return state;
- }
- const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)];
- const nextQueryKeys = [...queryKeys.slice(0, index), ...queryKeys.slice(index + 1)];
- return {
- ...state,
- queries: nextQueries,
- logsHighlighterExpressions: undefined,
- queryKeys: nextQueryKeys,
- };
- },
- })
- .addMapper({
- filter: scanStartAction,
- mapper: (state, action): ExploreItemState => {
- return { ...state, scanning: true };
- },
- })
- .addMapper({
- filter: scanStopAction,
- mapper: (state): ExploreItemState => {
- return {
- ...state,
- scanning: false,
- scanRange: undefined,
- update: makeInitialUpdateState(),
- };
- },
- })
- .addMapper({
- filter: setQueriesAction,
- mapper: (state, action): ExploreItemState => {
- const { queries } = action.payload;
- return {
- ...state,
- queries: queries.slice(),
- queryKeys: getQueryKeys(queries, state.datasourceInstance),
- };
- },
- })
- .addMapper({
- filter: updateUIStateAction,
- mapper: (state, action): ExploreItemState => {
- return { ...state, ...action.payload };
- },
- })
- .addMapper({
- filter: toggleTableAction,
- mapper: (state): ExploreItemState => {
- const showingTable = !state.showingTable;
- if (showingTable) {
- return { ...state };
- }
- return { ...state, tableResult: new TableModel() };
- },
- })
- .addMapper({
- filter: queriesImportedAction,
- mapper: (state, action): ExploreItemState => {
- const { queries } = action.payload;
- return {
- ...state,
- queries,
- queryKeys: getQueryKeys(queries, state.datasourceInstance),
- };
- },
- })
- .addMapper({
- filter: toggleLogLevelAction,
- mapper: (state, action): ExploreItemState => {
- const { hiddenLogLevels } = action.payload;
- return {
- ...state,
- hiddenLogLevels: Array.from(hiddenLogLevels),
- };
- },
- })
- .addMapper({
- filter: testDataSourcePendingAction,
- mapper: (state): ExploreItemState => {
- return {
- ...state,
- datasourceError: null,
- };
- },
- })
- .addMapper({
- filter: testDataSourceSuccessAction,
- mapper: (state): ExploreItemState => {
- return {
- ...state,
- datasourceError: null,
- };
- },
- })
- .addMapper({
- filter: testDataSourceFailureAction,
- mapper: (state, action): ExploreItemState => {
- return {
- ...state,
- datasourceError: action.payload.error,
- graphResult: undefined,
- tableResult: undefined,
- logsResult: undefined,
- update: makeInitialUpdateState(),
- };
- },
- })
- .addMapper({
- filter: loadExploreDatasources,
- mapper: (state, action): ExploreItemState => {
- return {
- ...state,
- exploreDatasources: action.payload.exploreDatasources,
- };
- },
- })
- .addMapper({
- filter: historyUpdatedAction,
- mapper: (state, action): ExploreItemState => {
- return {
- ...state,
- history: action.payload.history,
- };
- },
- })
- .addMapper({
- filter: setUrlReplacedAction,
- mapper: (state): ExploreItemState => {
- return {
- ...state,
- urlReplaced: true,
- };
- },
- })
- .addMapper({
- filter: changeRangeAction,
- mapper: (state, action): ExploreItemState => {
- const { range, absoluteRange } = action.payload;
- return {
- ...state,
- range,
- absoluteRange,
- };
- },
- })
- .addMapper({
- filter: changeLoadingStateAction,
- mapper: (state, action): ExploreItemState => {
- const { loadingState } = action.payload;
- return {
- ...state,
- queryResponse: {
- ...state.queryResponse,
- state: loadingState,
- },
- loading: loadingState === LoadingState.Loading || loadingState === LoadingState.Streaming,
- };
- },
- })
- .addMapper({
- //queryStreamUpdatedAction
- filter: queryEndedAction,
- mapper: (state, action): ExploreItemState => {
- return processQueryResponse(state, action);
- },
- })
- .addMapper({
- filter: queryStreamUpdatedAction,
- mapper: (state, action): ExploreItemState => {
- return processQueryResponse(state, action);
- },
- })
- .create();
- export const processQueryResponse = (
- state: ExploreItemState,
- action: ActionOf<QueryEndedPayload>
- ): ExploreItemState => {
- const { response } = action.payload;
- const { request, state: loadingState, series, legacy, error } = response;
- const replacePreviousResults = action.type === queryEndedAction.type;
- if (error) {
- // For Angular editors
- state.eventBridge.emit('data-error', error);
- console.error(error); // To help finding problems with query syntax
- if (!instanceOfDataQueryError(error)) {
- response.error = toDataQueryError(error);
- }
- return {
- ...state,
- loading: false,
- queryResponse: response,
- graphResult: null,
- tableResult: null,
- logsResult: null,
- showingStartPage: false,
- update: makeInitialUpdateState(),
- };
- }
- const latency = request.endTime - request.startTime;
- // temporary hack until we switch to PanelData, Loki already converts to DataFrame so using legacy will destroy the format
- const isLokiDataSource = state.datasourceInstance.meta.name === 'Loki';
- const processor = new ResultProcessor(state, replacePreviousResults, isLokiDataSource ? series : legacy);
- // For Angular editors
- state.eventBridge.emit('data-received', processor.getRawData());
- return {
- ...state,
- latency,
- queryResponse: response,
- graphResult: processor.getGraphResult(),
- tableResult: processor.getTableResult(),
- logsResult: processor.getLogsResult(),
- loading: loadingState === LoadingState.Loading || loadingState === LoadingState.Streaming,
- showingStartPage: false,
- update: makeInitialUpdateState(),
- };
- };
- export const updateChildRefreshState = (
- state: Readonly<ExploreItemState>,
- payload: LocationUpdate,
- exploreId: ExploreId
- ): ExploreItemState => {
- const path = payload.path || '';
- const queryState = payload.query[exploreId] as string;
- if (!queryState) {
- return state;
- }
- const urlState = parseUrlState(queryState);
- if (!state.urlState || path !== '/explore') {
- // we only want to refresh when browser back/forward
- return {
- ...state,
- urlState,
- update: { datasource: false, queries: false, range: false, mode: false, ui: false },
- };
- }
- const datasource = _.isEqual(urlState ? urlState.datasource : '', state.urlState.datasource) === false;
- const queries = _.isEqual(urlState ? urlState.queries : [], state.urlState.queries) === false;
- const range = _.isEqual(urlState ? urlState.range : DEFAULT_RANGE, state.urlState.range) === false;
- const mode = _.isEqual(urlState ? urlState.mode : ExploreMode.Metrics, state.urlState.mode) === false;
- const ui = _.isEqual(urlState ? urlState.ui : DEFAULT_UI_STATE, state.urlState.ui) === false;
- return {
- ...state,
- urlState,
- update: {
- ...state.update,
- datasource,
- queries,
- range,
- mode,
- ui,
- },
- };
- };
- /**
- * Global Explore reducer that handles multiple Explore areas (left and right).
- * Actions that have an `exploreId` get routed to the ExploreItemReducer.
- */
- export const exploreReducer = (state = initialExploreState, action: HigherOrderAction): ExploreState => {
- switch (action.type) {
- case splitCloseAction.type: {
- const { itemId } = action.payload as SplitCloseActionPayload;
- const targetSplit = {
- left: itemId === ExploreId.left ? state.right : state.left,
- right: initialExploreState.right,
- };
- return {
- ...state,
- ...targetSplit,
- split: false,
- };
- }
- case ActionTypes.SplitOpen: {
- return { ...state, split: true, right: { ...action.payload.itemState } };
- }
- case ActionTypes.ResetExplore: {
- return initialExploreState;
- }
- case updateLocation.type: {
- const { query } = action.payload;
- if (!query || !query[ExploreId.left]) {
- return state;
- }
- const split = query[ExploreId.right] ? true : false;
- const leftState = state[ExploreId.left];
- const rightState = state[ExploreId.right];
- return {
- ...state,
- split,
- [ExploreId.left]: updateChildRefreshState(leftState, action.payload, ExploreId.left),
- [ExploreId.right]: updateChildRefreshState(rightState, action.payload, ExploreId.right),
- };
- }
- case resetExploreAction.type: {
- const leftState = state[ExploreId.left];
- const rightState = state[ExploreId.right];
- stopQueryState(leftState.queryState, 'Navigated away from Explore');
- stopQueryState(rightState.queryState, 'Navigated away from Explore');
- return {
- ...state,
- [ExploreId.left]: updateChildRefreshState(leftState, action.payload, ExploreId.left),
- [ExploreId.right]: updateChildRefreshState(rightState, action.payload, ExploreId.right),
- };
- }
- }
- if (action.payload) {
- const { exploreId } = action.payload as any;
- if (exploreId !== undefined) {
- // @ts-ignore
- const exploreItemState = state[exploreId];
- return { ...state, [exploreId]: itemReducer(exploreItemState, action) };
- }
- }
- return state;
- };
- export default {
- explore: exploreReducer,
- };
|