From e6001f57a249678469a60f076cc6530827f7c7dd Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 16 May 2019 09:52:22 +0200 Subject: [PATCH] Explore: Allow switching between metrics and logs (#16959) Adds basic support for switching between Metrics and Logs in Explore. Currently only test datasource that supports both Metrics and Logs. Summary of changes: * Moves mode (Metric, Logs) selection to the left of datasource picker and add some quick styling. * Only trigger change in ToggleButton if not selected * Set correct mode if datasource only supports logs Closes #16808 --- .../ToggleButtonGroup/ToggleButtonGroup.tsx | 2 +- public/app/features/explore/Explore.tsx | 25 +++--- .../app/features/explore/ExploreToolbar.tsx | 78 ++++++++++++++++++- public/app/features/explore/QueryEditor.tsx | 3 +- public/app/features/explore/QueryRow.tsx | 1 + .../app/features/explore/state/actionTypes.ts | 11 +++ public/app/features/explore/state/actions.ts | 22 ++++-- .../features/explore/state/reducers.test.ts | 15 ++++ public/app/features/explore/state/reducers.ts | 29 ++++++- .../plugins/datasource/testdata/plugin.json | 1 + public/app/types/explore.ts | 7 ++ public/sass/pages/_explore.scss | 19 +++++ 12 files changed, 187 insertions(+), 26 deletions(-) diff --git a/public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx b/public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx index ce1f80c8dca..07a39cfd109 100644 --- a/public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx +++ b/public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx @@ -39,7 +39,7 @@ export const ToggleButton: FC = ({ }) => { const onClick = (event: React.SyntheticEvent) => { event.stopPropagation(); - if (onChange) { + if (!selected && onChange) { onChange(value); } }; diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 895ae0eeec8..21e047399cd 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -39,6 +39,7 @@ import { ExploreId, ExploreUpdateState, ExploreUIState, + ExploreMode, } from 'app/types/explore'; import { StoreState } from 'app/types'; import { @@ -79,15 +80,13 @@ interface ExploreProps { setQueries: typeof setQueries; split: boolean; showingStartPage?: boolean; - supportsGraph: boolean | null; - supportsLogs: boolean | null; - supportsTable: boolean | null; queryKeys: string[]; initialDatasource: string; initialQueries: DataQuery[]; initialRange: RawTimeRange; initialUI: ExploreUIState; queryErrors: DataQueryError[]; + mode: ExploreMode; } /** @@ -234,11 +233,9 @@ export class Explore extends React.PureComponent { exploreId, showingStartPage, split, - supportsGraph, - supportsLogs, - supportsTable, queryKeys, queryErrors, + mode, } = this.props; const exploreClass = split ? 'explore explore-split' : 'explore'; @@ -273,9 +270,11 @@ export class Explore extends React.PureComponent { {showingStartPage && } {!showingStartPage && ( <> - {supportsGraph && !supportsLogs && } - {supportsTable && } - {supportsLogs && ( + {mode === ExploreMode.Metrics && } + {mode === ExploreMode.Metrics && ( + + )} + {mode === ExploreMode.Logs && ( >; + selectedModeOption: SelectOptionItem; } interface DispatchProps { @@ -70,6 +81,7 @@ interface DispatchProps { closeSplit: typeof splitClose; split: typeof splitOpen; changeRefreshInterval: typeof changeRefreshInterval; + changeMode: typeof changeMode; } type Props = StateProps & DispatchProps & OwnProps; @@ -100,6 +112,11 @@ export class UnConnectedExploreToolbar extends PureComponent { changeRefreshInterval(exploreId, item); }; + onModeChange = (mode: ExploreMode) => { + const { changeMode, exploreId } = this.props; + changeMode(exploreId, mode); + }; + render() { const { datasourceMissing, @@ -115,6 +132,8 @@ export class UnConnectedExploreToolbar extends PureComponent { refreshInterval, onChangeTime, split, + supportedModeOptions, + selectedModeOption, } = this.props; return ( @@ -147,8 +166,31 @@ export class UnConnectedExploreToolbar extends PureComponent { current={selectedDatasource} /> + {supportedModeOptions.length > 1 ? ( +
+ + + {'Metrics'} + + + {'Logs'} + + +
+ ) : null} ) : null} + {exploreId === 'left' && !splitted ? (
{createResponsiveButton({ @@ -208,12 +250,41 @@ const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps graphIsLoading, logIsLoading, tableIsLoading, + supportedModes, + mode, } = exploreItem; const selectedDatasource = datasourceInstance ? exploreDatasources.find(datasource => datasource.name === datasourceInstance.name) : undefined; const loading = graphIsLoading || logIsLoading || tableIsLoading; + const supportedModeOptions: Array> = []; + let selectedModeOption = null; + for (const supportedMode of supportedModes) { + switch (supportedMode) { + case ExploreMode.Metrics: + const option1 = { + value: ExploreMode.Metrics, + label: ExploreMode.Metrics, + }; + supportedModeOptions.push(option1); + if (mode === ExploreMode.Metrics) { + selectedModeOption = option1; + } + break; + case ExploreMode.Logs: + const option2 = { + value: ExploreMode.Logs, + label: ExploreMode.Logs, + }; + supportedModeOptions.push(option2); + if (mode === ExploreMode.Logs) { + selectedModeOption = option2; + } + break; + } + } + return { datasourceMissing, exploreDatasources, @@ -223,6 +294,8 @@ const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps selectedDatasource, splitted, refreshInterval, + supportedModeOptions, + selectedModeOption, }; }; @@ -233,6 +306,7 @@ const mapDispatchToProps: DispatchProps = { runQueries, closeSplit: splitClose, split: splitOpen, + changeMode: changeMode, }; export const ExploreToolbar = hot(module)( diff --git a/public/app/features/explore/QueryEditor.tsx b/public/app/features/explore/QueryEditor.tsx index 30f876886c1..54927b8cc91 100644 --- a/public/app/features/explore/QueryEditor.tsx +++ b/public/app/features/explore/QueryEditor.tsx @@ -35,7 +35,7 @@ export default class QueryEditor extends PureComponent { const loader = getAngularLoader(); const template = ' '; - const target = { datasource: datasource.name, ...initialQuery }; + const target = { ...initialQuery }; const scopeProps = { ctrl: { datasource, @@ -60,6 +60,7 @@ export default class QueryEditor extends PureComponent { this.component = loader.load(this.element, scopeProps, template); setTimeout(() => { this.props.onQueryChange(target); + this.props.onExecuteQuery(); }, 1); } diff --git a/public/app/features/explore/QueryRow.tsx b/public/app/features/explore/QueryRow.tsx index fc4a5e025e9..2a0429dbd97 100644 --- a/public/app/features/explore/QueryRow.tsx +++ b/public/app/features/explore/QueryRow.tsx @@ -38,6 +38,7 @@ interface QueryRowProps extends PropsFromParent { addQueryRow: typeof addQueryRow; changeQuery: typeof changeQuery; className?: string; + exploreId: ExploreId; datasourceInstance: ExploreDataSourceApi; datasourceStatus: DataSourceStatus; highlightLogsExpressionAction: typeof highlightLogsExpressionAction; diff --git a/public/app/features/explore/state/actionTypes.ts b/public/app/features/explore/state/actionTypes.ts index cce8d649c9d..225a672ae2e 100644 --- a/public/app/features/explore/state/actionTypes.ts +++ b/public/app/features/explore/state/actionTypes.ts @@ -18,6 +18,7 @@ import { ResultType, QueryTransaction, ExploreUIState, + ExploreMode, } from 'app/types/explore'; import { actionCreatorFactory, noPayloadActionCreatorFactory, ActionOf } from 'app/core/redux/actionCreatorFactory'; @@ -49,6 +50,11 @@ export interface AddQueryRowPayload { query: DataQuery; } +export interface ChangeModePayload { + exploreId: ExploreId; + mode: ExploreMode; +} + export interface ChangeQueryPayload { exploreId: ExploreId; query: DataQuery; @@ -245,6 +251,11 @@ export const addQueryRowAction = actionCreatorFactory('explo */ export const changeDatasourceAction = noPayloadActionCreatorFactory('explore/CHANGE_DATASOURCE').create(); +/** + * Change the mode of Explore. + */ +export const changeModeAction = actionCreatorFactory('explore/CHANGE_MODE').create(); + /** * Query change handler for the query row with the given index. * If `override` is reset the query modifications and run the queries. Use this to set queries via a link. diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index 12b1a1b8b69..838685d5a47 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -44,6 +44,7 @@ import { QueryOptions, ExploreUIState, QueryTransaction, + ExploreMode, } from 'app/types/explore'; import { updateDatasourceInstanceAction, @@ -85,6 +86,7 @@ import { queryStartAction, historyUpdatedAction, resetQueryErrorAction, + changeModeAction, } from './actionTypes'; import { ActionOf, ActionCreator } from 'app/core/redux/actionCreatorFactory'; import { LogsDedupStrategy } from 'app/core/logs_model'; @@ -140,6 +142,16 @@ export function changeDatasource(exploreId: ExploreId, datasource: string): Thun }; } +/** + * Change the display mode in Explore. + */ +export function changeMode(exploreId: ExploreId, mode: ExploreMode): ThunkResult { + return dispatch => { + dispatch(changeModeAction({ exploreId, mode })); + dispatch(runQueries(exploreId)); + }; +} + /** * Query change handler for the query row with the given index. * If `override` is reset the query modifications and run the queries. Use this to set queries via a link. @@ -509,11 +521,9 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false): ThunkRe showingLogs, showingGraph, showingTable, - supportsGraph, - supportsLogs, - supportsTable, datasourceError, containerWidth, + mode, } = getState().explore[exploreId]; if (datasourceError) { @@ -533,7 +543,7 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false): ThunkRe dispatch(runQueriesAction({ exploreId })); // Keep table queries first since they need to return quickly - if ((ignoreUIState || showingTable) && supportsTable) { + if ((ignoreUIState || showingTable) && mode === ExploreMode.Metrics) { dispatch( runQueriesForType(exploreId, 'Table', { interval, @@ -543,7 +553,7 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false): ThunkRe }) ); } - if ((ignoreUIState || showingGraph) && supportsGraph) { + if ((ignoreUIState || showingGraph) && mode === ExploreMode.Metrics) { dispatch( runQueriesForType(exploreId, 'Graph', { interval, @@ -553,7 +563,7 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false): ThunkRe }) ); } - if ((ignoreUIState || showingLogs) && supportsLogs) { + if ((ignoreUIState || showingLogs) && mode === ExploreMode.Logs) { dispatch(runQueriesForType(exploreId, 'Logs', { interval, format: 'logs' })); } diff --git a/public/app/features/explore/state/reducers.test.ts b/public/app/features/explore/state/reducers.test.ts index 428d208e17a..c5ee8dbb779 100644 --- a/public/app/features/explore/state/reducers.test.ts +++ b/public/app/features/explore/state/reducers.test.ts @@ -12,6 +12,7 @@ import { ExploreState, QueryTransaction, RangeScanner, + ExploreMode, } from 'app/types/explore'; import { reducerTester } from 'test/core/redux/reducerTester'; import { @@ -23,6 +24,7 @@ import { updateDatasourceInstanceAction, splitOpenAction, splitCloseAction, + changeModeAction, } from './actionTypes'; import { Reducer } from 'redux'; import { ActionOf } from 'app/core/redux/actionCreatorFactory'; @@ -122,6 +124,17 @@ describe('Explore item reducer', () => { .thenStateShouldEqual(expectedState); }); }); + + describe('when changeDataType is dispatched', () => { + it('then it should set correct state', () => { + reducerTester() + .givenReducer(itemReducer, {}) + .whenActionIsDispatched(changeModeAction({ exploreId: ExploreId.left, mode: ExploreMode.Logs })) + .thenStateShouldEqual({ + mode: ExploreMode.Logs, + }); + }); + }); }); describe('changing datasource', () => { @@ -160,6 +173,8 @@ describe('Explore item reducer', () => { showingStartPage: true, queries, queryKeys, + supportedModes: [ExploreMode.Metrics, ExploreMode.Logs], + mode: ExploreMode.Metrics, }; reducerTester() diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index 9807bcb8ad8..f0847360cbe 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -8,7 +8,7 @@ import { DEFAULT_UI_STATE, generateNewKeyAndAddRefIdIfMissing, } from 'app/core/utils/explore'; -import { ExploreItemState, ExploreState, ExploreId, ExploreUpdateState } from 'app/types/explore'; +import { ExploreItemState, ExploreState, ExploreId, ExploreUpdateState, ExploreMode } from 'app/types/explore'; import { DataQuery } from '@grafana/ui/src/types'; import { HigherOrderAction, @@ -22,6 +22,7 @@ import { runQueriesAction, historyUpdatedAction, resetQueryErrorAction, + changeModeAction, } from './actionTypes'; import { reducerFactory } from 'app/core/redux'; import { @@ -107,6 +108,8 @@ export const makeExploreItemState = (): ExploreItemState => ({ update: makeInitialUpdateState(), queryErrors: [], latency: 0, + supportedModes: [], + mode: null, }); /** @@ -165,6 +168,13 @@ export const itemReducer = reducerFactory({} as ExploreItemSta return { ...state, containerWidth }; }, }) + .addMapper({ + filter: changeModeAction, + mapper: (state, action): ExploreItemState => { + const mode = action.payload.mode; + return { ...state, mode }; + }, + }) .addMapper({ filter: changeTimeAction, mapper: (state, action): ExploreItemState => { @@ -226,6 +236,21 @@ export const itemReducer = reducerFactory({} as ExploreItemSta const supportsLogs = datasourceInstance.meta.logs; const supportsTable = datasourceInstance.meta.tables; + let 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; @@ -243,6 +268,8 @@ export const itemReducer = reducerFactory({} as ExploreItemSta StartPage, showingStartPage: Boolean(StartPage), queryKeys: getQueryKeys(state.queries, datasourceInstance), + supportedModes, + mode, }; }, }) diff --git a/public/app/plugins/datasource/testdata/plugin.json b/public/app/plugins/datasource/testdata/plugin.json index 96c23b19665..f34498957be 100644 --- a/public/app/plugins/datasource/testdata/plugin.json +++ b/public/app/plugins/datasource/testdata/plugin.json @@ -4,6 +4,7 @@ "id": "testdata", "metrics": true, + "logs": true, "alerting": true, "annotations": true, diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 404e53c2832..a828cbf9d3a 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -17,6 +17,11 @@ import { Emitter, TimeSeries } from 'app/core/core'; import { LogsModel, LogsDedupStrategy } from 'app/core/logs_model'; import TableModel from 'app/core/table_model'; +export enum ExploreMode { + Metrics = 'Metrics', + Logs = 'Logs', +} + export interface CompletionItem { /** * The label of this completion item. By default @@ -258,6 +263,8 @@ export interface ExploreItemState { queryErrors: DataQueryError[]; latency: number; + supportedModes: ExploreMode[]; + mode: ExploreMode; } export interface ExploreUpdateState { diff --git a/public/sass/pages/_explore.scss b/public/sass/pages/_explore.scss index dba0614e11b..d7799203832 100644 --- a/public/sass/pages/_explore.scss +++ b/public/sass/pages/_explore.scss @@ -92,6 +92,7 @@ .explore-toolbar-content-item:first-child { padding-left: $dashboard-padding; margin-right: auto; + display: flex; } @media only screen and (max-width: 1545px) { @@ -413,3 +414,21 @@ margin: $space-xs 0; cursor: pointer; } + +.query-type-toggle { + margin-left: 5px; + + .toggle-button-group { + padding-top: 2px; + } + + .btn.active { + background-color: $input-bg; + background-image: none; + background-clip: padding-box; + border: $input-border; + border-radius: $input-border-radius; + @include box-shadow($input-box-shadow); + color: $input-color; + } +}