From 229d646bfc63c4db0db0e5294581ee82ca8ddeda Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Thu, 7 Feb 2019 17:46:33 +0100 Subject: [PATCH 01/52] Persis deduplication strategy in url --- public/app/core/utils/explore.test.ts | 6 ++- public/app/core/utils/explore.ts | 5 +- public/app/features/explore/Logs.tsx | 27 +++++----- public/app/features/explore/LogsContainer.tsx | 30 +++++++++-- .../app/features/explore/state/actionTypes.ts | 11 ++++ public/app/features/explore/state/actions.ts | 54 +++++++++++++++---- public/app/features/explore/state/reducers.ts | 15 ++++-- public/app/types/explore.ts | 8 ++- 8 files changed, 121 insertions(+), 35 deletions(-) diff --git a/public/app/core/utils/explore.test.ts b/public/app/core/utils/explore.test.ts index 1c00142c3b8..dae6554ade0 100644 --- a/public/app/core/utils/explore.test.ts +++ b/public/app/core/utils/explore.test.ts @@ -8,6 +8,7 @@ import { } from './explore'; import { ExploreUrlState } from 'app/types/explore'; import store from 'app/core/store'; +import { LogsDedupStrategy } from 'app/core/logs_model'; const DEFAULT_EXPLORE_STATE: ExploreUrlState = { datasource: null, @@ -17,6 +18,7 @@ const DEFAULT_EXPLORE_STATE: ExploreUrlState = { showingGraph: true, showingTable: true, showingLogs: true, + dedupStrategy: LogsDedupStrategy.none, } }; @@ -78,7 +80,7 @@ describe('state functions', () => { expect(serializeStateToUrlParam(state)).toBe( '{"datasource":"foo","queries":[{"expr":"metric{test=\\"a/b\\"}"},' + '{"expr":"super{foo=\\"x/z\\"}"}],"range":{"from":"now-5h","to":"now"},' + - '"ui":{"showingGraph":true,"showingTable":true,"showingLogs":true}}' + '"ui":{"showingGraph":true,"showingTable":true,"showingLogs":true,"dedupStrategy":"none"}}' ); }); @@ -100,7 +102,7 @@ describe('state functions', () => { }, }; expect(serializeStateToUrlParam(state, true)).toBe( - '["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"},{"ui":[true,true,true]}]' + '["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"},{"ui":[true,true,true,"none"]}]' ); }); }); diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index 107f411353c..1dcd66c6369 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -21,6 +21,7 @@ import { QueryIntervals, QueryOptions, } from 'app/types/explore'; +import { LogsDedupStrategy } from 'app/core/logs_model'; export const DEFAULT_RANGE = { from: 'now-6h', @@ -31,6 +32,7 @@ export const DEFAULT_UI_STATE = { showingTable: true, showingGraph: true, showingLogs: true, + dedupStrategy: LogsDedupStrategy.none, }; const MAX_HISTORY_ITEMS = 100; @@ -183,6 +185,7 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState { showingGraph: segment.ui[0], showingLogs: segment.ui[1], showingTable: segment.ui[2], + dedupStrategy: segment.ui[3], }; } }); @@ -204,7 +207,7 @@ export function serializeStateToUrlParam(urlState: ExploreUrlState, compact?: bo urlState.range.to, urlState.datasource, ...urlState.queries, - { ui: [!!urlState.ui.showingGraph, !!urlState.ui.showingLogs, !!urlState.ui.showingTable] }, + { ui: [!!urlState.ui.showingGraph, !!urlState.ui.showingLogs, !!urlState.ui.showingTable, urlState.ui.dedupStrategy] }, ]); } return JSON.stringify(urlState); diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index b6c903bc504..af6caee6206 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -57,14 +57,15 @@ interface Props { range?: RawTimeRange; scanning?: boolean; scanRange?: RawTimeRange; + dedupStrategy: LogsDedupStrategy; onChangeTime?: (range: RawTimeRange) => void; onClickLabel?: (label: string, value: string) => void; onStartScanning?: () => void; onStopScanning?: () => void; + onDedupStrategyChange: (dedupStrategy: LogsDedupStrategy) => void; } interface State { - dedup: LogsDedupStrategy; deferLogs: boolean; hiddenLogLevels: Set; renderAll: boolean; @@ -78,7 +79,6 @@ export default class Logs extends PureComponent { renderAllTimer: NodeJS.Timer; state = { - dedup: LogsDedupStrategy.none, deferLogs: true, hiddenLogLevels: new Set(), renderAll: false, @@ -111,12 +111,11 @@ export default class Logs extends PureComponent { } onChangeDedup = (dedup: LogsDedupStrategy) => { - this.setState(prevState => { - if (prevState.dedup === dedup) { - return { dedup: LogsDedupStrategy.none }; - } - return { dedup }; - }); + const { onDedupStrategyChange } = this.props; + if (this.props.dedupStrategy === dedup) { + return onDedupStrategyChange(LogsDedupStrategy.none); + } + return onDedupStrategyChange(dedup); }; onChangeLabels = (event: React.SyntheticEvent) => { @@ -171,17 +170,19 @@ export default class Logs extends PureComponent { return null; } - const { dedup, deferLogs, hiddenLogLevels, renderAll, showLocalTime, showUtc } = this.state; + const { deferLogs, hiddenLogLevels, renderAll, showLocalTime, showUtc, } = this.state; let { showLabels } = this.state; + const { dedupStrategy } = this.props; const hasData = data && data.rows && data.rows.length > 0; - const showDuplicates = dedup !== LogsDedupStrategy.none; + const showDuplicates = dedupStrategy !== LogsDedupStrategy.none; // Filtering const filteredData = filterLogLevels(data, hiddenLogLevels); - const dedupedData = dedupLogRows(filteredData, dedup); + const dedupedData = dedupLogRows(filteredData, dedupStrategy); const dedupCount = dedupedData.rows.reduce((sum, row) => sum + row.duplicates, 0); const meta = [...data.meta]; - if (dedup !== LogsDedupStrategy.none) { + + if (dedupStrategy !== LogsDedupStrategy.none) { meta.push({ label: 'Dedup count', value: dedupCount, @@ -233,7 +234,7 @@ export default class Logs extends PureComponent { key={i} value={dedupType} onChange={this.onChangeDedup} - selected={dedup === dedupType} + selected={dedupStrategy === dedupType} tooltip={LogsDedupDescription[dedupType]} > {dedupType} diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index 76970ef343a..a3cab0b256a 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -4,10 +4,10 @@ import { connect } from 'react-redux'; import { RawTimeRange, TimeRange } from '@grafana/ui'; import { ExploreId, ExploreItemState } from 'app/types/explore'; -import { LogsModel } from 'app/core/logs_model'; +import { LogsModel, LogsDedupStrategy } from 'app/core/logs_model'; import { StoreState } from 'app/types'; -import { toggleLogs } from './state/actions'; +import { toggleLogs, changeDedupStrategy } from './state/actions'; import Logs from './Logs'; import Panel from './Panel'; @@ -25,6 +25,8 @@ interface LogsContainerProps { scanRange?: RawTimeRange; showingLogs: boolean; toggleLogs: typeof toggleLogs; + changeDedupStrategy: typeof changeDedupStrategy; + dedupStrategy: LogsDedupStrategy; } export class LogsContainer extends PureComponent { @@ -32,6 +34,10 @@ export class LogsContainer extends PureComponent { this.props.toggleLogs(this.props.exploreId); }; + handleDedupStrategyChange = (dedupStrategy: LogsDedupStrategy) => { + this.props.changeDedupStrategy(this.props.exploreId, dedupStrategy); + }; + render() { const { exploreId, @@ -45,12 +51,13 @@ export class LogsContainer extends PureComponent { range, showingLogs, scanning, - scanRange, + scanRange } = this.props; return ( { onClickLabel={onClickLabel} onStartScanning={onStartScanning} onStopScanning={onStopScanning} + onDedupStrategyChange={this.handleDedupStrategyChange} range={range} scanning={scanning} scanRange={scanRange} @@ -69,11 +77,23 @@ export class LogsContainer extends PureComponent { } } +const selectItemUIState = (itemState: ExploreItemState) => { + const { showingGraph, showingLogs, showingTable, showingStartPage, dedupStrategy } = itemState; + return { + showingGraph, + showingLogs, + showingTable, + showingStartPage, + dedupStrategy, + }; +}; function mapStateToProps(state: StoreState, { exploreId }) { const explore = state.explore; const item: ExploreItemState = explore[exploreId]; - const { logsHighlighterExpressions, logsResult, queryTransactions, scanning, scanRange, showingLogs, range } = item; + const { logsHighlighterExpressions, logsResult, queryTransactions, scanning, scanRange, range } = item; const loading = queryTransactions.some(qt => qt.resultType === 'Logs' && !qt.done); + const {showingLogs, dedupStrategy} = selectItemUIState(item); + // const dedup = item.dedup; return { loading, logsHighlighterExpressions, @@ -82,11 +102,13 @@ function mapStateToProps(state: StoreState, { exploreId }) { scanRange, showingLogs, range, + dedupStrategy, }; } const mapDispatchToProps = { toggleLogs, + changeDedupStrategy, }; export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(LogsContainer)); diff --git a/public/app/features/explore/state/actionTypes.ts b/public/app/features/explore/state/actionTypes.ts index 98af5e8076e..71061607d3c 100644 --- a/public/app/features/explore/state/actionTypes.ts +++ b/public/app/features/explore/state/actionTypes.ts @@ -180,6 +180,8 @@ export interface SplitOpenPayload { itemState: ExploreItemState; } +// + export interface ToggleTablePayload { exploreId: ExploreId; } @@ -192,6 +194,10 @@ export interface ToggleLogsPayload { exploreId: ExploreId; } +export interface UpdateUIStatePayload extends Partial{ + exploreId: ExploreId; +} + export interface UpdateDatasourceInstancePayload { exploreId: ExploreId; datasourceInstance: DataSourceApi; @@ -366,6 +372,11 @@ export const splitCloseAction = noPayloadActionCreatorFactory('explore/SPLIT_CLO export const splitOpenAction = actionCreatorFactory('explore/SPLIT_OPEN').create(); export const stateSaveAction = noPayloadActionCreatorFactory('explore/STATE_SAVE').create(); +/** + * Update state of Explores UI + */ +export const updateUIStateAction = actionCreatorFactory('explore/UPDATE_UI_STATE').create(); + /** * Expand/collapse the table result viewer. When collapsed, table queries won't be run. */ diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index f6fa5c05d63..63f0bfd0350 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -67,14 +67,26 @@ import { ToggleGraphPayload, ToggleLogsPayload, ToggleTablePayload, + updateUIStateAction, } from './actionTypes'; import { ActionOf, ActionCreator } from 'app/core/redux/actionCreatorFactory'; +import { LogsDedupStrategy } from 'app/core/logs_model'; type ThunkResult = ThunkAction; -// /** -// * Adds a query row after the row with the given index. -// */ +/** + * Updates UI state and save it to the URL + */ +const updateExploreUIState = (exploreId, uiStateFragment: Partial) => { + return dispatch => { + dispatch(updateUIStateAction({ exploreId, ...uiStateFragment })); + dispatch(stateSave()); + }; +}; + +/** + * Adds a query row after the row with the given index. + */ export function addQueryRow(exploreId: ExploreId, index: number): ActionOf { const query = generateEmptyQuery(index + 1); return addQueryRowAction({ exploreId, index, query }); @@ -669,6 +681,7 @@ export function stateSave() { showingGraph: left.showingGraph, showingLogs: left.showingLogs, showingTable: left.showingTable, + dedupStrategy: left.dedupStrategy, }, }; urlStates.left = serializeStateToUrlParam(leftUrlState, true); @@ -677,7 +690,12 @@ export function stateSave() { datasource: right.datasourceInstance.name, queries: right.queries.map(clearQueryKeys), range: right.range, - ui: { showingGraph: right.showingGraph, showingLogs: right.showingLogs, showingTable: right.showingTable }, + ui: { + showingGraph: right.showingGraph, + showingLogs: right.showingLogs, + showingTable: right.showingTable, + dedupStrategy: right.dedupStrategy, + }, }; urlStates.right = serializeStateToUrlParam(rightUrlState, true); @@ -698,22 +716,29 @@ const togglePanelActionCreator = ( | ActionCreator ) => (exploreId: ExploreId) => { return (dispatch, getState) => { - let shouldRunQueries; - dispatch(actionCreator({ exploreId })); - dispatch(stateSave()); + let shouldRunQueries, uiFragmentStateUpdate: Partial; switch (actionCreator.type) { case toggleGraphAction.type: - shouldRunQueries = getState().explore[exploreId].showingGraph; + const isShowingGraph = getState().explore[exploreId].showingGraph; + shouldRunQueries = !isShowingGraph; + uiFragmentStateUpdate = { showingGraph: !isShowingGraph }; break; case toggleLogsAction.type: - shouldRunQueries = getState().explore[exploreId].showingLogs; + const isShowingLogs = getState().explore[exploreId].showingLogs; + shouldRunQueries = !isShowingLogs; + uiFragmentStateUpdate = { showingLogs: !isShowingLogs }; break; case toggleTableAction.type: - shouldRunQueries = getState().explore[exploreId].showingTable; + const isShowingTable = getState().explore[exploreId].showingTable; + shouldRunQueries = !isShowingTable; + uiFragmentStateUpdate = { showingTable: !isShowingTable }; break; } + dispatch(actionCreator({ exploreId })); + dispatch(updateExploreUIState(exploreId, uiFragmentStateUpdate)); + if (shouldRunQueries) { dispatch(runQueries(exploreId)); } @@ -734,3 +759,12 @@ export const toggleLogs = togglePanelActionCreator(toggleLogsAction); * 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. + */ +export const changeDedupStrategy = (exploreId, dedupStrategy: LogsDedupStrategy) => { + return dispatch => { + dispatch(updateExploreUIState(exploreId, { dedupStrategy })); + }; +}; diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index 76fc7d5de32..255591ee6e3 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -37,6 +37,7 @@ import { toggleLogsAction, toggleTableAction, queriesImportedAction, + updateUIStateAction, } from './actionTypes'; export const DEFAULT_RANGE = { @@ -406,6 +407,12 @@ export const itemReducer = reducerFactory({} as ExploreItemSta }; }, }) + .addMapper({ + filter: updateUIStateAction, + mapper: (state, action): ExploreItemState => { + return { ...state, ...action.payload }; + }, + }) .addMapper({ filter: toggleGraphAction, mapper: (state): ExploreItemState => { @@ -415,7 +422,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta // Discard transactions related to Graph query nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Graph'); } - return { ...state, queryTransactions: nextQueryTransactions, showingGraph }; + return { ...state, queryTransactions: nextQueryTransactions }; }, }) .addMapper({ @@ -427,7 +434,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta // Discard transactions related to Logs query nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Logs'); } - return { ...state, queryTransactions: nextQueryTransactions, showingLogs }; + return { ...state, queryTransactions: nextQueryTransactions }; }, }) .addMapper({ @@ -435,7 +442,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta mapper: (state): ExploreItemState => { const showingTable = !state.showingTable; if (showingTable) { - return { ...state, showingTable, queryTransactions: state.queryTransactions }; + return { ...state, queryTransactions: state.queryTransactions }; } // Toggle off needs discarding of table queries and results @@ -446,7 +453,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta state.queryIntervals.intervalMs ); - return { ...state, ...results, queryTransactions: nextQueryTransactions, showingTable }; + return { ...state, ...results, queryTransactions: nextQueryTransactions }; }, }) .addMapper({ diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 9c8d977c3ad..066ca226157 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -11,7 +11,7 @@ import { } from '@grafana/ui'; import { Emitter } from 'app/core/core'; -import { LogsModel } from 'app/core/logs_model'; +import { LogsModel, LogsDedupStrategy } from 'app/core/logs_model'; import TableModel from 'app/core/table_model'; export interface CompletionItem { @@ -237,12 +237,18 @@ export interface ExploreItemState { * React keys for rendering of QueryRows */ queryKeys: string[]; + + /** + * Current logs deduplication strategy + */ + dedupStrategy?: LogsDedupStrategy; } export interface ExploreUIState { showingTable: boolean; showingGraph: boolean; showingLogs: boolean; + dedupStrategy?: LogsDedupStrategy; } export interface ExploreUrlState { From 8d9c347cb1b2cec4b3a84104c72db8be1b5cac91 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Thu, 7 Feb 2019 21:22:16 +0100 Subject: [PATCH 02/52] fix: Add missing typing --- public/app/plugins/panel/graph2/GraphPanel.tsx | 4 ++-- public/app/plugins/panel/text2/module.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/panel/graph2/GraphPanel.tsx b/public/app/plugins/panel/graph2/GraphPanel.tsx index 01e5b2d819d..f1fc2b43d51 100644 --- a/public/app/plugins/panel/graph2/GraphPanel.tsx +++ b/public/app/plugins/panel/graph2/GraphPanel.tsx @@ -9,7 +9,7 @@ import { processTimeSeries } from '@grafana/ui/src/utils'; import { Graph } from '@grafana/ui'; // Types -import { PanelProps, NullValueMode } from '@grafana/ui/src/types'; +import { PanelProps, NullValueMode, TimeSeriesVMs } from '@grafana/ui/src/types'; import { Options } from './types'; interface Props extends PanelProps {} @@ -19,7 +19,7 @@ export class GraphPanel extends PureComponent { const { panelData, timeRange, width, height } = this.props; const { showLines, showBars, showPoints } = this.props.options; - let vmSeries; + let vmSeries: TimeSeriesVMs; if (panelData.timeSeries) { vmSeries = processTimeSeries({ timeSeries: panelData.timeSeries, diff --git a/public/app/plugins/panel/text2/module.tsx b/public/app/plugins/panel/text2/module.tsx index 68523ff0880..cc3ec016273 100644 --- a/public/app/plugins/panel/text2/module.tsx +++ b/public/app/plugins/panel/text2/module.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import { PanelProps } from '@grafana/ui'; export class Text2 extends PureComponent { - constructor(props) { + constructor(props: PanelProps) { super(props); } From a8a9bca07b04f7a92975edecdd8b1d3645b28ef1 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Thu, 7 Feb 2019 21:26:23 +0100 Subject: [PATCH 03/52] feat: Introduce IsDataPanel attribute to plugin.json --- pkg/api/frontendsettings.go | 1 + pkg/plugins/models.go | 1 + public/app/plugins/panel/gauge/plugin.json | 1 + public/app/plugins/panel/graph2/plugin.json | 2 +- public/app/plugins/panel/text2/plugin.json | 2 +- public/app/types/plugins.ts | 1 + 6 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index ed7054050e4..238a3965641 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -145,6 +145,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf "info": panel.Info, "hideFromList": panel.HideFromList, "sort": getPanelSort(panel.Id), + "isDataPanel": panel.IsDataPanel, } } diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go index 5ac436205c1..e37b1fcf7d9 100644 --- a/pkg/plugins/models.go +++ b/pkg/plugins/models.go @@ -47,6 +47,7 @@ type PluginBase struct { BaseUrl string `json:"baseUrl"` HideFromList bool `json:"hideFromList,omitempty"` State PluginState `json:"state,omitempty"` + IsDataPanel bool `json:"isDataPanel"` IncludedInAppId string `json:"-"` PluginDir string `json:"-"` diff --git a/public/app/plugins/panel/gauge/plugin.json b/public/app/plugins/panel/gauge/plugin.json index 58437779d25..733d2281cf4 100644 --- a/public/app/plugins/panel/gauge/plugin.json +++ b/public/app/plugins/panel/gauge/plugin.json @@ -2,6 +2,7 @@ "type": "panel", "name": "Gauge", "id": "gauge", + "isDataPanel": true, "info": { "author": { diff --git a/public/app/plugins/panel/graph2/plugin.json b/public/app/plugins/panel/graph2/plugin.json index 9cb6a1f78a4..9b2a915a597 100644 --- a/public/app/plugins/panel/graph2/plugin.json +++ b/public/app/plugins/panel/graph2/plugin.json @@ -2,7 +2,7 @@ "type": "panel", "name": "React Graph", "id": "graph2", - + "isDataPanel": true, "state": "alpha", "info": { diff --git a/public/app/plugins/panel/text2/plugin.json b/public/app/plugins/panel/text2/plugin.json index 53885dbd0f4..cd4ff424d89 100644 --- a/public/app/plugins/panel/text2/plugin.json +++ b/public/app/plugins/panel/text2/plugin.json @@ -2,8 +2,8 @@ "type": "panel", "name": "Text v2", "id": "text2", - "state": "alpha", + "isDataPanel": false, "info": { "author": { diff --git a/public/app/types/plugins.ts b/public/app/types/plugins.ts index 51c3b7b0476..0c5c53eb6f0 100644 --- a/public/app/types/plugins.ts +++ b/public/app/types/plugins.ts @@ -9,6 +9,7 @@ export interface PanelPlugin { info: any; sort: number; exports?: PluginExports; + isDataPanel?: boolean; } export interface Plugin { From 8d4caa593e924304f3dccdb06b541bf0bed69972 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Thu, 7 Feb 2019 21:31:05 +0100 Subject: [PATCH 04/52] feat: Add util to convert snapshotData to PanelData --- public/app/features/dashboard/utils/panel.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard/utils/panel.ts b/public/app/features/dashboard/utils/panel.ts index c0d753477a7..c60a153d889 100644 --- a/public/app/features/dashboard/utils/panel.ts +++ b/public/app/features/dashboard/utils/panel.ts @@ -4,7 +4,8 @@ import store from 'app/core/store'; // Models import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { PanelModel } from 'app/features/dashboard/state/PanelModel'; -import { TimeRange } from '@grafana/ui'; +import { PanelData, TimeRange, TimeSeries } from '@grafana/ui'; +import { TableData } from '@grafana/ui/src'; // Utils import { isString as _isString } from 'lodash'; @@ -168,3 +169,19 @@ export function getResolution(panel: PanelModel): number { return panel.maxDataPoints ? panel.maxDataPoints : Math.ceil(width * (panel.gridPos.w / 24)); } + +const isTimeSeries = (data: any): data is TimeSeries => data && data.hasOwnProperty('datapoints'); +const isTableData = (data: any): data is TableData => data && data.hasOwnProperty('columns'); +export const snapshotDataToPanelData = (panel: PanelModel): PanelData => { + const snapshotData = panel.snapshotData; + if (isTimeSeries(snapshotData[0])) { + return { + timeSeries: snapshotData + } as PanelData; + } else if (isTableData(snapshotData[0])) { + return { + tableData: snapshotData[0] + } as PanelData; + } + throw new Error('snapshotData is invalid:' + snapshotData.toString()); +}; From ec02ddd27b1c595fad0d721bde261022057602ec Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Thu, 7 Feb 2019 21:34:50 +0100 Subject: [PATCH 05/52] feat: Only use the DataPanel component when panel plugin has isDataPanel set to true in plugin.json. And fix PanelData when using snapshots --- .../dashboard/dashgrid/PanelChrome.tsx | 64 ++++++++++++------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index b02d9479dcc..1f9a2a32a5d 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -10,14 +10,14 @@ import { PanelHeader } from './PanelHeader/PanelHeader'; import { DataPanel } from './DataPanel'; // Utils -import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel'; +import { applyPanelTimeOverrides, snapshotDataToPanelData } from 'app/features/dashboard/utils/panel'; import { PANEL_HEADER_HEIGHT } from 'app/core/constants'; import { profiler } from 'app/core/profiler'; // Types import { DashboardModel, PanelModel } from '../state'; import { PanelPlugin } from 'app/types'; -import { TimeRange, LoadingState } from '@grafana/ui'; +import { TimeRange, LoadingState, PanelData } from '@grafana/ui'; import variables from 'sass/_variables.scss'; import templateSrv from 'app/features/templating/template_srv'; @@ -94,7 +94,7 @@ export class PanelChrome extends PureComponent { return !this.props.dashboard.otherPanelInFullscreen(this.props.panel); } - renderPanel(loading, panelData, width, height): JSX.Element { + renderPanelPlugin(loading: LoadingState, panelData: PanelData, width: number, height: number): JSX.Element { const { panel, plugin } = this.props; const { timeRange, renderCounter } = this.state; const PanelComponent = plugin.exports.Panel; @@ -121,11 +121,45 @@ export class PanelChrome extends PureComponent { ); } + renderHelper = (width: number, height: number): JSX.Element => { + const { panel, plugin } = this.props; + const { refreshCounter, timeRange } = this.state; + const { datasource, targets } = panel; + return ( + <> + {panel.snapshotData && panel.snapshotData.length > 0 ? ( + this.renderPanelPlugin(LoadingState.Done, snapshotDataToPanelData(panel), width, height) + ) : ( + <> + {plugin.isDataPanel === true ? + + {({ loading, panelData }) => { + return this.renderPanelPlugin(loading, panelData, width, height); + }} + + : ( + this.renderPanelPlugin(LoadingState.Done, null, width, height) + )} + + )} + + ); + } + + render() { - const { panel, dashboard } = this.props; - const { refreshCounter, timeRange, timeInfo } = this.state; + const { dashboard, panel } = this.props; + const { timeInfo } = this.state; + const { transparent } = panel; - const { datasource, targets, transparent } = panel; const containerClassNames = `panel-container panel-container--absolute ${transparent ? 'panel-transparent' : ''}`; return ( @@ -145,23 +179,7 @@ export class PanelChrome extends PureComponent { scopedVars={panel.scopedVars} links={panel.links} /> - {panel.snapshotData ? ( - this.renderPanel(false, panel.snapshotData, width, height) - ) : ( - - {({ loading, panelData }) => { - return this.renderPanel(loading, panelData, width, height); - }} - - )} + {this.renderHelper(width, height)} ); }} From 512aa62efc629a1488d4f5ced02de7e5f8b519fe Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Fri, 8 Feb 2019 11:50:05 +0100 Subject: [PATCH 06/52] Update config mock in metrics panel controller test --- public/app/features/panel/specs/metrics_panel_ctrl.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/app/features/panel/specs/metrics_panel_ctrl.test.ts b/public/app/features/panel/specs/metrics_panel_ctrl.test.ts index d647af616a9..3ee4c5165cb 100644 --- a/public/app/features/panel/specs/metrics_panel_ctrl.test.ts +++ b/public/app/features/panel/specs/metrics_panel_ctrl.test.ts @@ -1,6 +1,9 @@ jest.mock('app/core/core', () => ({})); jest.mock('app/core/config', () => { return { + bootData: { + user: {}, + }, panels: { test: { id: 'test', From c9fbd43231fc715ee2b37ad6e8a6aaa16cb1ec68 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 11 Feb 2019 11:59:48 +0100 Subject: [PATCH 07/52] Review changes --- public/app/features/explore/LogsContainer.tsx | 2 +- public/app/features/explore/state/actionTypes.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index a3cab0b256a..f64fec5e70e 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -93,7 +93,7 @@ function mapStateToProps(state: StoreState, { exploreId }) { const { logsHighlighterExpressions, logsResult, queryTransactions, scanning, scanRange, range } = item; const loading = queryTransactions.some(qt => qt.resultType === 'Logs' && !qt.done); const {showingLogs, dedupStrategy} = selectItemUIState(item); - // const dedup = item.dedup; + return { loading, logsHighlighterExpressions, diff --git a/public/app/features/explore/state/actionTypes.ts b/public/app/features/explore/state/actionTypes.ts index 71061607d3c..d54a8754c3d 100644 --- a/public/app/features/explore/state/actionTypes.ts +++ b/public/app/features/explore/state/actionTypes.ts @@ -180,8 +180,6 @@ export interface SplitOpenPayload { itemState: ExploreItemState; } -// - export interface ToggleTablePayload { exploreId: ExploreId; } @@ -373,7 +371,7 @@ export const splitOpenAction = actionCreatorFactory('explore/S export const stateSaveAction = noPayloadActionCreatorFactory('explore/STATE_SAVE').create(); /** - * Update state of Explores UI + * Update state of Explores UI elements (panels visiblity and deduplication strategy) */ export const updateUIStateAction = actionCreatorFactory('explore/UPDATE_UI_STATE').create(); From cee2e4788bc7963373262fb634faa14f8e49a7b9 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 11 Feb 2019 12:56:37 +0100 Subject: [PATCH 08/52] Do not read store state from toggle panelaction creator --- .../app/features/explore/GraphContainer.tsx | 2 +- public/app/features/explore/LogsContainer.tsx | 2 +- .../app/features/explore/TableContainer.tsx | 2 +- public/app/features/explore/state/actions.ts | 28 +++++++++++-------- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/public/app/features/explore/GraphContainer.tsx b/public/app/features/explore/GraphContainer.tsx index 3950d89c11f..92aac41367c 100644 --- a/public/app/features/explore/GraphContainer.tsx +++ b/public/app/features/explore/GraphContainer.tsx @@ -25,7 +25,7 @@ interface GraphContainerProps { export class GraphContainer extends PureComponent { onClickGraphButton = () => { - this.props.toggleGraph(this.props.exploreId); + this.props.toggleGraph(this.props.exploreId, this.props.showingGraph); }; onChangeTime = (timeRange: TimeRange) => { diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index 183af5b28fa..190c1c43b5a 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -32,7 +32,7 @@ interface LogsContainerProps { export class LogsContainer extends PureComponent { onClickLogsButton = () => { - this.props.toggleLogs(this.props.exploreId); + this.props.toggleLogs(this.props.exploreId, this.props.showingLogs); }; handleDedupStrategyChange = (dedupStrategy: LogsDedupStrategy) => { diff --git a/public/app/features/explore/TableContainer.tsx b/public/app/features/explore/TableContainer.tsx index f386e5ab99b..e41d4a1eecb 100644 --- a/public/app/features/explore/TableContainer.tsx +++ b/public/app/features/explore/TableContainer.tsx @@ -21,7 +21,7 @@ interface TableContainerProps { export class TableContainer extends PureComponent { onClickTableButton = () => { - this.props.toggleTable(this.props.exploreId); + this.props.toggleTable(this.props.exploreId, this.props.showingTable); }; render() { diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index 63f0bfd0350..8c5ed661851 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -714,25 +714,20 @@ const togglePanelActionCreator = ( | ActionCreator | ActionCreator | ActionCreator -) => (exploreId: ExploreId) => { - return (dispatch, getState) => { - let shouldRunQueries, uiFragmentStateUpdate: Partial; +) => (exploreId: ExploreId, isPanelVisible: boolean) => { + return (dispatch) => { + let uiFragmentStateUpdate: Partial; + const shouldRunQueries = !isPanelVisible; switch (actionCreator.type) { case toggleGraphAction.type: - const isShowingGraph = getState().explore[exploreId].showingGraph; - shouldRunQueries = !isShowingGraph; - uiFragmentStateUpdate = { showingGraph: !isShowingGraph }; + uiFragmentStateUpdate = { showingGraph: !isPanelVisible }; break; case toggleLogsAction.type: - const isShowingLogs = getState().explore[exploreId].showingLogs; - shouldRunQueries = !isShowingLogs; - uiFragmentStateUpdate = { showingLogs: !isShowingLogs }; + uiFragmentStateUpdate = { showingLogs: !isPanelVisible }; break; case toggleTableAction.type: - const isShowingTable = getState().explore[exploreId].showingTable; - shouldRunQueries = !isShowingTable; - uiFragmentStateUpdate = { showingTable: !isShowingTable }; + uiFragmentStateUpdate = { showingTable: !isPanelVisible }; break; } @@ -768,3 +763,12 @@ export const changeDedupStrategy = (exploreId, dedupStrategy: LogsDedupStrategy) dispatch(updateExploreUIState(exploreId, { dedupStrategy })); }; }; + +/** + * Change logs deduplication strategy and update URL. + */ +export const hiddenLogLe = (exploreId, dedupStrategy: LogsDedupStrategy) => { + return dispatch => { + dispatch(updateExploreUIState(exploreId, { dedupStrategy })); + }; +}; From 85780eb30cd1be80c602d98ea615baab1c91f438 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 11 Feb 2019 13:18:16 +0100 Subject: [PATCH 09/52] Remove not related code --- public/app/features/explore/state/actions.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index 8c5ed661851..b84a0534836 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -715,7 +715,7 @@ const togglePanelActionCreator = ( | ActionCreator | ActionCreator ) => (exploreId: ExploreId, isPanelVisible: boolean) => { - return (dispatch) => { + return dispatch => { let uiFragmentStateUpdate: Partial; const shouldRunQueries = !isPanelVisible; @@ -763,12 +763,3 @@ export const changeDedupStrategy = (exploreId, dedupStrategy: LogsDedupStrategy) dispatch(updateExploreUIState(exploreId, { dedupStrategy })); }; }; - -/** - * Change logs deduplication strategy and update URL. - */ -export const hiddenLogLe = (exploreId, dedupStrategy: LogsDedupStrategy) => { - return dispatch => { - dispatch(updateExploreUIState(exploreId, { dedupStrategy })); - }; -}; From 0019e0ffc9fc4d9e7768165b2022d18624528d2f Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Mon, 11 Feb 2019 16:20:32 +0100 Subject: [PATCH 10/52] chore: Only show Queries tab for panel plugins with isDataPanel set to true --- .../dashboard/panel_editor/PanelEditor.tsx | 53 +++++++++++++------ 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/public/app/features/dashboard/panel_editor/PanelEditor.tsx b/public/app/features/dashboard/panel_editor/PanelEditor.tsx index bfdc13bc8f2..37240389373 100644 --- a/public/app/features/dashboard/panel_editor/PanelEditor.tsx +++ b/public/app/features/dashboard/panel_editor/PanelEditor.tsx @@ -30,6 +30,32 @@ interface PanelEditorTab { text: string; } +enum PanelEditorTabIds { + Queries = 'queries', + Visualization = 'visualization', + Advanced = 'advanced', + Alert = 'alert' +} + +interface PanelEditorTab { + id: string; + text: string; +} + +const panelEditorTabTexts = { + [PanelEditorTabIds.Queries]: 'Queries', + [PanelEditorTabIds.Visualization]: 'Visualization', + [PanelEditorTabIds.Advanced]: 'Panel Options', + [PanelEditorTabIds.Alert]: 'Alert', +}; + +const getPanelEditorTab = (tabId: PanelEditorTabIds): PanelEditorTab => { + return { + id: tabId, + text: panelEditorTabTexts[tabId] + }; +}; + export class PanelEditor extends PureComponent { constructor(props) { super(props); @@ -72,31 +98,26 @@ export class PanelEditor extends PureComponent { render() { const { plugin } = this.props; - let activeTab = store.getState().location.query.tab || 'queries'; + let activeTab: PanelEditorTabIds = store.getState().location.query.tab || PanelEditorTabIds.Queries; const tabs: PanelEditorTab[] = [ - { id: 'queries', text: 'Queries' }, - { id: 'visualization', text: 'Visualization' }, - { id: 'advanced', text: 'Panel Options' }, + getPanelEditorTab(PanelEditorTabIds.Queries), + getPanelEditorTab(PanelEditorTabIds.Visualization), + getPanelEditorTab(PanelEditorTabIds.Advanced), ]; // handle panels that do not have queries tab - if (plugin.exports.PanelCtrl) { - if (!plugin.exports.PanelCtrl.prototype.onDataReceived) { - // remove queries tab - tabs.shift(); - // switch tab - if (activeTab === 'queries') { - activeTab = 'visualization'; - } + if (!plugin.isDataPanel) { + // remove queries tab + tabs.shift(); + // switch tab + if (activeTab === PanelEditorTabIds.Queries) { + activeTab = PanelEditorTabIds.Visualization; } } if (config.alertingEnabled && plugin.id === 'graph') { - tabs.push({ - id: 'alert', - text: 'Alert', - }); + tabs.push(getPanelEditorTab(PanelEditorTabIds.Alert)); } return ( From 1693f083cc8e35efaa9daf9bc8398c9d2c70b525 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 11 Feb 2019 16:57:49 +0100 Subject: [PATCH 11/52] Move deduplication calculation from Logs component to redux selector --- package.json | 2 + public/app/core/utils/reselect.ts | 5 +++ public/app/features/explore/Logs.tsx | 16 +++---- public/app/features/explore/LogsContainer.tsx | 42 ++++++++++++++++++- .../app/features/explore/state/actionTypes.ts | 13 +++++- public/app/features/explore/state/reducers.ts | 11 +++++ public/app/types/explore.ts | 7 +++- yarn.lock | 12 ++++++ 8 files changed, 94 insertions(+), 14 deletions(-) create mode 100644 public/app/core/utils/reselect.ts diff --git a/package.json b/package.json index fae51a1d856..22cfe33a4d0 100644 --- a/package.json +++ b/package.json @@ -151,6 +151,7 @@ "dependencies": { "@babel/polyfill": "^7.0.0", "@torkelo/react-select": "2.1.1", + "@types/reselect": "^2.2.0", "angular": "1.6.6", "angular-bindonce": "0.3.1", "angular-native-dragdrop": "1.2.2", @@ -187,6 +188,7 @@ "redux-logger": "^3.0.6", "redux-thunk": "^2.3.0", "remarkable": "^1.7.1", + "reselect": "^4.0.0", "rst2html": "github:thoward/rst2html#990cb89", "rxjs": "^6.3.3", "slate": "^0.33.4", diff --git a/public/app/core/utils/reselect.ts b/public/app/core/utils/reselect.ts new file mode 100644 index 00000000000..7c8fc7727b0 --- /dev/null +++ b/public/app/core/utils/reselect.ts @@ -0,0 +1,5 @@ +import { memoize } from 'lodash'; +import { createSelectorCreator } from 'reselect'; + +const hashFn = (...args) => args.reduce((acc, val) => acc + '-' + JSON.stringify(val), ''); +export const createLodashMemoizedSelector = createSelectorCreator(memoize, hashFn); diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index f41555b9121..0e3b3f3558e 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -9,8 +9,6 @@ import { LogsDedupDescription, LogsDedupStrategy, LogsModel, - dedupLogRows, - filterLogLevels, LogLevel, LogsMetaKind, } from 'app/core/logs_model'; @@ -51,6 +49,7 @@ function renderMetaItem(value: any, kind: LogsMetaKind) { interface Props { data?: LogsModel; + dedupedData?: LogsModel; width: number; exploreId: string; highlighterExpressions: string[]; @@ -59,16 +58,17 @@ interface Props { scanning?: boolean; scanRange?: RawTimeRange; dedupStrategy: LogsDedupStrategy; + hiddenLogLevels: Set; onChangeTime?: (range: RawTimeRange) => void; onClickLabel?: (label: string, value: string) => void; onStartScanning?: () => void; onStopScanning?: () => void; onDedupStrategyChange: (dedupStrategy: LogsDedupStrategy) => void; + onToggleLogLevel: (hiddenLogLevels: Set) => void; } interface State { deferLogs: boolean; - hiddenLogLevels: Set; renderAll: boolean; showLabels: boolean | null; // Tristate: null means auto showLocalTime: boolean; @@ -81,7 +81,6 @@ export default class Logs extends PureComponent { state = { deferLogs: true, - hiddenLogLevels: new Set(), renderAll: false, showLabels: null, showLocalTime: true, @@ -142,7 +141,7 @@ export default class Logs extends PureComponent { onToggleLogLevel = (rawLevel: string, hiddenRawLevels: Set) => { const hiddenLogLevels: Set = new Set(Array.from(hiddenRawLevels).map(level => LogLevel[level])); - this.setState({ hiddenLogLevels }); + this.props.onToggleLogLevel(hiddenLogLevels); }; onClickScan = (event: React.SyntheticEvent) => { @@ -166,21 +165,18 @@ export default class Logs extends PureComponent { scanning, scanRange, width, + dedupedData, } = this.props; if (!data) { return null; } - const { deferLogs, hiddenLogLevels, renderAll, showLocalTime, showUtc, } = this.state; + const { deferLogs, renderAll, showLocalTime, showUtc } = this.state; let { showLabels } = this.state; const { dedupStrategy } = this.props; const hasData = data && data.rows && data.rows.length > 0; const showDuplicates = dedupStrategy !== LogsDedupStrategy.none; - - // Filtering - const filteredData = filterLogLevels(data, hiddenLogLevels); - const dedupedData = dedupLogRows(filteredData, dedupStrategy); const dedupCount = dedupedData.rows.reduce((sum, row) => sum + row.duplicates, 0); const meta = [...data.meta]; diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index 190c1c43b5a..9fd06afae9b 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -4,18 +4,21 @@ import { connect } from 'react-redux'; import { RawTimeRange, TimeRange } from '@grafana/ui'; import { ExploreId, ExploreItemState } from 'app/types/explore'; -import { LogsModel, LogsDedupStrategy } from 'app/core/logs_model'; +import { LogsModel, LogsDedupStrategy, LogLevel, filterLogLevels, dedupLogRows } from 'app/core/logs_model'; import { StoreState } from 'app/types'; import { toggleLogs, changeDedupStrategy } from './state/actions'; import Logs from './Logs'; import Panel from './Panel'; +import { toggleLogLevelAction } from 'app/features/explore/state/actionTypes'; +import { createLodashMemoizedSelector } from 'app/core/utils/reselect'; interface LogsContainerProps { exploreId: ExploreId; loading: boolean; logsHighlighterExpressions?: string[]; logsResult?: LogsModel; + dedupedResult?: LogsModel; onChangeTime: (range: TimeRange) => void; onClickLabel: (key: string, value: string) => void; onStartScanning: () => void; @@ -25,8 +28,10 @@ interface LogsContainerProps { scanRange?: RawTimeRange; showingLogs: boolean; toggleLogs: typeof toggleLogs; + toggleLogLevelAction: typeof toggleLogLevelAction; changeDedupStrategy: typeof changeDedupStrategy; dedupStrategy: LogsDedupStrategy; + hiddenLogLevels: Set; width: number; } @@ -39,12 +44,21 @@ export class LogsContainer extends PureComponent { this.props.changeDedupStrategy(this.props.exploreId, dedupStrategy); }; + hangleToggleLogLevel = (hiddenLogLevels: Set) => { + const { exploreId } = this.props; + this.props.toggleLogLevelAction({ + exploreId, + hiddenLogLevels, + }); + }; + render() { const { exploreId, loading, logsHighlighterExpressions, logsResult, + dedupedResult, onChangeTime, onClickLabel, onStartScanning, @@ -54,6 +68,7 @@ export class LogsContainer extends PureComponent { scanning, scanRange, width, + hiddenLogLevels, } = this.props; return ( @@ -61,6 +76,7 @@ export class LogsContainer extends PureComponent { { onStartScanning={onStartScanning} onStopScanning={onStopScanning} onDedupStrategyChange={this.handleDedupStrategyChange} + onToggleLogLevel={this.hangleToggleLogLevel} range={range} scanning={scanning} scanRange={scanRange} width={width} + hiddenLogLevels={hiddenLogLevels} /> ); @@ -90,12 +108,29 @@ const selectItemUIState = (itemState: ExploreItemState) => { dedupStrategy, }; }; + +const logsSelector = (state: ExploreItemState) => state.logsResult; +const hiddenLogLevelsSelector = (state: ExploreItemState) => state.hiddenLogLevels; +const dedupStrategySelector = (state: ExploreItemState) => state.dedupStrategy; +const deduplicatedLogsSelector = createLodashMemoizedSelector( + logsSelector, hiddenLogLevelsSelector, dedupStrategySelector, + (logs, hiddenLogLevels, dedupStrategy) => { + if (!logs) { + return null; + } + const filteredData = filterLogLevels(logs, new Set(hiddenLogLevels)); + return dedupLogRows(filteredData, dedupStrategy); + } +); + function mapStateToProps(state: StoreState, { exploreId }) { const explore = state.explore; const item: ExploreItemState = explore[exploreId]; const { logsHighlighterExpressions, logsResult, queryTransactions, scanning, scanRange, range } = item; const loading = queryTransactions.some(qt => qt.resultType === 'Logs' && !qt.done); - const {showingLogs, dedupStrategy} = selectItemUIState(item); + const { showingLogs, dedupStrategy } = selectItemUIState(item); + const hiddenLogLevels = new Set(item.hiddenLogLevels); + const dedupedResult = deduplicatedLogsSelector(item); return { loading, @@ -106,12 +141,15 @@ function mapStateToProps(state: StoreState, { exploreId }) { showingLogs, range, dedupStrategy, + hiddenLogLevels, + dedupedResult, }; } const mapDispatchToProps = { toggleLogs, changeDedupStrategy, + toggleLogLevelAction, }; export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(LogsContainer)); diff --git a/public/app/features/explore/state/actionTypes.ts b/public/app/features/explore/state/actionTypes.ts index d54a8754c3d..c54eef97a43 100644 --- a/public/app/features/explore/state/actionTypes.ts +++ b/public/app/features/explore/state/actionTypes.ts @@ -18,6 +18,7 @@ import { ExploreUIState, } from 'app/types/explore'; import { actionCreatorFactory, noPayloadActionCreatorFactory, ActionOf } from 'app/core/redux/actionCreatorFactory'; +import { LogLevel } from 'app/core/logs_model'; /** Higher order actions * @@ -201,6 +202,11 @@ export interface UpdateDatasourceInstancePayload { datasourceInstance: DataSourceApi; } +export interface ToggleLogLevelPayload { + exploreId: ExploreId; + hiddenLogLevels: Set; +} + export interface QueriesImportedPayload { exploreId: ExploreId; queries: DataQuery[]; @@ -397,6 +403,10 @@ export const updateDatasourceInstanceAction = actionCreatorFactory( + 'explore/TOGGLE_LOG_LEVEL' +).create(); + /** * Resets state for explore. */ @@ -436,4 +446,5 @@ export type Action = | ActionOf | ActionOf | ActionOf - | ActionOf; + | ActionOf + | ActionOf; diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index 255591ee6e3..db3e9a95858 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -38,6 +38,7 @@ import { toggleTableAction, queriesImportedAction, updateUIStateAction, + toggleLogLevelAction, } from './actionTypes'; export const DEFAULT_RANGE = { @@ -467,6 +468,16 @@ export const itemReducer = reducerFactory({} as ExploreItemSta }; }, }) + .addMapper({ + filter: toggleLogLevelAction, + mapper: (state, action): ExploreItemState => { + const { hiddenLogLevels } = action.payload; + return { + ...state, + hiddenLogLevels: Array.from(hiddenLogLevels) + }; + }, + }) .create(); /** diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 066ca226157..7a6af04b2ee 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -11,7 +11,7 @@ import { } from '@grafana/ui'; import { Emitter } from 'app/core/core'; -import { LogsModel, LogsDedupStrategy } from 'app/core/logs_model'; +import { LogsModel, LogsDedupStrategy, LogLevel } from 'app/core/logs_model'; import TableModel from 'app/core/table_model'; export interface CompletionItem { @@ -242,6 +242,11 @@ export interface ExploreItemState { * Current logs deduplication strategy */ dedupStrategy?: LogsDedupStrategy; + + /** + * Currently hidden log series + */ + hiddenLogLevels?: LogLevel[]; } export interface ExploreUIState { diff --git a/yarn.lock b/yarn.lock index df2e1cea37e..3c86bdf810f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1819,6 +1819,13 @@ "@types/prop-types" "*" csstype "^2.2.0" +"@types/reselect@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@types/reselect/-/reselect-2.2.0.tgz#c667206cfdc38190e1d379babe08865b2288575f" + integrity sha1-xmcgbP3DgZDh03m6vgiGWyKIV18= + dependencies: + reselect "*" + "@types/storybook__addon-actions@^3.4.1": version "3.4.1" resolved "https://registry.yarnpkg.com/@types/storybook__addon-actions/-/storybook__addon-actions-3.4.1.tgz#8f90d76b023b58ee794170f2fe774a3fddda2c1d" @@ -14894,6 +14901,11 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= +reselect@*, reselect@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7" + integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA== + resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" From 951e5932d4e650e160aeae2bbde850b7985dd237 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 11 Feb 2019 17:00:16 +0100 Subject: [PATCH 12/52] changelog: adds note for #15363 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a82fc7050b4..6589099e178 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ * **AzureMonitor**: improve autocomplete for Log Analytics and App Insights editor [#15131](https://github.com/grafana/grafana/issues/15131) * **LDAP**: Fix IPA/FreeIPA v4.6.4 does not allow LDAP searches with empty attributes [#14432](https://github.com/grafana/grafana/issues/14432) +### Breaking changes + +* **Internal Metrics** Edition has been added to the build_info metric. This will break any Graphite queries using this metric. Edition will be a new label for the Prometheus metric. [#15363](https://github.com/grafana/grafana/pull/15363) + ### 6.0.0-beta1 fixes * **Postgres**: Fix default port not added when port not configured [#15189](https://github.com/grafana/grafana/issues/15189) From 0493d905f17f6b1b7ffbb7afbf181a91798d5bf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 17:42:31 +0100 Subject: [PATCH 13/52] Update CHANGELOG.md --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6589099e178..7b97da0e81c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ -# 6.0.0-beta2 (unreleased) +# 6.0.0-beta3 (unreleased) + +# 6.0.0-beta2 (2019-02-11) ### New Features * **AzureMonitor**: Enable alerting by converting Azure Monitor API to Go [#14623](https://github.com/grafana/grafana/issues/14623) From e38cfc1a7183bd30e6e3eb022077e96fe6167d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 17:43:02 +0100 Subject: [PATCH 14/52] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2f44291a86a..f004ee07732 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "company": "Grafana Labs" }, "name": "grafana", - "version": "6.0.0-prebeta2", + "version": "6.0.0-pre3", "repository": { "type": "git", "url": "http://github.com/grafana/grafana.git" From fc91e1cf57a22e4a4a5cd1e99f7bee70ad3ec65b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 17:47:47 +0100 Subject: [PATCH 15/52] Fixed issue with gauge requests being cancelled --- public/app/features/dashboard/dashgrid/DataPanel.tsx | 3 +-- public/app/features/dashboard/dashgrid/PanelChrome.tsx | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DataPanel.tsx b/public/app/features/dashboard/dashgrid/DataPanel.tsx index 2183548000b..b81d66fa7f5 100644 --- a/public/app/features/dashboard/dashgrid/DataPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DataPanel.tsx @@ -28,7 +28,7 @@ interface RenderProps { export interface Props { datasource: string | null; queries: any[]; - panelId?: number; + panelId: number; dashboardId?: number; isVisible?: boolean; timeRange?: TimeRange; @@ -50,7 +50,6 @@ export interface State { export class DataPanel extends Component { static defaultProps = { isVisible: true, - panelId: 1, dashboardId: 1, }; diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index b02d9479dcc..b29be4b389d 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -149,6 +149,7 @@ export class PanelChrome extends PureComponent { this.renderPanel(false, panel.snapshotData, width, height) ) : ( Date: Mon, 11 Feb 2019 21:11:19 +0100 Subject: [PATCH 16/52] Fix error caused by named colors that are not part of named colors palette --- packages/grafana-ui/src/utils/namedColorsPalette.test.ts | 8 ++++---- packages/grafana-ui/src/utils/namedColorsPalette.ts | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/grafana-ui/src/utils/namedColorsPalette.test.ts b/packages/grafana-ui/src/utils/namedColorsPalette.test.ts index aa57b46636c..e544548aa4a 100644 --- a/packages/grafana-ui/src/utils/namedColorsPalette.test.ts +++ b/packages/grafana-ui/src/utils/namedColorsPalette.test.ts @@ -44,10 +44,6 @@ describe('colors', () => { }); describe('getColorFromHexRgbOrName', () => { - it('returns undefined for unknown color', () => { - expect(() => getColorFromHexRgbOrName('aruba-sunshine')).toThrow(); - }); - it('returns dark hex variant for known color if theme not specified', () => { expect(getColorFromHexRgbOrName(SemiDarkBlue.name)).toBe(SemiDarkBlue.variants.dark); }); @@ -64,5 +60,9 @@ describe('colors', () => { expect(getColorFromHexRgbOrName('rgb(0,0,0)')).toBe('rgb(0,0,0)'); expect(getColorFromHexRgbOrName('rgba(0,0,0,1)')).toBe('rgba(0,0,0,1)'); }); + + it('returns hex for named color that is not a part of named colors palette', () => { + expect(getColorFromHexRgbOrName('lime')).toBe('#00ff00'); + }); }); }); diff --git a/packages/grafana-ui/src/utils/namedColorsPalette.ts b/packages/grafana-ui/src/utils/namedColorsPalette.ts index ee5741e794e..88ae510a6d8 100644 --- a/packages/grafana-ui/src/utils/namedColorsPalette.ts +++ b/packages/grafana-ui/src/utils/namedColorsPalette.ts @@ -1,5 +1,6 @@ import { flatten } from 'lodash'; import { GrafanaThemeType } from '../types'; +import tinycolor from 'tinycolor2'; type Hue = 'green' | 'yellow' | 'red' | 'blue' | 'orange' | 'purple'; @@ -106,7 +107,7 @@ export const getColorFromHexRgbOrName = (color: string, theme?: GrafanaThemeType const colorDefinition = getColorByName(color); if (!colorDefinition) { - throw new Error('Unknown color'); + return new tinycolor(color).toHexString(); } return theme ? colorDefinition.variants[theme] : colorDefinition.variants.dark; From a5d158c014a65fded992bab069368eb61adbe1a8 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 11 Feb 2019 21:22:53 +0100 Subject: [PATCH 17/52] Added one more test case for color resolving helper --- packages/grafana-ui/src/utils/namedColorsPalette.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/grafana-ui/src/utils/namedColorsPalette.test.ts b/packages/grafana-ui/src/utils/namedColorsPalette.test.ts index e544548aa4a..19c7d9c84ad 100644 --- a/packages/grafana-ui/src/utils/namedColorsPalette.test.ts +++ b/packages/grafana-ui/src/utils/namedColorsPalette.test.ts @@ -44,6 +44,10 @@ describe('colors', () => { }); describe('getColorFromHexRgbOrName', () => { + it('returns black for unknown color', () => { + expect(getColorFromHexRgbOrName('aruba-sunshine')).toBe("#000000"); + }); + it('returns dark hex variant for known color if theme not specified', () => { expect(getColorFromHexRgbOrName(SemiDarkBlue.name)).toBe(SemiDarkBlue.variants.dark); }); From edd9576f1586178501b1d75726944d1df70fcc1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 12 Feb 2019 08:04:30 +0100 Subject: [PATCH 18/52] Fixed elastic5 docker compose block --- devenv/docker/blocks/elastic5/docker-compose.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/devenv/docker/blocks/elastic5/docker-compose.yaml b/devenv/docker/blocks/elastic5/docker-compose.yaml index 33a7d9855b0..3a2ef39faba 100644 --- a/devenv/docker/blocks/elastic5/docker-compose.yaml +++ b/devenv/docker/blocks/elastic5/docker-compose.yaml @@ -1,6 +1,3 @@ -# You need to run 'sysctl -w vm.max_map_count=262144' on the host machine -version: '2' -services: elasticsearch5: image: elasticsearch:5 command: elasticsearch From 3b9105e1bebb24e518548faf258576e0b30f70a8 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 12 Feb 2019 08:45:21 +0100 Subject: [PATCH 19/52] enable testing provsioned datasources closes #12164 --- .../datasources/settings/ButtonRow.test.tsx | 1 + .../datasources/settings/ButtonRow.tsx | 8 +- .../settings/DataSourceSettingsPage.tsx | 89 ++++++++++--------- .../__snapshots__/ButtonRow.test.tsx.snap | 7 ++ .../DataSourceSettingsPage.test.tsx.snap | 4 + 5 files changed, 68 insertions(+), 41 deletions(-) diff --git a/public/app/features/datasources/settings/ButtonRow.test.tsx b/public/app/features/datasources/settings/ButtonRow.test.tsx index 0acab8941ff..84b16d829d5 100644 --- a/public/app/features/datasources/settings/ButtonRow.test.tsx +++ b/public/app/features/datasources/settings/ButtonRow.test.tsx @@ -7,6 +7,7 @@ const setup = (propOverrides?: object) => { isReadOnly: true, onSubmit: jest.fn(), onDelete: jest.fn(), + onTest: jest.fn(), }; Object.assign(props, propOverrides); diff --git a/public/app/features/datasources/settings/ButtonRow.tsx b/public/app/features/datasources/settings/ButtonRow.tsx index 6b85e21405c..3e8ac060010 100644 --- a/public/app/features/datasources/settings/ButtonRow.tsx +++ b/public/app/features/datasources/settings/ButtonRow.tsx @@ -4,14 +4,20 @@ export interface Props { isReadOnly: boolean; onDelete: () => void; onSubmit: (event) => void; + onTest: (event) => void; } -const ButtonRow: FC = ({ isReadOnly, onDelete, onSubmit }) => { +const ButtonRow: FC = ({ isReadOnly, onDelete, onSubmit, onTest }) => { return (
+ {isReadOnly && ( + + )} diff --git a/public/app/features/datasources/settings/DataSourceSettingsPage.tsx b/public/app/features/datasources/settings/DataSourceSettingsPage.tsx index ff840390cf5..fe1121ed73e 100644 --- a/public/app/features/datasources/settings/DataSourceSettingsPage.tsx +++ b/public/app/features/datasources/settings/DataSourceSettingsPage.tsx @@ -72,6 +72,12 @@ export class DataSourceSettingsPage extends PureComponent { this.testDataSource(); }; + onTest = async (evt: React.FormEvent) => { + evt.preventDefault(); + + this.testDataSource(); + }; + onDelete = () => { appEvents.emit('confirm-modal', { title: 'Delete', @@ -180,52 +186,55 @@ export class DataSourceSettingsPage extends PureComponent { return ( - {this.hasDataSource &&
-
-
- {this.isReadOnly() && this.renderIsReadOnlyMessage()} - {this.shouldRenderInfoBox() &&
{this.getInfoText()}
} + {this.hasDataSource && ( +
+
+ + {this.isReadOnly() && this.renderIsReadOnlyMessage()} + {this.shouldRenderInfoBox() &&
{this.getInfoText()}
} - setIsDefault(state)} - onNameChange={name => setDataSourceName(name)} - /> - - {dataSourceMeta.module && ( - setIsDefault(state)} + onNameChange={name => setDataSourceName(name)} /> - )} -
- {testingMessage && ( -
-
- {testingStatus === 'error' ? ( - - ) : ( - - )} -
-
-
{testingMessage}
-
-
+ {dataSourceMeta.module && ( + )} -
- this.onSubmit(event)} - isReadOnly={this.isReadOnly()} - onDelete={this.onDelete} - /> - +
+ {testingMessage && ( +
+
+ {testingStatus === 'error' ? ( + + ) : ( + + )} +
+
+
{testingMessage}
+
+
+ )} +
+ + this.onSubmit(event)} + isReadOnly={this.isReadOnly()} + onDelete={this.onDelete} + onTest={event => this.onTest(event)} + /> + +
-
} + )} ); diff --git a/public/app/features/datasources/settings/__snapshots__/ButtonRow.test.tsx.snap b/public/app/features/datasources/settings/__snapshots__/ButtonRow.test.tsx.snap index bd190f60b03..d4ec7eeea1e 100644 --- a/public/app/features/datasources/settings/__snapshots__/ButtonRow.test.tsx.snap +++ b/public/app/features/datasources/settings/__snapshots__/ButtonRow.test.tsx.snap @@ -12,6 +12,13 @@ exports[`Render should render component 1`] = ` > Save & Test +
@@ -202,6 +203,7 @@ exports[`Render should render beta info text 1`] = ` isReadOnly={false} onDelete={[Function]} onSubmit={[Function]} + onTest={[Function]} />
@@ -302,6 +304,7 @@ exports[`Render should render component 1`] = ` isReadOnly={false} onDelete={[Function]} onSubmit={[Function]} + onTest={[Function]} /> @@ -407,6 +410,7 @@ exports[`Render should render is ready only message 1`] = ` isReadOnly={true} onDelete={[Function]} onSubmit={[Function]} + onTest={[Function]} /> From 5388541fd7dbefc7300c46b23e280872bdf61881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Tue, 12 Feb 2019 08:03:43 +0100 Subject: [PATCH 20/52] Fixes bug #12972 with a new type of input that escapes and unescapes special regexp characters --- .../components/OrgActionBar/OrgActionBar.tsx | 10 ++-- .../__snapshots__/OrgActionBar.test.tsx.snap | 5 +- .../RegExpSafeInput/RegExpSafeInput.tsx | 48 +++++++++++++++++++ .../features/alerting/AlertRuleList.test.tsx | 5 +- .../app/features/alerting/AlertRuleList.tsx | 11 ++--- .../__snapshots__/AlertRuleList.test.tsx.snap | 6 +-- .../features/api-keys/ApiKeysPage.test.tsx | 9 ++-- public/app/features/api-keys/ApiKeysPage.tsx | 16 ++----- .../panel_editor/VisualizationTab.tsx | 7 ++- .../datasources/NewDataSourcePage.tsx | 10 ++-- public/app/features/teams/TeamList.test.tsx | 9 ++-- public/app/features/teams/TeamList.tsx | 12 ++--- .../app/features/teams/TeamMembers.test.tsx | 3 +- public/app/features/teams/TeamMembers.tsx | 8 ++-- .../__snapshots__/TeamMembers.test.tsx.snap | 9 ++-- public/app/features/users/UsersActionBar.tsx | 6 +-- .../UsersActionBar.test.tsx.snap | 20 ++++---- 17 files changed, 109 insertions(+), 85 deletions(-) create mode 100644 public/app/core/components/RegExpSafeInput/RegExpSafeInput.tsx diff --git a/public/app/core/components/OrgActionBar/OrgActionBar.tsx b/public/app/core/components/OrgActionBar/OrgActionBar.tsx index b6b2046736f..9bf1eacc515 100644 --- a/public/app/core/components/OrgActionBar/OrgActionBar.tsx +++ b/public/app/core/components/OrgActionBar/OrgActionBar.tsx @@ -1,5 +1,6 @@ import React, { PureComponent } from 'react'; import LayoutSelector, { LayoutMode } from '../LayoutSelector/LayoutSelector'; +import { RegExpSafeInput } from '../RegExpSafeInput/RegExpSafeInput'; export interface Props { searchQuery: string; @@ -23,12 +24,11 @@ export default class OrgActionBar extends PureComponent {
diff --git a/public/app/core/components/OrgActionBar/__snapshots__/OrgActionBar.test.tsx.snap b/public/app/core/components/OrgActionBar/__snapshots__/OrgActionBar.test.tsx.snap index 25de037930a..db453b8cc3f 100644 --- a/public/app/core/components/OrgActionBar/__snapshots__/OrgActionBar.test.tsx.snap +++ b/public/app/core/components/OrgActionBar/__snapshots__/OrgActionBar.test.tsx.snap @@ -10,11 +10,10 @@ exports[`Render should render component 1`] = `