Explore: Remove not running query for collapsed elements (#27026)

* Make graph and table collapsing just a UI thing

* Remove showingGraph and showingTable, set them defaultly to true

* Remove collaapsing for panels in Explore

* UI toggle WiP

* WIP, add query type

* Refactor, clean up

* Update tests

* Clean uo

* Update rangeAll to range and instant

* Remove console logs

* Update packages/grafana-data/src/types/datasource.ts

Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com>

* Update public/app/core/utils/explore.ts

Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com>

* Fix prettier error

Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com>
pull/27711/head
Ivana Huckova 5 years ago committed by GitHub
parent 32deddbf0b
commit f6c91d1318
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      packages/grafana-data/src/types/datasource.ts
  2. 7
      public/app/core/utils/explore.ts
  3. 4
      public/app/features/explore/Explore.test.tsx
  4. 19
      public/app/features/explore/Explore.tsx
  5. 25
      public/app/features/explore/ExploreGraphPanel.tsx
  6. 2
      public/app/features/explore/Logs.tsx
  7. 26
      public/app/features/explore/LogsContainer.tsx
  8. 5
      public/app/features/explore/TableContainer.test.tsx
  9. 17
      public/app/features/explore/TableContainer.tsx
  10. 2
      public/app/features/explore/__snapshots__/TableContainer.test.tsx.snap
  11. 18
      public/app/features/explore/state/actionTypes.ts
  12. 58
      public/app/features/explore/state/actions.ts
  13. 38
      public/app/features/explore/state/reducers.test.ts
  14. 22
      public/app/features/explore/state/reducers.ts
  15. 92
      public/app/plugins/datasource/prometheus/components/PromExploreQueryEditor.tsx
  16. 2
      public/app/plugins/datasource/prometheus/components/PromQueryField.tsx
  17. 104
      public/app/plugins/datasource/prometheus/components/__snapshots__/PromExploreQueryEditor.test.tsx.snap
  18. 104
      public/app/plugins/datasource/prometheus/datasource.test.ts
  19. 49
      public/app/plugins/datasource/prometheus/datasource.ts
  20. 1
      public/app/plugins/datasource/prometheus/types.ts
  21. 10
      public/app/types/explore.ts

@ -453,6 +453,9 @@ export interface DataQueryRequest<TQuery extends DataQuery = DataQuery> {
// Explore state used by various datasources // Explore state used by various datasources
liveStreaming?: boolean; liveStreaming?: boolean;
/**
* @deprecated showingGraph and showingTable are always set to true and set to true
*/
showingGraph?: boolean; showingGraph?: boolean;
showingTable?: boolean; showingTable?: boolean;
} }

@ -160,8 +160,11 @@ export function buildQueryTransaction(
maxDataPoints: queryOptions.maxDataPoints, maxDataPoints: queryOptions.maxDataPoints,
exploreMode: queryOptions.mode, exploreMode: queryOptions.mode,
liveStreaming: queryOptions.liveStreaming, liveStreaming: queryOptions.liveStreaming,
showingGraph: queryOptions.showingGraph, /**
showingTable: queryOptions.showingTable, * @deprecated (external API) showingGraph and showingTable are always set to true and set to true
*/
showingGraph: true,
showingTable: true,
}; };
return { return {

@ -5,7 +5,6 @@ import { ExploreId } from 'app/types/explore';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { Explore, ExploreProps } from './Explore'; import { Explore, ExploreProps } from './Explore';
import { scanStopAction } from './state/actionTypes'; import { scanStopAction } from './state/actionTypes';
import { toggleGraph } from './state/actions';
import { SecondaryActions } from './SecondaryActions'; import { SecondaryActions } from './SecondaryActions';
import { getTheme } from '@grafana/ui'; import { getTheme } from '@grafana/ui';
@ -67,11 +66,8 @@ const dummyProps: ExploreProps = {
from: 0, from: 0,
to: 0, to: 0,
}, },
showingGraph: false,
showingTable: false,
timeZone: 'UTC', timeZone: 'UTC',
onHiddenSeriesChanged: jest.fn(), onHiddenSeriesChanged: jest.fn(),
toggleGraph: toggleGraph,
queryResponse: { queryResponse: {
state: LoadingState.NotStarted, state: LoadingState.NotStarted,
series: [], series: [],

@ -37,7 +37,6 @@ import {
refreshExplore, refreshExplore,
scanStart, scanStart,
setQueries, setQueries,
toggleGraph,
updateTimeRange, updateTimeRange,
} from './state/actions'; } from './state/actions';
@ -114,11 +113,8 @@ export interface ExploreProps {
logsResult?: LogsModel; logsResult?: LogsModel;
loading?: boolean; loading?: boolean;
absoluteRange: AbsoluteTimeRange; absoluteRange: AbsoluteTimeRange;
showingGraph?: boolean;
showingTable?: boolean;
timeZone?: TimeZone; timeZone?: TimeZone;
onHiddenSeriesChanged?: (hiddenSeries: string[]) => void; onHiddenSeriesChanged?: (hiddenSeries: string[]) => void;
toggleGraph: typeof toggleGraph;
queryResponse: PanelData; queryResponse: PanelData;
originPanelId: number; originPanelId: number;
addQueryRow: typeof addQueryRow; addQueryRow: typeof addQueryRow;
@ -269,11 +265,6 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
this.props.scanStopAction({ exploreId: this.props.exploreId }); this.props.scanStopAction({ exploreId: this.props.exploreId });
}; };
onToggleGraph = (showingGraph: boolean) => {
const { toggleGraph, exploreId } = this.props;
toggleGraph(exploreId, showingGraph);
};
onUpdateTimeRange = (absoluteRange: AbsoluteTimeRange) => { onUpdateTimeRange = (absoluteRange: AbsoluteTimeRange) => {
const { exploreId, updateTimeRange } = this.props; const { exploreId, updateTimeRange } = this.props;
updateTimeRange({ exploreId, absoluteRange }); updateTimeRange({ exploreId, absoluteRange });
@ -321,8 +312,6 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
graphResult, graphResult,
loading, loading,
absoluteRange, absoluteRange,
showingGraph,
showingTable,
timeZone, timeZone,
queryResponse, queryResponse,
syncedTimes, syncedTimes,
@ -396,10 +385,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
absoluteRange={absoluteRange} absoluteRange={absoluteRange}
isStacked={false} isStacked={false}
showPanel={true} showPanel={true}
showingGraph={showingGraph}
showingTable={showingTable}
timeZone={timeZone} timeZone={timeZone}
onToggleGraph={this.onToggleGraph}
onUpdateTimeRange={this.onUpdateTimeRange} onUpdateTimeRange={this.onUpdateTimeRange}
showBars={false} showBars={false}
showLines={true} showLines={true}
@ -484,8 +470,6 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps): Partia
showTable, showTable,
showTrace, showTrace,
loading, loading,
showingGraph,
showingTable,
absoluteRange, absoluteRange,
queryResponse, queryResponse,
} = item; } = item;
@ -514,8 +498,6 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps): Partia
graphResult: graphResult ?? undefined, graphResult: graphResult ?? undefined,
logsResult: logsResult ?? undefined, logsResult: logsResult ?? undefined,
loading, loading,
showingGraph,
showingTable,
absoluteRange, absoluteRange,
queryResponse, queryResponse,
originPanelId, originPanelId,
@ -537,7 +519,6 @@ const mapDispatchToProps: Partial<ExploreProps> = {
scanStopAction, scanStopAction,
setQueries, setQueries,
updateTimeRange, updateTimeRange,
toggleGraph,
addQueryRow, addQueryRow,
}; };

@ -49,11 +49,8 @@ interface Props extends Themeable {
showBars: boolean; showBars: boolean;
showLines: boolean; showLines: boolean;
isStacked: boolean; isStacked: boolean;
showingGraph?: boolean;
showingTable?: boolean;
timeZone?: TimeZone; timeZone?: TimeZone;
onUpdateTimeRange: (absoluteRange: AbsoluteTimeRange) => void; onUpdateTimeRange: (absoluteRange: AbsoluteTimeRange) => void;
onToggleGraph?: (showingGraph: boolean) => void;
onHiddenSeriesChanged?: (hiddenSeries: string[]) => void; onHiddenSeriesChanged?: (hiddenSeries: string[]) => void;
} }
@ -74,13 +71,6 @@ class UnThemedExploreGraphPanel extends PureComponent<Props, State> {
}); });
}; };
onClickGraphButton = () => {
const { onToggleGraph, showingGraph } = this.props;
if (onToggleGraph) {
onToggleGraph(showingGraph ?? false);
}
};
onChangeTime = (from: number, to: number) => { onChangeTime = (from: number, to: number) => {
const { onUpdateTimeRange } = this.props; const { onUpdateTimeRange } = this.props;
onUpdateTimeRange({ from, to }); onUpdateTimeRange({ from, to });
@ -95,8 +85,6 @@ class UnThemedExploreGraphPanel extends PureComponent<Props, State> {
timeZone, timeZone,
absoluteRange, absoluteRange,
showPanel, showPanel,
showingGraph,
showingTable,
showBars, showBars,
showLines, showLines,
isStacked, isStacked,
@ -116,10 +104,9 @@ class UnThemedExploreGraphPanel extends PureComponent<Props, State> {
}, },
}; };
const height = showPanel === false ? 100 : showingGraph && showingTable ? 200 : 400; const height = showPanel ? 200 : 100;
const lineWidth = showLines ? 1 : 5; const lineWidth = showLines ? 1 : 5;
const seriesToShow = showAllTimeSeries ? series : series.slice(0, MAX_NUMBER_OF_TIME_SERIES); const seriesToShow = showAllTimeSeries ? series : series.slice(0, MAX_NUMBER_OF_TIME_SERIES);
return ( return (
<GraphSeriesToggler series={seriesToShow} onHiddenSeriesChanged={onHiddenSeriesChanged}> <GraphSeriesToggler series={seriesToShow} onHiddenSeriesChanged={onHiddenSeriesChanged}>
{({ onSeriesToggle, toggledSeries }: GraphSeriesTogglerAPI) => { {({ onSeriesToggle, toggledSeries }: GraphSeriesTogglerAPI) => {
@ -153,7 +140,7 @@ class UnThemedExploreGraphPanel extends PureComponent<Props, State> {
}; };
render() { render() {
const { series, showPanel, showingGraph, loading, theme } = this.props; const { series, showPanel, loading, theme } = this.props;
const { showAllTimeSeries } = this.state; const { showAllTimeSeries } = this.state;
const style = getStyles(theme); const style = getStyles(theme);
@ -171,13 +158,7 @@ class UnThemedExploreGraphPanel extends PureComponent<Props, State> {
)} )}
{showPanel && ( {showPanel && (
<Collapse <Collapse label="Graph" loading={loading} isOpen>
label="Graph"
collapsible
isOpen={showingGraph}
loading={loading}
onToggle={this.onClickGraphButton}
>
{this.renderGraph()} {this.renderGraph()}
</Collapse> </Collapse>
)} )}

@ -266,8 +266,6 @@ export class Logs extends PureComponent<Props, State> {
absoluteRange={visibleRange || absoluteRange} absoluteRange={visibleRange || absoluteRange}
isStacked={true} isStacked={true}
showPanel={false} showPanel={false}
showingGraph={true}
showingTable={true}
timeZone={timeZone} timeZone={timeZone}
showBars={true} showBars={true}
showLines={false} showLines={false}

@ -62,15 +62,7 @@ interface LogsContainerProps {
splitOpen: typeof splitOpen; splitOpen: typeof splitOpen;
} }
interface LogsContainerState { export class LogsContainer extends PureComponent<LogsContainerProps> {
logsContainerOpen: boolean;
}
export class LogsContainer extends PureComponent<LogsContainerProps, LogsContainerState> {
state: LogsContainerState = {
logsContainerOpen: true,
};
onChangeTime = (absoluteRange: AbsoluteTimeRange) => { onChangeTime = (absoluteRange: AbsoluteTimeRange) => {
const { exploreId, updateTimeRange } = this.props; const { exploreId, updateTimeRange } = this.props;
updateTimeRange({ exploreId, absoluteRange }); updateTimeRange({ exploreId, absoluteRange });
@ -102,12 +94,6 @@ export class LogsContainer extends PureComponent<LogsContainerProps, LogsContain
return getFieldLinksForExplore(field, rowIndex, this.props.splitOpen, this.props.range); return getFieldLinksForExplore(field, rowIndex, this.props.splitOpen, this.props.range);
}; };
onToggleCollapse = () => {
this.setState(state => ({
logsContainerOpen: !state.logsContainerOpen,
}));
};
render() { render() {
const { const {
loading, loading,
@ -130,8 +116,6 @@ export class LogsContainer extends PureComponent<LogsContainerProps, LogsContain
exploreId, exploreId,
} = this.props; } = this.props;
const { logsContainerOpen } = this.state;
return ( return (
<> <>
<LogsCrossFadeTransition visible={isLive}> <LogsCrossFadeTransition visible={isLive}>
@ -151,13 +135,7 @@ export class LogsContainer extends PureComponent<LogsContainerProps, LogsContain
</Collapse> </Collapse>
</LogsCrossFadeTransition> </LogsCrossFadeTransition>
<LogsCrossFadeTransition visible={!isLive}> <LogsCrossFadeTransition visible={!isLive}>
<Collapse <Collapse label="Logs" loading={loading} isOpen>
label="Logs"
loading={loading}
isOpen={logsContainerOpen}
onToggle={this.onToggleCollapse}
collapsible
>
<Logs <Logs
dedupStrategy={this.props.dedupStrategy || LogsDedupStrategy.none} dedupStrategy={this.props.dedupStrategy || LogsDedupStrategy.none}
logRows={logRows} logRows={logRows}

@ -2,7 +2,6 @@ import React from 'react';
import { render, shallow } from 'enzyme'; import { render, shallow } from 'enzyme';
import { TableContainer } from './TableContainer'; import { TableContainer } from './TableContainer';
import { DataFrame } from '@grafana/data'; import { DataFrame } from '@grafana/data';
import { toggleTable } from './state/actions';
import { ExploreId } from 'app/types/explore'; import { ExploreId } from 'app/types/explore';
describe('TableContainer', () => { describe('TableContainer', () => {
@ -12,9 +11,7 @@ describe('TableContainer', () => {
loading: false, loading: false,
width: 800, width: 800,
onCellFilterAdded: jest.fn(), onCellFilterAdded: jest.fn(),
showingTable: true,
tableResult: {} as DataFrame, tableResult: {} as DataFrame,
toggleTable: {} as typeof toggleTable,
splitOpen: (() => {}) as any, splitOpen: (() => {}) as any,
range: {} as any, range: {} as any,
}; };
@ -29,13 +26,11 @@ describe('TableContainer', () => {
loading: false, loading: false,
width: 800, width: 800,
onCellFilterAdded: jest.fn(), onCellFilterAdded: jest.fn(),
showingTable: true,
tableResult: { tableResult: {
name: 'TableResultName', name: 'TableResultName',
fields: [], fields: [],
length: 0, length: 0,
} as DataFrame, } as DataFrame,
toggleTable: {} as typeof toggleTable,
splitOpen: (() => {}) as any, splitOpen: (() => {}) as any,
range: {} as any, range: {} as any,
}; };

@ -5,7 +5,7 @@ import { DataFrame, TimeRange, ValueLinkConfig } from '@grafana/data';
import { Collapse, Table } from '@grafana/ui'; import { Collapse, Table } from '@grafana/ui';
import { ExploreId, ExploreItemState } from 'app/types/explore'; import { ExploreId, ExploreItemState } from 'app/types/explore';
import { StoreState } from 'app/types'; import { StoreState } from 'app/types';
import { splitOpen, toggleTable } from './state/actions'; import { splitOpen } from './state/actions';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
import { PANEL_BORDER } from 'app/core/constants'; import { PANEL_BORDER } from 'app/core/constants';
import { MetaInfoText } from './MetaInfoText'; import { MetaInfoText } from './MetaInfoText';
@ -18,18 +18,12 @@ interface TableContainerProps {
loading: boolean; loading: boolean;
width: number; width: number;
onCellFilterAdded?: (filter: FilterItem) => void; onCellFilterAdded?: (filter: FilterItem) => void;
showingTable: boolean;
tableResult?: DataFrame; tableResult?: DataFrame;
toggleTable: typeof toggleTable;
splitOpen: typeof splitOpen; splitOpen: typeof splitOpen;
range: TimeRange; range: TimeRange;
} }
export class TableContainer extends PureComponent<TableContainerProps> { export class TableContainer extends PureComponent<TableContainerProps> {
onClickTableButton = () => {
this.props.toggleTable(this.props.exploreId, this.props.showingTable);
};
getTableHeight() { getTableHeight() {
const { tableResult } = this.props; const { tableResult } = this.props;
@ -42,7 +36,7 @@ export class TableContainer extends PureComponent<TableContainerProps> {
} }
render() { render() {
const { loading, onCellFilterAdded, showingTable, tableResult, width, splitOpen, range, ariaLabel } = this.props; const { loading, onCellFilterAdded, tableResult, width, splitOpen, range, ariaLabel } = this.props;
const height = this.getTableHeight(); const height = this.getTableHeight();
const tableWidth = width - config.theme.panelPadding * 2 - PANEL_BORDER; const tableWidth = width - config.theme.panelPadding * 2 - PANEL_BORDER;
@ -60,7 +54,7 @@ export class TableContainer extends PureComponent<TableContainerProps> {
} }
return ( return (
<Collapse label="Table" loading={loading} collapsible isOpen={showingTable} onToggle={this.onClickTableButton}> <Collapse label="Table" loading={loading} isOpen>
{hasTableResult ? ( {hasTableResult ? (
<Table <Table
ariaLabel={ariaLabel} ariaLabel={ariaLabel}
@ -81,13 +75,12 @@ function mapStateToProps(state: StoreState, { exploreId }: { exploreId: string }
const explore = state.explore; const explore = state.explore;
// @ts-ignore // @ts-ignore
const item: ExploreItemState = explore[exploreId]; const item: ExploreItemState = explore[exploreId];
const { loading: loadingInState, showingTable, tableResult, range } = item; const { loading: loadingInState, tableResult, range } = item;
const loading = tableResult && tableResult.length > 0 ? false : loadingInState; const loading = tableResult && tableResult.length > 0 ? false : loadingInState;
return { loading, showingTable, tableResult, range }; return { loading, tableResult, range };
} }
const mapDispatchToProps = { const mapDispatchToProps = {
toggleTable,
splitOpen, splitOpen,
}; };

@ -2,11 +2,9 @@
exports[`TableContainer should render component 1`] = ` exports[`TableContainer should render component 1`] = `
<Collapse <Collapse
collapsible={true}
isOpen={true} isOpen={true}
label="Table" label="Table"
loading={false} loading={false}
onToggle={[Function]}
> >
<Memo(MetaInfoText) <Memo(MetaInfoText)
metaItems={ metaItems={

@ -126,14 +126,6 @@ export interface SyncTimesPayload {
syncedTimes: boolean; syncedTimes: boolean;
} }
export interface ToggleTablePayload {
exploreId: ExploreId;
}
export interface ToggleGraphPayload {
exploreId: ExploreId;
}
export interface UpdateUIStatePayload extends Partial<ExploreUIState> { export interface UpdateUIStatePayload extends Partial<ExploreUIState> {
exploreId: ExploreId; exploreId: ExploreId;
} }
@ -296,16 +288,6 @@ export const richHistoryUpdatedAction = createAction<any>('explore/richHistoryUp
*/ */
export const updateUIStateAction = createAction<UpdateUIStatePayload>('explore/updateUIState'); export const updateUIStateAction = createAction<UpdateUIStatePayload>('explore/updateUIState');
/**
* Expand/collapse the table result viewer. When collapsed, table queries won't be run.
*/
export const toggleTableAction = createAction<ToggleTablePayload>('explore/toggleTable');
/**
* Expand/collapse the graph result viewer. When collapsed, graph queries won't be run.
*/
export const toggleGraphAction = createAction<ToggleGraphPayload>('explore/toggleGraph');
/** /**
* Updates datasource instance before datasouce loading has started * Updates datasource instance before datasouce loading has started
*/ */

@ -1,7 +1,7 @@
// Libraries // Libraries
import { map, throttleTime } from 'rxjs/operators'; import { map, throttleTime } from 'rxjs/operators';
import { identity } from 'rxjs'; import { identity } from 'rxjs';
import { ActionCreatorWithPayload, PayloadAction } from '@reduxjs/toolkit'; import { PayloadAction } from '@reduxjs/toolkit';
import { DataSourceSrv } from '@grafana/runtime'; import { DataSourceSrv } from '@grafana/runtime';
import { RefreshPicker } from '@grafana/ui'; import { RefreshPicker } from '@grafana/ui';
import { import {
@ -77,10 +77,6 @@ import {
splitCloseAction, splitCloseAction,
splitOpenAction, splitOpenAction,
syncTimesAction, syncTimesAction,
toggleGraphAction,
ToggleGraphPayload,
toggleTableAction,
ToggleTablePayload,
updateDatasourceInstanceAction, updateDatasourceInstanceAction,
updateUIStateAction, updateUIStateAction,
changeLoadingStateAction, changeLoadingStateAction,
@ -429,8 +425,6 @@ export const runQueries = (exploreId: ExploreId): ThunkResult<void> => {
queryResponse, queryResponse,
querySubscription, querySubscription,
history, history,
showingGraph,
showingTable,
} = exploreItemState; } = exploreItemState;
if (!hasNonEmptyQuery(queries)) { if (!hasNonEmptyQuery(queries)) {
@ -461,8 +455,6 @@ export const runQueries = (exploreId: ExploreId): ThunkResult<void> => {
// maxDataPoints: mode === ExploreMode.Logs && datasourceId === 'loki' ? undefined : containerWidth, // maxDataPoints: mode === ExploreMode.Logs && datasourceId === 'loki' ? undefined : containerWidth,
maxDataPoints: containerWidth, maxDataPoints: containerWidth,
liveStreaming: live, liveStreaming: live,
showingGraph,
showingTable,
}; };
const datasourceName = exploreItemState.requestedDatasourceName; const datasourceName = exploreItemState.requestedDatasourceName;
@ -577,9 +569,9 @@ export const stateSave = (): ThunkResult<void> => {
queries: left.queries.map(clearQueryKeys), queries: left.queries.map(clearQueryKeys),
range: toRawTimeRange(left.range), range: toRawTimeRange(left.range),
ui: { ui: {
showingGraph: left.showingGraph, showingGraph: true,
showingLogs: true, showingLogs: true,
showingTable: left.showingTable, showingTable: true,
dedupStrategy: left.dedupStrategy, dedupStrategy: left.dedupStrategy,
}, },
}; };
@ -590,9 +582,9 @@ export const stateSave = (): ThunkResult<void> => {
queries: right.queries.map(clearQueryKeys), queries: right.queries.map(clearQueryKeys),
range: toRawTimeRange(right.range), range: toRawTimeRange(right.range),
ui: { ui: {
showingGraph: right.showingGraph, showingGraph: true,
showingLogs: true, showingLogs: true,
showingTable: right.showingTable, showingTable: true,
dedupStrategy: right.dedupStrategy, dedupStrategy: right.dedupStrategy,
}, },
}; };
@ -753,46 +745,6 @@ export function syncTimes(exploreId: ExploreId): ThunkResult<void> {
}; };
} }
/**
* Creates action to collapse graph/logs/table panel. When panel is collapsed,
* queries won't be run
*/
const togglePanelActionCreator = (
actionCreator: ActionCreatorWithPayload<ToggleGraphPayload> | ActionCreatorWithPayload<ToggleTablePayload>
) => (exploreId: ExploreId, isPanelVisible: boolean): ThunkResult<void> => {
return dispatch => {
let uiFragmentStateUpdate: Partial<ExploreUIState>;
const shouldRunQueries = !isPanelVisible;
switch (actionCreator.type) {
case toggleGraphAction.type:
uiFragmentStateUpdate = { showingGraph: !isPanelVisible };
break;
case toggleTableAction.type:
uiFragmentStateUpdate = { showingTable: !isPanelVisible };
break;
}
dispatch(actionCreator({ exploreId }));
// The switch further up is exhaustive so uiFragmentStateUpdate should definitely be initialized
dispatch(updateExploreUIState(exploreId, uiFragmentStateUpdate!));
if (shouldRunQueries) {
dispatch(runQueries(exploreId));
}
};
};
/**
* Expand/collapse the graph result viewer. When collapsed, graph queries won't be run.
*/
export const toggleGraph = togglePanelActionCreator(toggleGraphAction);
/**
* Expand/collapse the table result viewer. When collapsed, table queries won't be run.
*/
export const toggleTable = togglePanelActionCreator(toggleTableAction);
/** /**
* Change logs deduplication strategy and update URL. * Change logs deduplication strategy and update URL.
*/ */

@ -6,7 +6,6 @@ import {
LoadingState, LoadingState,
LogsDedupStrategy, LogsDedupStrategy,
RawTimeRange, RawTimeRange,
toDataFrame,
UrlQueryMap, UrlQueryMap,
ExploreUrlState, ExploreUrlState,
} from '@grafana/data'; } from '@grafana/data';
@ -28,8 +27,6 @@ import {
scanStopAction, scanStopAction,
splitCloseAction, splitCloseAction,
splitOpenAction, splitOpenAction,
toggleGraphAction,
toggleTableAction,
updateDatasourceInstanceAction, updateDatasourceInstanceAction,
addQueryRowAction, addQueryRowAction,
removeQueryRowAction, removeQueryRowAction,
@ -160,41 +157,6 @@ describe('Explore item reducer', () => {
}); });
}); });
describe('toggling panels', () => {
describe('when toggleGraphAction is dispatched', () => {
it('then it should set correct state', () => {
reducerTester<ExploreItemState>()
.givenReducer(itemReducer, ({ graphResult: [] } as unknown) as ExploreItemState)
.whenActionIsDispatched(toggleGraphAction({ exploreId: ExploreId.left }))
.thenStateShouldEqual(({ showingGraph: true, graphResult: [] } as unknown) as ExploreItemState)
.whenActionIsDispatched(toggleGraphAction({ exploreId: ExploreId.left }))
.thenStateShouldEqual(({ showingGraph: false, graphResult: [] } as unknown) as ExploreItemState);
});
});
describe('when toggleTableAction is dispatched', () => {
it('then it should set correct state', () => {
const table = toDataFrame({
name: 'logs',
fields: [
{
name: 'time',
type: 'number',
values: [1, 2],
},
],
});
reducerTester<ExploreItemState>()
.givenReducer(itemReducer, ({ tableResult: table } as unknown) as ExploreItemState)
.whenActionIsDispatched(toggleTableAction({ exploreId: ExploreId.left }))
.thenStateShouldEqual(({ showingTable: true, tableResult: table } as unknown) as ExploreItemState)
.whenActionIsDispatched(toggleTableAction({ exploreId: ExploreId.left }))
.thenStateShouldEqual(({ showingTable: false, tableResult: table } as unknown) as ExploreItemState);
});
});
});
describe('changing range', () => { describe('changing range', () => {
describe('when changeRangeAction is dispatched', () => { describe('when changeRangeAction is dispatched', () => {
it('then it should set correct state', () => { it('then it should set correct state', () => {

@ -60,9 +60,7 @@ import {
SplitCloseActionPayload, SplitCloseActionPayload,
splitOpenAction, splitOpenAction,
syncTimesAction, syncTimesAction,
toggleGraphAction,
toggleLogLevelAction, toggleLogLevelAction,
toggleTableAction,
updateDatasourceInstanceAction, updateDatasourceInstanceAction,
updateUIStateAction, updateUIStateAction,
cancelQueriesAction, cancelQueriesAction,
@ -106,8 +104,6 @@ export const makeExploreItemState = (): ExploreItemState => ({
to: null, to: null,
} as any, } as any,
scanning: false, scanning: false,
showingGraph: true,
showingTable: true,
loading: false, loading: false,
queryKeys: [], queryKeys: [],
urlState: null, urlState: null,
@ -409,24 +405,6 @@ export const itemReducer = (state: ExploreItemState = makeExploreItemState(), ac
return { ...state, ...action.payload }; return { ...state, ...action.payload };
} }
if (toggleGraphAction.match(action)) {
const showingGraph = !state.showingGraph;
if (showingGraph) {
return { ...state, showingGraph };
}
return { ...state, showingGraph };
}
if (toggleTableAction.match(action)) {
const showingTable = !state.showingTable;
if (showingTable) {
return { ...state, showingTable };
}
return { ...state, showingTable };
}
if (queriesImportedAction.match(action)) { if (queriesImportedAction.match(action)) {
const { queries } = action.payload; const { queries } = action.payload;
return { return {

@ -1,7 +1,9 @@
import React, { memo, FC } from 'react'; import React, { memo, FC } from 'react';
import { css } from 'emotion';
// Types // Types
import { ExploreQueryFieldProps } from '@grafana/data'; import { ExploreQueryFieldProps } from '@grafana/data';
import { RadioButtonGroup } from '@grafana/ui';
import { PrometheusDatasource } from '../datasource'; import { PrometheusDatasource } from '../datasource';
import { PromQuery, PromOptions } from '../types'; import { PromQuery, PromOptions } from '../types';
@ -26,6 +28,19 @@ export const PromExploreQueryEditor: FC<Props> = (props: Props) => {
} }
} }
function onQueryTypeChange(value: string) {
const { query, onChange } = props;
let nextQuery;
if (value === 'instant') {
nextQuery = { ...query, instant: true, range: false };
} else if (value === 'range') {
nextQuery = { ...query, instant: false, range: true };
} else {
nextQuery = { ...query, instant: true, range: true };
}
onChange(nextQuery);
}
function onReturnKeyDown(e: React.KeyboardEvent<HTMLInputElement>) { function onReturnKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
if (e.key === 'Enter') { if (e.key === 'Enter') {
onRunQuery(); onRunQuery();
@ -33,27 +48,62 @@ export const PromExploreQueryEditor: FC<Props> = (props: Props) => {
} }
return ( return (
<PromQueryField <>
datasource={datasource} <PromQueryField
query={query} datasource={datasource}
onRunQuery={onRunQuery} query={query}
onChange={onChange} onRunQuery={onRunQuery}
onBlur={() => {}} onChange={onChange}
history={history} onBlur={() => {}}
data={data} history={history}
ExtraFieldElement={ data={data}
<PromExploreExtraField ExtraFieldElement={
label={'Step'} <PromExploreExtraField
onChangeFunc={onStepChange} label={'Step'}
onKeyDownFunc={onReturnKeyDown} onChangeFunc={onStepChange}
value={query.interval || ''} onKeyDownFunc={onReturnKeyDown}
hasTooltip={true} value={query.interval || ''}
tooltipContent={ hasTooltip={true}
'Time units can be used here, for example: 5s, 1m, 3h, 1d, 1y (Default if no unit is specified: s)' tooltipContent={
} 'Time units can be used here, for example: 5s, 1m, 3h, 1d, 1y (Default if no unit is specified: s)'
/> }
} />
/> }
/>
<PromExploreRadioButton
selected={query.range && query.instant ? 'both' : query.instant ? 'instant' : 'range'}
onQueryTypeChange={onQueryTypeChange}
/>
</>
);
};
type PromExploreRadioButtonProps = {
selected: string;
onQueryTypeChange: (value: string) => void;
};
const PromExploreRadioButton: React.FunctionComponent<PromExploreRadioButtonProps> = ({
selected,
onQueryTypeChange,
}) => {
const rangeOptions = [
{ value: 'range', label: 'Range' },
{ value: 'instant', label: 'Instant' },
{ value: 'both', label: 'Both' },
];
return (
<div
className={css`
display: flex;
`}
>
<button className={`gf-form-label gf-form-label--btn width-5`}>
<span className="btn-title">Query type</span>
</button>
<RadioButtonGroup options={rangeOptions} value={selected} onChange={onQueryTypeChange} />
</div>
); );
}; };

@ -326,7 +326,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
return ( return (
<> <>
<div className="gf-form-inline gf-form-inline--xs-view-flex-column flex-grow-1"> <div className="gf-form-inline gf-form-inline--xs-view-flex-column flex-grow-1">
<div className="gf-form flex-shrink-0"> <div className="gf-form flex-shrink-0 min-width-5">
<ButtonCascader options={metricsOptions} disabled={buttonDisabled} onChange={this.onChangeMetrics}> <ButtonCascader options={metricsOptions} disabled={buttonDisabled} onChange={this.onChangeMetrics}>
{chooserText} {chooserText}
</ButtonCascader> </ButtonCascader>

@ -1,62 +1,68 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PromExploreQueryEditor should render component 1`] = ` exports[`PromExploreQueryEditor should render component 1`] = `
<PromQueryField <Fragment>
ExtraFieldElement={ <PromQueryField
<PromExploreExtraField ExtraFieldElement={
hasTooltip={true} <PromExploreExtraField
label="Step" hasTooltip={true}
onChangeFunc={[Function]} label="Step"
onKeyDownFunc={[Function]} onChangeFunc={[Function]}
tooltipContent="Time units can be used here, for example: 5s, 1m, 3h, 1d, 1y (Default if no unit is specified: s)" onKeyDownFunc={[Function]}
value="1s" tooltipContent="Time units can be used here, for example: 5s, 1m, 3h, 1d, 1y (Default if no unit is specified: s)"
/> value="1s"
} />
data={ }
Object { data={
"request": Object { Object {
"app": "Grafana", "request": Object {
"dashboardId": 1, "app": "Grafana",
"interval": "1s", "dashboardId": 1,
"intervalMs": 1000, "interval": "1s",
"panelId": 1, "intervalMs": 1000,
"range": Object { "panelId": 1,
"from": "2020-01-01T00:00:00.000Z", "range": Object {
"raw": Object {
"from": "2020-01-01T00:00:00.000Z", "from": "2020-01-01T00:00:00.000Z",
"raw": Object {
"from": "2020-01-01T00:00:00.000Z",
"to": "2020-01-02T00:00:00.000Z",
},
"to": "2020-01-02T00:00:00.000Z", "to": "2020-01-02T00:00:00.000Z",
}, },
"to": "2020-01-02T00:00:00.000Z", "requestId": "1",
"scopedVars": Object {},
"startTime": 0,
"targets": Array [],
"timezone": "GMT",
}, },
"requestId": "1", "series": Array [],
"scopedVars": Object {}, "state": "NotStarted",
"startTime": 0, "timeRange": Object {
"targets": Array [],
"timezone": "GMT",
},
"series": Array [],
"state": "NotStarted",
"timeRange": Object {
"from": "2020-01-01T00:00:00.000Z",
"raw": Object {
"from": "2020-01-01T00:00:00.000Z", "from": "2020-01-01T00:00:00.000Z",
"raw": Object {
"from": "2020-01-01T00:00:00.000Z",
"to": "2020-01-02T00:00:00.000Z",
},
"to": "2020-01-02T00:00:00.000Z", "to": "2020-01-02T00:00:00.000Z",
}, },
"to": "2020-01-02T00:00:00.000Z", }
},
} }
} datasource={Object {}}
datasource={Object {}} history={Array []}
history={Array []} onBlur={[Function]}
onBlur={[Function]} onChange={[MockFunction]}
onChange={[MockFunction]} onRunQuery={[MockFunction]}
onRunQuery={[MockFunction]} query={
query={ Object {
Object { "expr": "",
"expr": "", "interval": "1s",
"interval": "1s", "refId": "A",
"refId": "A", }
} }
} />
/> <PromExploreRadioButton
onQueryTypeChange={[Function]}
selected="range"
/>
</Fragment>
`; `;

@ -1810,17 +1810,16 @@ describe('prepareTargets', () => {
}); });
describe('when run from Explore', () => { describe('when run from Explore', () => {
describe('and both Graph and Table are shown', () => { describe('when query type Both is selected', () => {
it('then it should return both instant and time series related objects', () => { it('then it should return both instant and time series related objects', () => {
const target: PromQuery = { const target: PromQuery = {
refId: 'A', refId: 'A',
expr: 'up', expr: 'up',
range: true,
instant: true,
}; };
const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext(target, CoreApp.Explore, { const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext(target, CoreApp.Explore);
showingGraph: true,
showingTable: true,
});
expect(queries.length).toBe(2); expect(queries.length).toBe(2);
expect(activeTargets.length).toBe(2); expect(activeTargets.length).toBe(2);
@ -1868,33 +1867,16 @@ describe('prepareTargets', () => {
}); });
}); });
describe('and both Graph and Table are hidden', () => { describe('when query type Instant is selected', () => {
it('then it should return empty arrays', () => { it('then it should just add targets', () => {
const target: PromQuery = {
refId: 'A',
expr: 'up',
showingGraph: false,
showingTable: false,
};
const { queries, activeTargets } = getPrepareTargetsContext(target, CoreApp.Explore);
expect(queries.length).toBe(0);
expect(activeTargets.length).toBe(0);
});
});
describe('and Graph is hidden', () => {
it('then it should return only intant related objects', () => {
const target: PromQuery = { const target: PromQuery = {
refId: 'A', refId: 'A',
expr: 'up', expr: 'up',
instant: true,
range: false,
}; };
const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext(target, CoreApp.Explore, { const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext(target, CoreApp.Explore);
showingGraph: false,
showingTable: true,
});
expect(queries.length).toBe(1); expect(queries.length).toBe(1);
expect(activeTargets.length).toBe(1); expect(activeTargets.length).toBe(1);
@ -1908,55 +1890,43 @@ describe('prepareTargets', () => {
hinting: undefined, hinting: undefined,
instant: true, instant: true,
refId: target.refId, refId: target.refId,
requestId: panelId + target.refId + '_instant', requestId: panelId + target.refId,
start, start,
step: 1, step: 1,
}); });
expect(activeTargets[0]).toEqual({ expect(activeTargets[0]).toEqual(target);
...target,
format: 'table',
instant: true,
requestId: panelId + target.refId + '_instant',
valueWithRefId: true,
});
}); });
}); });
});
describe('and Table is hidden', () => { describe('when query type Range is selected', () => {
it('then it should return only time series related objects', () => { it('then it should just add targets', () => {
const target: PromQuery = { const target: PromQuery = {
refId: 'A', refId: 'A',
expr: 'up', expr: 'up',
}; range: true,
instant: false,
};
const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext(target, CoreApp.Explore, { const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext(target, CoreApp.Explore);
showingGraph: true,
showingTable: false,
});
expect(queries.length).toBe(1); expect(queries.length).toBe(1);
expect(activeTargets.length).toBe(1); expect(activeTargets.length).toBe(1);
expect(queries[0]).toEqual({ expect(queries[0]).toEqual({
end, end,
expr: 'up', expr: 'up',
headers: { headers: {
'X-Dashboard-Id': undefined, 'X-Dashboard-Id': undefined,
'X-Panel-Id': panelId, 'X-Panel-Id': panelId,
}, },
hinting: undefined, hinting: undefined,
instant: false, instant: false,
refId: target.refId, refId: target.refId,
requestId: panelId + target.refId, requestId: panelId + target.refId,
start, start,
step: 1, step: 1,
});
expect(activeTargets[0]).toEqual({
...target,
format: 'time_series',
instant: false,
requestId: panelId + target.refId,
});
}); });
expect(activeTargets[0]).toEqual(target);
}); });
}); });
}); });

@ -176,7 +176,8 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
query: PromQueryRequest, query: PromQueryRequest,
target: PromQuery, target: PromQuery,
responseListLength: number, responseListLength: number,
scopedVars?: ScopedVars scopedVars?: ScopedVars,
mixedQueries?: boolean
) => { ) => {
// Keeping original start/end for transformers // Keeping original start/end for transformers
const transformerOptions = { const transformerOptions = {
@ -191,8 +192,10 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
refId: target.refId, refId: target.refId,
valueWithRefId: target.valueWithRefId, valueWithRefId: target.valueWithRefId,
meta: { meta: {
/** Fix for showing of Prometheus results in Explore table. We want to show result of instant query in table and the rest of time series in graph */ /** Fix for showing of Prometheus results in Explore table.
preferredVisualisationType: query.instant ? 'table' : 'graph', * We want to show result of instant query always in table and result of range query based on target.runAll;
*/
preferredVisualisationType: target.instant ? 'table' : mixedQueries ? 'graph' : undefined,
}, },
}; };
const series = this.resultTransformer.transform(response, transformerOptions); const series = this.resultTransformer.transform(response, transformerOptions);
@ -211,32 +214,32 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
target.requestId = options.panelId + target.refId; target.requestId = options.panelId + target.refId;
if (options.app !== CoreApp.Explore) { if (target.range && target.instant) {
activeTargets.push(target); // If running both (only available in Explore) - instant and range query, prepare both targets
queries.push(this.createQuery(target, options, start, end)); // Create instant target
continue;
}
if (options.showingTable) {
// create instant target only if Table is showed in Explore
const instantTarget: any = cloneDeep(target); const instantTarget: any = cloneDeep(target);
instantTarget.format = 'table'; instantTarget.format = 'table';
instantTarget.instant = true; instantTarget.instant = true;
instantTarget.range = false;
instantTarget.valueWithRefId = true; instantTarget.valueWithRefId = true;
delete instantTarget.maxDataPoints; delete instantTarget.maxDataPoints;
instantTarget.requestId += '_instant'; instantTarget.requestId += '_instant';
activeTargets.push(instantTarget); // Create range target
queries.push(this.createQuery(instantTarget, options, start, end)); const rangeTarget: any = cloneDeep(target);
} rangeTarget.format = 'time_series';
rangeTarget.instant = false;
if (options.showingGraph) { instantTarget.range = true;
// create time series target only if Graph is showed in Explore
target.format = 'time_series'; // Add both targets to activeTargets and queries arrays
target.instant = false; activeTargets.push(instantTarget, rangeTarget);
queries.push(
activeTargets.push(target); this.createQuery(instantTarget, options, start, end),
this.createQuery(rangeTarget, options, start, end)
);
} else {
queries.push(this.createQuery(target, options, start, end)); queries.push(this.createQuery(target, options, start, end));
activeTargets.push(target);
} }
} }
@ -268,6 +271,8 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
private exploreQuery(queries: PromQueryRequest[], activeTargets: PromQuery[], end: number) { private exploreQuery(queries: PromQueryRequest[], activeTargets: PromQuery[], end: number) {
let runningQueriesCount = queries.length; let runningQueriesCount = queries.length;
const mixedQueries = activeTargets.some(t => t.range) && activeTargets.some(t => t.instant);
const subQueries = queries.map((query, index) => { const subQueries = queries.map((query, index) => {
const target = activeTargets[index]; const target = activeTargets[index];
@ -281,7 +286,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
tap(() => runningQueriesCount--), tap(() => runningQueriesCount--),
filter((response: any) => (response.cancelled ? false : true)), filter((response: any) => (response.cancelled ? false : true)),
map((response: any) => { map((response: any) => {
const data = this.processResult(response, query, target, queries.length); const data = this.processResult(response, query, target, queries.length, undefined, mixedQueries);
return { return {
data, data,
key: query.requestId, key: query.requestId,

@ -4,6 +4,7 @@ export interface PromQuery extends DataQuery {
expr: string; expr: string;
format?: string; format?: string;
instant?: boolean; instant?: boolean;
range?: boolean;
hinting?: boolean; hinting?: boolean;
interval?: string; interval?: string;
intervalFactor?: number; intervalFactor?: number;

@ -118,14 +118,6 @@ export interface ExploreItemState {
* Current scanning range to be shown to the user while scanning is active. * Current scanning range to be shown to the user while scanning is active.
*/ */
scanRange?: RawTimeRange; scanRange?: RawTimeRange;
/**
* True if graph result viewer is expanded. Query runs will contain graph queries.
*/
showingGraph: boolean;
/**
* True if table result viewer is expanded. Query runs will contain table queries.
*/
showingTable: boolean;
loading: boolean; loading: boolean;
/** /**
@ -206,8 +198,6 @@ export interface QueryOptions {
minInterval?: string; minInterval?: string;
maxDataPoints?: number; maxDataPoints?: number;
liveStreaming?: boolean; liveStreaming?: boolean;
showingGraph?: boolean;
showingTable?: boolean;
mode?: ExploreMode; mode?: ExploreMode;
} }

Loading…
Cancel
Save