diff --git a/packages/grafana-data/src/field/fieldOverrides.test.ts b/packages/grafana-data/src/field/fieldOverrides.test.ts index ab75cbcac65..46c7ef1db3e 100644 --- a/packages/grafana-data/src/field/fieldOverrides.test.ts +++ b/packages/grafana-data/src/field/fieldOverrides.test.ts @@ -545,7 +545,7 @@ describe('getLinksSupplier', () => { expect.objectContaining({ title: 'testDS', href: - '/explore?left={"datasource":"testDS","queries":["12345"],"mode":"Metrics","ui":{"showingGraph":true,"showingTable":true,"showingLogs":true}}', + '/explore?left={"datasource":"testDS","queries":["12345"],"ui":{"showingGraph":true,"showingTable":true,"showingLogs":true}}', onClick: undefined, }) ); diff --git a/packages/grafana-data/src/types/data.ts b/packages/grafana-data/src/types/data.ts index faf395bacd0..7fe6ae08961 100644 --- a/packages/grafana-data/src/types/data.ts +++ b/packages/grafana-data/src/types/data.ts @@ -15,7 +15,7 @@ export enum LoadingState { Error = 'Error', } -export type PreferredVisualisationType = 'graph' | 'table'; +export type PreferredVisualisationType = 'graph' | 'table' | 'logs' | 'trace'; export interface QueryResultMeta { /** DatasSource Specific Values */ @@ -47,6 +47,7 @@ export interface QueryResultMeta { searchWords?: string[]; // used by log models and loki limit?: number; // used by log models and loki json?: boolean; // used to keep track of old json doc values + instant?: boolean; } export interface QueryResultMetaStat extends FieldConfig { diff --git a/packages/grafana-data/src/types/datasource.ts b/packages/grafana-data/src/types/datasource.ts index 6d08b24545b..ee503bec5d7 100644 --- a/packages/grafana-data/src/types/datasource.ts +++ b/packages/grafana-data/src/types/datasource.ts @@ -300,7 +300,6 @@ export interface QueryEditorProps< * Contains query response filtered by refId of QueryResultBase and possible query error */ data?: PanelData; - exploreMode?: ExploreMode; exploreId?: any; history?: HistoryItem[]; } @@ -324,13 +323,11 @@ export interface ExploreQueryFieldProps< history: any[]; onBlur?: () => void; absoluteRange?: AbsoluteTimeRange; - exploreMode?: ExploreMode; exploreId?: any; } export interface ExploreStartPageProps { datasource: DataSourceApi; - exploreMode: ExploreMode; onClickExample: (query: DataQuery) => void; exploreId?: any; } diff --git a/packages/grafana-data/src/types/explore.ts b/packages/grafana-data/src/types/explore.ts index 04812ab435a..92fd8a1e322 100644 --- a/packages/grafana-data/src/types/explore.ts +++ b/packages/grafana-data/src/types/explore.ts @@ -1,4 +1,3 @@ -import { ExploreMode } from './datasource'; import { RawTimeRange } from './time'; import { LogsDedupStrategy } from './logs'; @@ -6,7 +5,6 @@ import { LogsDedupStrategy } from './logs'; export interface ExploreUrlState { datasource: string; queries: any[]; // Should be a DataQuery, but we're going to strip refIds, so typing makes less sense - mode: ExploreMode; range: RawTimeRange; ui: ExploreUIState; originPanelId?: number; diff --git a/packages/grafana-data/src/utils/dataLinks.test.ts b/packages/grafana-data/src/utils/dataLinks.test.ts index 08b27d21567..97674cd8b67 100644 --- a/packages/grafana-data/src/utils/dataLinks.test.ts +++ b/packages/grafana-data/src/utils/dataLinks.test.ts @@ -31,7 +31,7 @@ describe('mapInternalLinkToExplore', () => { expect.objectContaining({ title: 'testDS', href: - '/explore?left={"datasource":"testDS","queries":[{"query":"12344"}],"mode":"Metrics","ui":{"showingGraph":true,"showingTable":true,"showingLogs":true}}', + '/explore?left={"datasource":"testDS","queries":[{"query":"12344"}],"ui":{"showingGraph":true,"showingTable":true,"showingLogs":true}}', onClick: undefined, }) ); diff --git a/packages/grafana-data/src/utils/dataLinks.ts b/packages/grafana-data/src/utils/dataLinks.ts index 5921b739ec1..dd792021b0f 100644 --- a/packages/grafana-data/src/utils/dataLinks.ts +++ b/packages/grafana-data/src/utils/dataLinks.ts @@ -2,7 +2,6 @@ import { DataLink, DataQuery, DataSourceInstanceSettings, - ExploreMode, Field, InterpolateFunction, LinkModel, @@ -82,7 +81,6 @@ function generateInternalHref(datasourceName: string, queries: [query], // This should get overwritten if datasource does not support that mode and we do not know what mode is // preferred anyway. - mode: ExploreMode.Metrics, ui: { showingGraph: true, showingTable: true, diff --git a/packages/grafana-data/src/utils/url.ts b/packages/grafana-data/src/utils/url.ts index f7a492e9619..af426ea252f 100644 --- a/packages/grafana-data/src/utils/url.ts +++ b/packages/grafana-data/src/utils/url.ts @@ -139,7 +139,6 @@ export function serializeStateToUrlParam(urlState: ExploreUrlState, compact?: bo urlState.range.to, urlState.datasource, ...urlState.queries, - { mode: urlState.mode }, { ui: [ !!urlState.ui.showingGraph, diff --git a/pkg/tsdb/cloudwatch/log_actions.go b/pkg/tsdb/cloudwatch/log_actions.go index e2cac5bed5f..e471a17d0a7 100644 --- a/pkg/tsdb/cloudwatch/log_actions.go +++ b/pkg/tsdb/cloudwatch/log_actions.go @@ -45,6 +45,14 @@ func (e *CloudWatchExecutor) executeLogActions(ctx context.Context, queryContext return nil } + if dataframe.Meta != nil { + dataframe.Meta.PreferredVisualization = "logs" + } else { + dataframe.Meta = &data.FrameMeta{ + PreferredVisualization: "logs", + } + } + resultChan <- &tsdb.QueryResult{RefId: query.RefId, Dataframes: tsdb.NewDecodedDataFrames(data.Frames{dataframe})} return nil }) diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index ba0b3438a2f..c3ab257a78f 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -32,7 +32,6 @@ import { import { getThemeColor } from 'app/core/utils/colors'; import { sortInAscendingOrder, deduplicateLogRowsById } from 'app/core/utils/explore'; -import { getGraphSeriesModel } from 'app/plugins/panel/graph2/getGraphSeriesModel'; import { decimalSIPrefix } from '@grafana/data/src/valueFormats/symbolFormatters'; export const LogLevelColor = { @@ -143,19 +142,23 @@ export function makeSeriesForLogs(sortedRows: LogRowModel[], bucketSize: number, const fieldCache = new FieldCache(data); const timeField = fieldCache.getFirstFieldOfType(FieldType.time); - timeField.display = getDisplayProcessor({ - field: timeField, - timeZone, - }); + if (timeField) { + timeField.display = getDisplayProcessor({ + field: timeField, + timeZone, + }); + } const valueField = fieldCache.getFirstFieldOfType(FieldType.number); - valueField.config = { - ...valueField.config, - color: series.color, - }; - valueField.name = series.alias; - const fieldDisplayProcessor = getDisplayProcessor({ field: valueField, timeZone }); - valueField.display = (value: any) => ({ ...fieldDisplayProcessor(value), color: series.color }); + if (valueField) { + valueField.config = { + ...valueField.config, + color: series.color, + }; + valueField.name = series.alias; + const fieldDisplayProcessor = getDisplayProcessor({ field: valueField, timeZone }); + valueField.display = (value: any) => ({ ...fieldDisplayProcessor(value), color: series.color }); + } const points = getFlotPairs({ xField: timeField, @@ -201,35 +204,21 @@ export function dataFrameToLogsModel( timeZone: TimeZone, absoluteRange?: AbsoluteTimeRange ): LogsModel { - const { logSeries, metricSeries } = separateLogsAndMetrics(dataFrame); + const { logSeries } = separateLogsAndMetrics(dataFrame); const logsModel = logSeriesToLogsModel(logSeries); + // unification: Removed logic for using metrics data in LogsModel as with the unification changes this would result + // in the incorrect data being used. Instead logs series are always derived from logs. if (logsModel) { - if (metricSeries.length === 0) { - // Create histogram metrics from logs using the interval as bucket size for the line count - if (intervalMs && logsModel.rows.length > 0) { - const sortedRows = logsModel.rows.sort(sortInAscendingOrder); - const { visibleRange, bucketSize } = getSeriesProperties(sortedRows, intervalMs, absoluteRange); - logsModel.visibleRange = visibleRange; - logsModel.series = makeSeriesForLogs(sortedRows, bucketSize, timeZone); - } else { - logsModel.series = []; - } + // Create histogram metrics from logs using the interval as bucket size for the line count + if (intervalMs && logsModel.rows.length > 0) { + const sortedRows = logsModel.rows.sort(sortInAscendingOrder); + const { visibleRange, bucketSize } = getSeriesProperties(sortedRows, intervalMs, absoluteRange); + logsModel.visibleRange = visibleRange; + logsModel.series = makeSeriesForLogs(sortedRows, bucketSize, timeZone); } else { - // We got metrics in the dataFrame so process those - logsModel.series = getGraphSeriesModel( - metricSeries, - timeZone, - {}, - { showBars: true, showLines: false, showPoints: false }, - { - asTable: false, - isVisible: true, - placement: 'under', - } - ); + logsModel.series = []; } - return logsModel; } @@ -431,8 +420,8 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel | undefi // Stats are per query, keeping track by refId const { refId } = series; if (refId && !queriesVisited[refId]) { - if (totalBytesKey && series.meta.stats) { - const byteStat = series.meta.stats.find(stat => stat.displayName === totalBytesKey); + if (totalBytesKey && series.meta?.stats) { + const byteStat = series.meta?.stats.find(stat => stat.displayName === totalBytesKey); if (byteStat) { totalBytes += byteStat.value; } diff --git a/public/app/core/utils/explore.test.ts b/public/app/core/utils/explore.test.ts index 6a82c1e9e70..c8dbd0b9bd2 100644 --- a/public/app/core/utils/explore.test.ts +++ b/public/app/core/utils/explore.test.ts @@ -18,7 +18,6 @@ import store from 'app/core/store'; import { DataQueryError, dateTime, - ExploreMode, LogLevel, LogRowModel, LogsDedupStrategy, @@ -33,7 +32,6 @@ const DEFAULT_EXPLORE_STATE: ExploreUrlState = { datasource: '', queries: [], range: DEFAULT_RANGE, - mode: ExploreMode.Metrics, ui: { showingGraph: true, showingTable: true, @@ -101,7 +99,6 @@ 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"},' + - '"mode":"Metrics",' + '"ui":{"showingGraph":true,"showingTable":true,"showingLogs":true,"dedupStrategy":"none"}}' ); }); @@ -124,7 +121,7 @@ describe('state functions', () => { }, }; expect(serializeStateToUrlParam(state, true)).toBe( - '["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"},{"mode":"Metrics"},{"ui":[true,true,true,"none"]}]' + '["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 08b55081429..cce634dfb17 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -10,7 +10,6 @@ import { DataSourceApi, dateMath, DefaultTimeZone, - ExploreMode, HistoryItem, IntervalValues, LogRowModel, @@ -249,9 +248,6 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState { const metricProperties = ['expr', 'expression', 'target', 'datasource', 'query']; const queries = parsedSegments.filter(segment => isSegment(segment, ...metricProperties)); - const modeObj = parsedSegments.filter(segment => isSegment(segment, 'mode'))[0]; - const mode = modeObj ? modeObj.mode : ExploreMode.Metrics; - const uiState = parsedSegments.filter(segment => isSegment(segment, 'ui'))[0]; const ui = uiState ? { @@ -263,7 +259,7 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState { : DEFAULT_UI_STATE; const originPanelId = parsedSegments.filter(segment => isSegment(segment, 'originPanelId'))[0]; - return { datasource, queries, range, ui, mode, originPanelId }; + return { datasource, queries, range, ui, originPanelId }; } export function generateKey(index = 0): string { diff --git a/public/app/core/utils/richHistory.ts b/public/app/core/utils/richHistory.ts index eb211b2cf1e..a2bc9976cae 100644 --- a/public/app/core/utils/richHistory.ts +++ b/public/app/core/utils/richHistory.ts @@ -2,15 +2,7 @@ import _ from 'lodash'; // Services & Utils -import { - DataQuery, - DataSourceApi, - ExploreMode, - dateTimeFormat, - AppEvents, - urlUtil, - ExploreUrlState, -} from '@grafana/data'; +import { DataQuery, DataSourceApi, dateTimeFormat, AppEvents, urlUtil, ExploreUrlState } from '@grafana/data'; import appEvents from 'app/core/app_events'; import store from 'app/core/store'; import { SortOrder } from './explore'; @@ -187,15 +179,6 @@ export const createUrlFromRichHistory = (query: RichHistoryQuery) => { range: { from: 'now-1h', to: 'now' }, datasource: query.datasourceName, queries: query.queries, - /* Default mode is metrics. Exceptions are Loki (logs) and Jaeger (tracing) data sources. - * In the future, we can remove this as we are working on metrics & logs logic. - **/ - mode: - query.datasourceId === 'loki' - ? ExploreMode.Logs - : query.datasourceId === 'jaeger' - ? ExploreMode.Tracing - : ExploreMode.Metrics, ui: { showingGraph: true, showingLogs: true, diff --git a/public/app/features/explore/Explore.test.tsx b/public/app/features/explore/Explore.test.tsx index 0637abe2bdb..76b6adb37d4 100644 --- a/public/app/features/explore/Explore.test.tsx +++ b/public/app/features/explore/Explore.test.tsx @@ -1,23 +1,12 @@ import React from 'react'; -import { - DataSourceApi, - LoadingState, - ExploreMode, - toUtc, - DataQueryError, - DataQueryRequest, - CoreApp, - MutableDataFrame, -} from '@grafana/data'; +import { DataSourceApi, LoadingState, toUtc, DataQueryError, DataQueryRequest, CoreApp } from '@grafana/data'; import { getFirstNonQueryRowSpecificError } from 'app/core/utils/explore'; import { ExploreId } from 'app/types/explore'; import { shallow } from 'enzyme'; -import AutoSizer from 'react-virtualized-auto-sizer'; import { Explore, ExploreProps } from './Explore'; import { scanStopAction } from './state/actionTypes'; import { toggleGraph } from './state/actions'; import { SecondaryActions } from './SecondaryActions'; -import { TraceView } from './TraceView/TraceView'; import { getTheme } from '@grafana/ui'; const dummyProps: ExploreProps = { @@ -64,7 +53,6 @@ const dummyProps: ExploreProps = { to: 'now', }, }, - mode: ExploreMode.Metrics, initialUI: { showingTable: false, showingGraph: false, @@ -119,6 +107,10 @@ const dummyProps: ExploreProps = { originPanelId: 1, addQueryRow: jest.fn(), theme: getTheme(), + showMetrics: true, + showLogs: true, + showTable: true, + showTrace: true, }; const setupErrors = (hasRefId?: boolean) => { @@ -144,34 +136,6 @@ describe('Explore', () => { expect(wrapper.find(SecondaryActions).props().addQueryRowButtonHidden).toBe(false); }); - it('does not show add row button if mode is tracing', () => { - const wrapper = shallow(); - expect(wrapper.find(SecondaryActions).props().addQueryRowButtonHidden).toBe(true); - }); - - it('renders TraceView if tracing mode', () => { - const wrapper = shallow( - - ); - const autoSizer = shallow( - wrapper - .find(AutoSizer) - .props() - .children({ width: 100, height: 100 }) as React.ReactElement - ); - expect(autoSizer.find(TraceView).length).toBe(1); - }); - it('should filter out a query-row-specific error when looking for non-query-row-specific errors', async () => { const queryErrors = setupErrors(true); const queryError = getFirstNonQueryRowSpecificError(queryErrors); diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 52a123497ab..82ef4be9efd 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -11,7 +11,6 @@ import { AbsoluteTimeRange, DataQuery, DataSourceApi, - ExploreMode, GrafanaTheme, GraphSeriesXY, LoadingState, @@ -21,6 +20,7 @@ import { TimeZone, ExploreUIState, ExploreUrlState, + LogsModel, } from '@grafana/data'; import store from 'app/core/store'; @@ -58,6 +58,7 @@ import { getTimeZone } from '../profile/state/selectors'; import { ErrorContainer } from './ErrorContainer'; import { scanStopAction } from './state/actionTypes'; import { ExploreGraphPanel } from './ExploreGraphPanel'; +//TODO:unification import { TraceView } from './TraceView/TraceView'; import { SecondaryActions } from './SecondaryActions'; import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR, FilterItem } from '@grafana/ui/src/components/Table/types'; @@ -104,12 +105,12 @@ export interface ExploreProps { initialDatasource: string; initialQueries: DataQuery[]; initialRange: TimeRange; - mode: ExploreMode; initialUI: ExploreUIState; isLive: boolean; syncedTimes: boolean; updateTimeRange: typeof updateTimeRange; graphResult?: GraphSeriesXY[] | null; + logsResult?: LogsModel; loading?: boolean; absoluteRange: AbsoluteTimeRange; showingGraph?: boolean; @@ -121,6 +122,10 @@ export interface ExploreProps { originPanelId: number; addQueryRow: typeof addQueryRow; theme: GrafanaTheme; + showMetrics: boolean; + showTable: boolean; + showLogs: boolean; + showTrace: boolean; } interface ExploreState { @@ -170,7 +175,6 @@ export class Explore extends React.PureComponent { initialDatasource, initialQueries, initialRange, - mode, initialUI, originPanelId, } = this.props; @@ -183,7 +187,6 @@ export class Explore extends React.PureComponent { initialDatasource, initialQueries, initialRange, - mode, width, this.exploreEvents, initialUI, @@ -301,7 +304,6 @@ export class Explore extends React.PureComponent { exploreId, split, queryKeys, - mode, graphResult, loading, absoluteRange, @@ -312,6 +314,10 @@ export class Explore extends React.PureComponent { syncedTimes, isLive, theme, + showMetrics, + showTable, + showLogs, + showTrace, } = this.props; const { showRichHistory } = this.state; const exploreClass = split ? 'explore explore-split' : 'explore'; @@ -334,7 +340,8 @@ export class Explore extends React.PureComponent { { )} {!showStartPage && ( <> - {mode === ExploreMode.Metrics && ( + {showMetrics && ( { showLines={true} /> )} - {mode === ExploreMode.Metrics && ( + {showTable && ( { } /> )} - {mode === ExploreMode.Logs && ( + {showLogs && ( { onStopScanning={this.onStopScanning} /> )} - {mode === ExploreMode.Tracing && + {/* TODO:unification */} + {showTrace && // We expect only one trace at the moment to be in the dataframe // If there is not data (like 404) we show a separate error so no need to show anything here queryResponse.series[0] && ( @@ -442,9 +449,12 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps): Partia urlState, update, isLive, - supportedModes, - mode, graphResult, + logsResult, + showLogs, + showMetrics, + showTable, + showTrace, loading, showingGraph, showingTable, @@ -452,31 +462,13 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps): Partia queryResponse, } = item; - const { datasource, queries, range: urlRange, mode: urlMode, ui, originPanelId } = (urlState || - {}) as ExploreUrlState; + const { datasource, queries, range: urlRange, ui, originPanelId } = (urlState || {}) as ExploreUrlState; const initialDatasource = datasource || store.get(lastUsedDatasourceKeyForOrgId(state.user.orgId)); const initialQueries: DataQuery[] = ensureQueriesMemoized(queries); const initialRange = urlRange ? getTimeRangeFromUrlMemoized(urlRange, timeZone) : getTimeRange(timeZone, DEFAULT_RANGE); - let newMode: ExploreMode | undefined; - - if (supportedModes.length) { - const urlModeIsValid = supportedModes.includes(urlMode); - const modeStateIsValid = supportedModes.includes(mode); - - if (modeStateIsValid) { - newMode = mode; - } else if (urlModeIsValid) { - newMode = urlMode; - } else { - newMode = supportedModes[0]; - } - } else { - newMode = [ExploreMode.Metrics, ExploreMode.Logs, ExploreMode.Tracing].includes(urlMode) ? urlMode : undefined; - } - const initialUI = ui || DEFAULT_UI_STATE; return { @@ -489,10 +481,10 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps): Partia initialDatasource, initialQueries, initialRange, - mode: newMode, initialUI, isLive, - graphResult, + graphResult: graphResult ?? undefined, + logsResult: logsResult ?? undefined, loading, showingGraph, showingTable, @@ -501,6 +493,10 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps): Partia originPanelId, syncedTimes, timeZone, + showLogs, + showMetrics, + showTable, + showTrace, }; } diff --git a/public/app/features/explore/ExploreToolbar.test.tsx b/public/app/features/explore/ExploreToolbar.test.tsx deleted file mode 100644 index 5391822d185..00000000000 --- a/public/app/features/explore/ExploreToolbar.test.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React from 'react'; -import { shallow, ShallowWrapper } from 'enzyme'; -import { UnConnectedExploreToolbar } from './ExploreToolbar'; -import { ExploreMode } from '@grafana/data'; -import { ExploreId } from '../../types'; -import { ToggleButtonGroup } from '@grafana/ui'; - -jest.mock('./state/selectors', () => { - return { - __esModule: true, - getExploreDatasources: () => [] as any, - }; -}); - -describe('ExploreToolbar', () => { - it('displays correct modes', () => { - let wrapper = shallow(createToolbar([ExploreMode.Tracing, ExploreMode.Logs])); - checkModes(wrapper, ['Logs', 'Tracing']); - - wrapper = shallow(createToolbar([ExploreMode.Logs])); - checkModes(wrapper, []); - - wrapper = shallow(createToolbar([ExploreMode.Logs, ExploreMode.Tracing, ExploreMode.Metrics])); - checkModes(wrapper, ['Metrics', 'Logs', 'Tracing']); - }); -}); - -function checkModes(wrapper: ShallowWrapper, modes: string[]) { - expect( - wrapper - .find(ToggleButtonGroup) - .children() - .map(node => node.children().text()) - ).toEqual(modes); -} - -function createToolbar(supportedModes: ExploreMode[]) { - return ( - {}) as any} - clearAll={(() => {}) as any} - cancelQueries={(() => {}) as any} - runQueries={(() => {}) as any} - closeSplit={(() => {}) as any} - split={(() => {}) as any} - syncTimes={(() => {}) as any} - changeRefreshInterval={(() => {}) as any} - changeMode={(() => {}) as any} - updateLocation={(() => {}) as any} - setDashboardQueriesToUpdateOnLoad={(() => {}) as any} - exploreId={ExploreId.left} - onChangeTime={(() => {}) as any} - onChangeTimeZone={(() => {}) as any} - /> - ); -} diff --git a/public/app/features/explore/ExploreToolbar.tsx b/public/app/features/explore/ExploreToolbar.tsx index 5f398845969..210316f8753 100644 --- a/public/app/features/explore/ExploreToolbar.tsx +++ b/public/app/features/explore/ExploreToolbar.tsx @@ -6,14 +6,13 @@ import classNames from 'classnames'; import { css } from 'emotion'; import { ExploreId, ExploreItemState } from 'app/types/explore'; -import { Icon, IconButton, LegacyForms, SetInterval, ToggleButton, ToggleButtonGroup, Tooltip } from '@grafana/ui'; -import { DataQuery, ExploreMode, RawTimeRange, TimeRange, TimeZone } from '@grafana/data'; +import { Icon, IconButton, LegacyForms, SetInterval, Tooltip } from '@grafana/ui'; +import { DataQuery, RawTimeRange, TimeRange, TimeZone } from '@grafana/data'; import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker'; import { StoreState } from 'app/types/store'; import { cancelQueries, changeDatasource, - changeMode, changeRefreshInterval, clearQueries, runQueries, @@ -60,8 +59,6 @@ interface StateProps { splitted: boolean; syncedTimes: boolean; refreshInterval?: string; - supportedModes: ExploreMode[]; - selectedMode: ExploreMode; hasLiveOption: boolean; isLive: boolean; isPaused: boolean; @@ -81,7 +78,6 @@ interface DispatchProps { split: typeof splitOpen; syncTimes: typeof syncTimes; changeRefreshInterval: typeof changeRefreshInterval; - changeMode: typeof changeMode; updateLocation: typeof updateLocation; setDashboardQueriesToUpdateOnLoad: typeof setDashboardQueriesToUpdateOnLoad; onChangeTimeZone: typeof updateTimeZoneForSession; @@ -111,11 +107,6 @@ export class UnConnectedExploreToolbar extends PureComponent { changeRefreshInterval(exploreId, item); }; - onModeChange = (mode: ExploreMode) => { - const { changeMode, exploreId } = this.props; - changeMode(exploreId, mode); - }; - onChangeTimeSync = () => { const { syncTimes, exploreId } = this.props; syncTimes(exploreId); @@ -174,8 +165,6 @@ export class UnConnectedExploreToolbar extends PureComponent { refreshInterval, onChangeTime, split, - supportedModes, - selectedMode, hasLiveOption, isLive, isPaused, @@ -195,8 +184,6 @@ export class UnConnectedExploreToolbar extends PureComponent { const showSmallDataSourcePicker = (splitted ? containerWidth < 700 : containerWidth < 800) || false; const showSmallTimePicker = splitted || containerWidth < 1210; - const showModeToggle = supportedModes.length > 1; - return (
@@ -239,26 +226,6 @@ export class UnConnectedExploreToolbar extends PureComponent { hideTextValue={showSmallDataSourcePicker} />
- {showModeToggle ? ( -
- - {[ExploreMode.Metrics, ExploreMode.Logs, ExploreMode.Tracing] - .filter(mode => supportedModes.includes(mode)) - .map(mode => { - return ( - - {mode} - - ); - })} - -
- ) : null}
) : null} @@ -369,8 +336,6 @@ const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps range, refreshInterval, loading, - supportedModes, - mode, isLive, isPaused, originPanelId, @@ -379,7 +344,7 @@ const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps containerWidth, } = exploreItem; - const hasLiveOption = !!(datasourceInstance?.meta?.streaming && mode === ExploreMode.Logs); + const hasLiveOption = !!datasourceInstance?.meta?.streaming; return { datasourceMissing, @@ -389,15 +354,13 @@ const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps timeZone: getTimeZone(state.user), splitted, refreshInterval, - supportedModes, - selectedMode: supportedModes.includes(mode) ? mode : supportedModes[0], hasLiveOption, isLive, isPaused, originPanelId, queries, syncedTimes, - datasourceLoading, + datasourceLoading: datasourceLoading ?? undefined, containerWidth, }; }; @@ -412,7 +375,6 @@ const mapDispatchToProps: DispatchProps = { closeSplit: splitClose, split: splitOpen, syncTimes, - changeMode: changeMode, setDashboardQueriesToUpdateOnLoad, onChangeTimeZone: updateTimeZoneForSession, }; diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index abcb42d631f..c93ad6524df 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -62,7 +62,15 @@ interface LogsContainerProps { splitOpen: typeof splitOpen; } -export class LogsContainer extends PureComponent { +interface LogsContainerState { + logsContainerOpen: boolean; +} + +export class LogsContainer extends PureComponent { + state: LogsContainerState = { + logsContainerOpen: true, + }; + onChangeTime = (absoluteRange: AbsoluteTimeRange) => { const { exploreId, updateTimeRange } = this.props; updateTimeRange({ exploreId, absoluteRange }); @@ -94,6 +102,12 @@ export class LogsContainer extends PureComponent { return getFieldLinksForExplore(field, rowIndex, this.props.splitOpen, this.props.range); }; + onToggleCollapse = () => { + this.setState(state => ({ + logsContainerOpen: !state.logsContainerOpen, + })); + }; + render() { const { loading, @@ -116,6 +130,8 @@ export class LogsContainer extends PureComponent { exploreId, } = this.props; + const { logsContainerOpen } = this.state; + return ( <> @@ -135,7 +151,13 @@ export class LogsContainer extends PureComponent { - + { const props: QueryRowProps = { @@ -23,7 +23,6 @@ const setup = (propOverrides?: object) => { removeQueryRowAction: jest.fn() as any, runQueries: jest.fn(), queryResponse: {} as PanelData, - mode: ExploreMode.Metrics, latency: 1, }; @@ -33,34 +32,9 @@ const setup = (propOverrides?: object) => { return wrapper; }; -const ExploreMetricsQueryField = () =>
; -const ExploreLogsQueryField = () =>
; -const ExploreQueryField = () =>
; const QueryEditor = () =>
; describe('QueryRow', () => { - describe('if datasource has all query field components ', () => { - const allComponents = { - ExploreMetricsQueryField, - ExploreLogsQueryField, - ExploreQueryField, - QueryEditor, - }; - - it('it should render ExploreMetricsQueryField in metrics mode', () => { - const wrapper = setup({ mode: ExploreMode.Metrics, datasourceInstance: { components: allComponents } }); - expect(wrapper.find(ExploreMetricsQueryField)).toHaveLength(1); - }); - it('it should render ExploreLogsQueryField in logs mode', () => { - const wrapper = setup({ mode: ExploreMode.Logs, datasourceInstance: { components: allComponents } }); - expect(wrapper.find(ExploreLogsQueryField)).toHaveLength(1); - }); - it('it should render ExploreQueryField in tracing mode', () => { - const wrapper = setup({ mode: ExploreMode.Tracing, datasourceInstance: { components: allComponents } }); - expect(wrapper.find(ExploreQueryField)).toHaveLength(1); - }); - }); - describe('if datasource does not have Explore query fields ', () => { it('it should render QueryEditor if datasource has it', () => { const wrapper = setup({ datasourceInstance: { components: { QueryEditor } } }); diff --git a/public/app/features/explore/QueryRow.tsx b/public/app/features/explore/QueryRow.tsx index 30d4154fd15..2535d006975 100644 --- a/public/app/features/explore/QueryRow.tsx +++ b/public/app/features/explore/QueryRow.tsx @@ -20,7 +20,6 @@ import { TimeRange, AbsoluteTimeRange, LoadingState, - ExploreMode, } from '@grafana/data'; import { ExploreItemState, ExploreId } from 'app/types/explore'; @@ -48,7 +47,6 @@ export interface QueryRowProps extends PropsFromParent { removeQueryRowAction: typeof removeQueryRowAction; runQueries: typeof runQueries; queryResponse: PanelData; - mode: ExploreMode; latency: number; } @@ -102,12 +100,13 @@ export class QueryRow extends PureComponent { }; setReactQueryEditor = () => { - const { mode, datasourceInstance } = this.props; + const { datasourceInstance } = this.props; let QueryEditor; - if (mode === ExploreMode.Metrics && datasourceInstance.components?.ExploreMetricsQueryField) { + // TODO:unification + if (datasourceInstance.components?.ExploreMetricsQueryField) { QueryEditor = datasourceInstance.components.ExploreMetricsQueryField; - } else if (mode === ExploreMode.Logs && datasourceInstance.components?.ExploreLogsQueryField) { + } else if (datasourceInstance.components?.ExploreLogsQueryField) { QueryEditor = datasourceInstance.components.ExploreLogsQueryField; } else if (datasourceInstance.components?.ExploreQueryField) { QueryEditor = datasourceInstance.components.ExploreQueryField; @@ -126,7 +125,6 @@ export class QueryRow extends PureComponent { range, absoluteRange, queryResponse, - mode, exploreId, } = this.props; @@ -145,7 +143,6 @@ export class QueryRow extends PureComponent { onChange={this.onChange} data={queryResponse} absoluteRange={absoluteRange} - exploreMode={mode} exploreId={exploreId} /> ); @@ -174,10 +171,9 @@ export class QueryRow extends PureComponent { }, 500); render() { - const { datasourceInstance, query, queryResponse, mode, latency } = this.props; + const { datasourceInstance, query, queryResponse, latency } = this.props; - const canToggleEditorModes = - mode === ExploreMode.Metrics && has(datasourceInstance, 'components.QueryCtrl.prototype.toggleEditorMode'); + const canToggleEditorModes = has(datasourceInstance, 'components.QueryCtrl.prototype.toggleEditorMode'); const isNotStarted = queryResponse.state === LoadingState.NotStarted; const queryErrors = queryResponse.error && queryResponse.error.refId === query.refId ? [queryResponse.error] : []; @@ -204,7 +200,7 @@ export class QueryRow extends PureComponent { function mapStateToProps(state: StoreState, { exploreId, index }: QueryRowProps) { const explore = state.explore; const item: ExploreItemState = explore[exploreId]; - const { datasourceInstance, history, queries, range, absoluteRange, mode, queryResponse, latency } = item; + const { datasourceInstance, history, queries, range, absoluteRange, queryResponse, latency } = item; const query = queries[index]; return { @@ -214,7 +210,6 @@ function mapStateToProps(state: StoreState, { exploreId, index }: QueryRowProps) range, absoluteRange, queryResponse, - mode, latency, }; } diff --git a/public/app/features/explore/state/actionTypes.ts b/public/app/features/explore/state/actionTypes.ts index 2957c4e1cd8..14f97275bef 100644 --- a/public/app/features/explore/state/actionTypes.ts +++ b/public/app/features/explore/state/actionTypes.ts @@ -13,7 +13,6 @@ import { PanelData, QueryFixAction, TimeRange, - ExploreMode, ExploreUIState, } from '@grafana/data'; import { ExploreId, ExploreItemState } from 'app/types/explore'; @@ -24,11 +23,6 @@ export interface AddQueryRowPayload { query: DataQuery; } -export interface ChangeModePayload { - exploreId: ExploreId; - mode: ExploreMode; -} - export interface ChangeQueryPayload { exploreId: ExploreId; query: DataQuery; @@ -62,7 +56,6 @@ export interface InitializeExplorePayload { eventBridge: Emitter; queries: DataQuery[]; range: TimeRange; - mode: ExploreMode; ui: ExploreUIState; originPanelId?: number | null; } @@ -149,7 +142,6 @@ export interface UpdateDatasourceInstancePayload { exploreId: ExploreId; datasourceInstance: DataSourceApi; version?: string; - mode?: ExploreMode; } export interface ToggleLogLevelPayload { @@ -191,11 +183,6 @@ export interface ResetExplorePayload { */ export const addQueryRowAction = createAction('explore/addQueryRow'); -/** - * Change the mode of Explore. - */ -export const changeModeAction = createAction('explore/changeMode'); - /** * 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.test.ts b/public/app/features/explore/state/actions.test.ts index c8c8d6b1750..6bc4487eda2 100644 --- a/public/app/features/explore/state/actions.test.ts +++ b/public/app/features/explore/state/actions.test.ts @@ -1,27 +1,17 @@ import { PayloadAction } from '@reduxjs/toolkit'; -import { DataQuery, DefaultTimeZone, ExploreMode, LogsDedupStrategy, toUtc, ExploreUrlState } from '@grafana/data'; +import { DataQuery, DefaultTimeZone, LogsDedupStrategy, toUtc, ExploreUrlState } from '@grafana/data'; -import * as Actions from './actions'; -import { - cancelQueries, - changeDatasource, - changeMode, - loadDatasource, - navigateToExplore, - refreshExplore, -} from './actions'; +import { cancelQueries, loadDatasource, navigateToExplore, refreshExplore } from './actions'; import { ExploreId, ExploreUpdateState } from 'app/types'; import { thunkTester } from 'test/core/thunk/thunkTester'; import { cancelQueriesAction, - changeModeAction, initializeExploreAction, InitializeExplorePayload, loadDatasourcePendingAction, loadDatasourceReadyAction, scanStopAction, setQueriesAction, - updateDatasourceInstanceAction, updateUIStateAction, } from './actionTypes'; import { Emitter } from 'app/core/core'; @@ -80,7 +70,6 @@ const setup = (updateOverides?: Partial) => { datasource: 'some-datasource', queries: [], range: range.raw, - mode: ExploreMode.Metrics, ui, }; const updateDefaults = makeInitialUpdateState(); @@ -219,64 +208,6 @@ describe('running queries', () => { }); }); -describe('changing datasource', () => { - it('should switch to logs mode when changing from prometheus to loki', async () => { - const lokiMock = { - testDatasource: () => Promise.resolve({ status: 'success' }), - name: 'Loki', - init: jest.fn(), - meta: { id: 'some id', name: 'Loki' }, - }; - - getDatasourceSrvMock.mockImplementation( - () => - ({ - getExternal: jest.fn().mockReturnValue([]), - get: jest.fn().mockReturnValue(lokiMock), - } as any) - ); - - const exploreId = ExploreId.left; - const name = 'Prometheus'; - const mockPromDatasourceInstance = { - testDatasource: () => Promise.resolve({ status: 'success' }), - name, - init: jest.fn(), - meta: { id: 'some id', name }, - }; - - const initialState = { - explore: { - [exploreId]: { - requestedDatasourceName: 'Loki', - datasourceInstance: mockPromDatasourceInstance, - }, - }, - user: { - orgId: 1, - }, - }; - - jest.spyOn(Actions, 'importQueries').mockImplementationOnce(() => jest.fn); - jest.spyOn(Actions, 'loadDatasource').mockImplementationOnce(() => jest.fn); - const runQueriesAction = jest.spyOn(Actions, 'runQueries').mockImplementationOnce(() => jest.fn); - const dispatchedActions = await thunkTester(initialState) - .givenThunk(changeDatasource) - .whenThunkIsDispatched(exploreId, name); - - expect(dispatchedActions).toEqual([ - updateDatasourceInstanceAction({ - exploreId, - datasourceInstance: lokiMock as any, - version: undefined, - mode: ExploreMode.Logs, - }), - ]); - // Don't run queries just on datasource change - expect(runQueriesAction).toHaveBeenCalledTimes(0); - }); -}); - describe('loading datasource', () => { describe('when loadDatasource thunk is dispatched', () => { describe('and all goes fine', () => { @@ -336,28 +267,6 @@ describe('loading datasource', () => { }); }); -describe('changing mode', () => { - it('should trigger changeModeAction and updateLocation', async () => { - const { exploreId, initialState, range } = setup(); - const dispatchedActions = await thunkTester(initialState) - .givenThunk(changeMode) - .whenThunkIsDispatched(exploreId, ExploreMode.Logs); - const rawTimeRange = Actions.toRawTimeRange(range); - const leftQuery = JSON.stringify([ - rawTimeRange.from, - rawTimeRange.to, - initialState.explore.left.datasourceInstance.name, - {}, - { ui: [false, true, false, null] }, - ]); - - expect(dispatchedActions).toEqual([ - changeModeAction({ exploreId, mode: ExploreMode.Logs }), - updateLocation({ query: { left: leftQuery, orgId: '1' }, replace: false }), - ]); - }); -}); - const getNavigateToExploreContext = async (openInNewWindow?: (url: string) => void) => { const url = 'http://www.someurl.com'; const panel: Partial = { diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index cbb48d776e4..c4dae5ab462 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -16,7 +16,6 @@ import { QueryFixAction, RawTimeRange, TimeRange, - ExploreMode, ExploreUrlState, ExploreUIState, } from '@grafana/data'; @@ -53,7 +52,6 @@ import { ExploreItemState, ThunkResult } from 'app/types'; import { ExploreId, QueryOptions } from 'app/types/explore'; import { addQueryRowAction, - changeModeAction, changeQueryAction, changeRangeAction, changeRefreshIntervalAction, @@ -141,16 +139,11 @@ export function changeDatasource( const orgId = getState().user.orgId; const datasourceVersion = newDataSourceInstance.getVersion && (await newDataSourceInstance.getVersion()); - // HACK: Switch to logs mode if coming from Prometheus to Loki - const prometheusToLoki = - currentDataSourceInstance?.meta?.name === 'Prometheus' && newDataSourceInstance?.meta?.name === 'Loki'; - dispatch( updateDatasourceInstanceAction({ exploreId, datasourceInstance: newDataSourceInstance, version: datasourceVersion, - mode: prometheusToLoki ? ExploreMode.Logs : undefined, }) ); @@ -166,16 +159,6 @@ export function changeDatasource( }; } -/** - * Change the display mode in Explore. - */ -export function changeMode(exploreId: ExploreId, mode: ExploreMode): ThunkResult { - return dispatch => { - dispatch(changeModeAction({ exploreId, mode })); - dispatch(stateSave()); - }; -} - /** * 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. @@ -291,7 +274,6 @@ export function initializeExplore( datasourceName: string, queries: DataQuery[], range: TimeRange, - mode: ExploreMode, containerWidth: number, eventBridge: Emitter, ui: ExploreUIState, @@ -306,7 +288,6 @@ export function initializeExplore( eventBridge, queries, range, - mode, ui, originPanelId, }) @@ -444,7 +425,6 @@ export const runQueries = (exploreId: ExploreId): ThunkResult => { queryResponse, querySubscription, history, - mode, showingGraph, showingTable, } = exploreItemState; @@ -461,11 +441,11 @@ export const runQueries = (exploreId: ExploreId): ThunkResult => { // Some datasource's query builders allow per-query interval limits, // but we're using the datasource interval limit for now - const minInterval = datasourceInstance.interval; + const minInterval = datasourceInstance?.interval; stopQueryState(querySubscription); - const datasourceId = datasourceInstance.meta.id; + const datasourceId = datasourceInstance?.meta.id; const queryOptions: QueryOptions = { minInterval, @@ -473,11 +453,12 @@ export const runQueries = (exploreId: ExploreId): ThunkResult => { // Loki - used for logs streaming for buffer size, with undefined it falls back to datasource config if it supports that. // Elastic - limits the number of datapoints for the counts query and for logs it has hardcoded limit. // Influx - used to correctly display logs in graph - maxDataPoints: mode === ExploreMode.Logs && datasourceId === 'loki' ? undefined : containerWidth, + // TODO:unification + // maxDataPoints: mode === ExploreMode.Logs && datasourceId === 'loki' ? undefined : containerWidth, + maxDataPoints: containerWidth, liveStreaming: live, showingGraph, showingTable, - mode, }; const datasourceName = exploreItemState.requestedDatasourceName; @@ -591,7 +572,6 @@ export const stateSave = (): ThunkResult => { datasource: left.datasourceInstance!.name, queries: left.queries.map(clearQueryKeys), range: toRawTimeRange(left.range), - mode: left.mode, ui: { showingGraph: left.showingGraph, showingLogs: true, @@ -605,7 +585,6 @@ export const stateSave = (): ThunkResult => { datasource: right.datasourceInstance!.name, queries: right.queries.map(clearQueryKeys), range: toRawTimeRange(right.range), - mode: right.mode, ui: { showingGraph: right.showingGraph, showingLogs: true, @@ -837,7 +816,7 @@ export function refreshExplore(exploreId: ExploreId): ThunkResult { return; } - const { datasource, queries, range: urlRange, mode, ui, originPanelId } = urlState; + const { datasource, queries, range: urlRange, ui, originPanelId } = urlState; const refreshQueries: DataQuery[] = []; for (let index = 0; index < queries.length; index++) { @@ -852,17 +831,7 @@ export function refreshExplore(exploreId: ExploreId): ThunkResult { if (update.datasource) { const initialQueries = ensureQueries(queries); dispatch( - initializeExplore( - exploreId, - datasource, - initialQueries, - range, - mode, - containerWidth, - eventBridge, - ui, - originPanelId - ) + initializeExplore(exploreId, datasource, initialQueries, range, containerWidth, eventBridge, ui, originPanelId) ); return; } @@ -881,11 +850,6 @@ export function refreshExplore(exploreId: ExploreId): ThunkResult { dispatch(setQueriesAction({ exploreId, queries: refreshQueries })); } - // need to refresh mode - if (update.mode) { - dispatch(changeModeAction({ exploreId, mode })); - } - // always run queries when refresh is needed if (update.queries || update.ui || update.range) { dispatch(runQueries(exploreId)); diff --git a/public/app/features/explore/state/reducers.test.ts b/public/app/features/explore/state/reducers.test.ts index 75618d0dd20..bf7c9828010 100644 --- a/public/app/features/explore/state/reducers.test.ts +++ b/public/app/features/explore/state/reducers.test.ts @@ -22,7 +22,6 @@ import { import { ExploreId, ExploreItemState, ExploreState } from 'app/types/explore'; import { reducerTester } from 'test/core/redux/reducerTester'; import { - changeModeAction, changeRangeAction, changeRefreshIntervalAction, scanStartAction, @@ -75,21 +74,6 @@ describe('Explore item reducer', () => { }); describe('changing datasource', () => { - describe('when changeMode is dispatched', () => { - it('then it should set correct state', () => { - reducerTester() - .givenReducer(itemReducer, ({} as unknown) as ExploreItemState) - .whenActionIsDispatched(changeModeAction({ exploreId: ExploreId.left, mode: ExploreMode.Logs })) - .thenStatePredicateShouldEqual((resultingState: ExploreItemState) => { - expect(resultingState.mode).toEqual(ExploreMode.Logs); - expect(resultingState.logsResult).toBeNull(); - expect(resultingState.graphResult).toBeNull(); - expect(resultingState.tableResult).toBeNull(); - return true; - }); - }); - }); - describe('when updateDatasourceInstanceAction is dispatched', () => { describe('and datasourceInstance supports graph, logs, table and has a startpage', () => { it('then it should set correct state', () => { @@ -118,7 +102,6 @@ describe('Explore item reducer', () => { logsResult: null, tableResult: null, supportedModes: [ExploreMode.Metrics, ExploreMode.Logs], - mode: ExploreMode.Metrics, latency: 0, loading: false, queryResponse: createEmptyQueryResponse(), @@ -185,7 +168,7 @@ describe('Explore item reducer', () => { .whenActionIsDispatched(toggleGraphAction({ exploreId: ExploreId.left })) .thenStateShouldEqual(({ showingGraph: true, graphResult: [] } as unknown) as ExploreItemState) .whenActionIsDispatched(toggleGraphAction({ exploreId: ExploreId.left })) - .thenStateShouldEqual(({ showingGraph: false, graphResult: null } as unknown) as ExploreItemState); + .thenStateShouldEqual(({ showingGraph: false, graphResult: [] } as unknown) as ExploreItemState); }); }); @@ -207,7 +190,7 @@ describe('Explore item reducer', () => { .whenActionIsDispatched(toggleTableAction({ exploreId: ExploreId.left })) .thenStateShouldEqual(({ showingTable: true, tableResult: table } as unknown) as ExploreItemState) .whenActionIsDispatched(toggleTableAction({ exploreId: ExploreId.left })) - .thenStateShouldEqual(({ showingTable: false, tableResult: null } as unknown) as ExploreItemState); + .thenStateShouldEqual(({ showingTable: false, tableResult: table } as unknown) as ExploreItemState); }); }); }); @@ -316,7 +299,6 @@ export const setup = (urlStateOverrides?: any) => { from: '', to: '', }, - mode: ExploreMode.Metrics, ui: { dedupStrategy: LogsDedupStrategy.none, showingGraph: false, diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index df16a403edc..6e7ac870b1d 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -30,7 +30,6 @@ import { ExploreId, ExploreItemState, ExploreState, ExploreUpdateState } from 'a import { addQueryRowAction, changeLoadingStateAction, - changeModeAction, changeQueryAction, changeRangeAction, changeRefreshIntervalAction, @@ -114,7 +113,6 @@ export const makeExploreItemState = (): ExploreItemState => ({ update: makeInitialUpdateState(), latency: 0, supportedModes: [], - mode: (null as unknown) as ExploreMode, isLive: false, isPaused: false, urlReplaced: false, @@ -189,18 +187,6 @@ export const itemReducer = (state: ExploreItemState = makeExploreItemState(), ac return { ...state, containerWidth }; } - if (changeModeAction.match(action)) { - return { - ...state, - mode: action.payload.mode, - graphResult: null, - tableResult: null, - logsResult: null, - queryResponse: createEmptyQueryResponse(), - loading: false, - }; - } - if (changeRefreshIntervalAction.match(action)) { const { refreshInterval } = action.payload; const live = RefreshPicker.isLive(refreshInterval); @@ -255,13 +241,12 @@ export const itemReducer = (state: ExploreItemState = makeExploreItemState(), ac } if (initializeExploreAction.match(action)) { - const { containerWidth, eventBridge, queries, range, mode, ui, originPanelId } = action.payload; + const { containerWidth, eventBridge, queries, range, ui, originPanelId } = action.payload; return { ...state, containerWidth, eventBridge, range, - mode, queries, initialized: true, queryKeys: getQueryKeys(queries, state.datasourceInstance), @@ -272,7 +257,7 @@ export const itemReducer = (state: ExploreItemState = makeExploreItemState(), ac } if (updateDatasourceInstanceAction.match(action)) { - const { datasourceInstance, version, mode } = action.payload; + const { datasourceInstance, version } = action.payload; // Custom components stopQueryState(state.querySubscription); @@ -294,7 +279,7 @@ export const itemReducer = (state: ExploreItemState = makeExploreItemState(), ac } const updatedDatasourceInstance = Object.assign(datasourceInstance, { meta: newMetadata }); - const [supportedModes, newMode] = getModesForDatasource(updatedDatasourceInstance, state.mode); + const supportedModes = getModesForDatasource(updatedDatasourceInstance); return { ...state, @@ -307,7 +292,6 @@ export const itemReducer = (state: ExploreItemState = makeExploreItemState(), ac loading: false, queryKeys: [], supportedModes, - mode: mode ?? newMode, originPanelId: state.urlState && state.urlState.originPanelId, }; } @@ -430,7 +414,7 @@ export const itemReducer = (state: ExploreItemState = makeExploreItemState(), ac return { ...state, showingGraph }; } - return { ...state, showingGraph, graphResult: null }; + return { ...state, showingGraph }; } if (toggleTableAction.match(action)) { @@ -439,7 +423,7 @@ export const itemReducer = (state: ExploreItemState = makeExploreItemState(), ac return { ...state, showingTable }; } - return { ...state, showingTable, tableResult: null }; + return { ...state, showingTable }; } if (queriesImportedAction.match(action)) { @@ -570,6 +554,10 @@ export const processQueryResponse = ( logsResult, loading: loadingState === LoadingState.Loading || loadingState === LoadingState.Streaming, update: makeInitialUpdateState(), + showLogs: !!logsResult, + showMetrics: !!graphResult, + showTable: !!tableResult, + showTrace: !!processor.traceFrames.length, }; }; @@ -601,7 +589,6 @@ export const updateChildRefreshState = ( const datasource = _.isEqual(urlState ? urlState.datasource : '', state.urlState.datasource) === false; const queries = _.isEqual(urlState ? urlState.queries : [], state.urlState.queries) === false; const range = _.isEqual(urlState ? urlState.range : DEFAULT_RANGE, state.urlState.range) === false; - const mode = _.isEqual(urlState ? urlState.mode : ExploreMode.Metrics, state.urlState.mode) === false; const ui = _.isEqual(urlState ? urlState.ui : DEFAULT_UI_STATE, state.urlState.ui) === false; return { @@ -612,18 +599,16 @@ export const updateChildRefreshState = ( datasource, queries, range, - mode, ui, }, }; }; -const getModesForDatasource = (dataSource: DataSourceApi, currentMode: ExploreMode): [ExploreMode[], ExploreMode] => { +const getModesForDatasource = (dataSource: DataSourceApi): ExploreMode[] => { const supportsGraph = dataSource.meta.metrics; const supportsLogs = dataSource.meta.logs; const supportsTracing = dataSource.meta.tracing; - let mode = currentMode || ExploreMode.Metrics; const supportedModes: ExploreMode[] = []; if (supportsGraph) { @@ -638,17 +623,7 @@ const getModesForDatasource = (dataSource: DataSourceApi, currentMode: ExploreMo supportedModes.push(ExploreMode.Tracing); } - if (supportedModes.length === 1) { - mode = supportedModes[0]; - } - - // HACK: Used to set Loki's default explore mode to Logs mode. - // A better solution would be to introduce a "default" or "preferred" mode to the datasource config - if (dataSource.meta.name === 'Loki' && (!currentMode || supportedModes.indexOf(currentMode) === -1)) { - mode = ExploreMode.Logs; - } - - return [supportedModes, mode]; + return supportedModes; }; /** diff --git a/public/app/features/explore/utils/ResultProcessor.test.ts b/public/app/features/explore/utils/ResultProcessor.test.ts index d2e778133bc..17e3fff6c98 100644 --- a/public/app/features/explore/utils/ResultProcessor.test.ts +++ b/public/app/features/explore/utils/ResultProcessor.test.ts @@ -6,7 +6,7 @@ jest.mock('@grafana/data/src/datetime/formatter', () => ({ import { ResultProcessor } from './ResultProcessor'; import { ExploreItemState } from 'app/types/explore'; import TableModel from 'app/core/table_model'; -import { ExploreMode, FieldType, LogRowModel, TimeSeries, toDataFrame } from '@grafana/data'; +import { FieldType, LogRowModel, TimeSeries, toDataFrame, ArrayVector } from '@grafana/data'; const testContext = (options: any = {}) => { const timeSeries = toDataFrame({ @@ -34,9 +34,20 @@ const testContext = (options: any = {}) => { const emptyTable = toDataFrame({ name: 'empty-table', refId: 'A', fields: [] }); + const logs = toDataFrame({ + name: 'logs-res', + refId: 'A', + fields: [ + { name: 'value', type: FieldType.number, values: [4, 5, 6] }, + { name: 'time', type: FieldType.time, values: [100, 100, 100] }, + { name: 'tsNs', type: FieldType.time, values: ['100000002', undefined, '100000001'] }, + { name: 'message', type: FieldType.string, values: ['this is a message', 'second message', 'third'] }, + ], + meta: { preferredVisualisationType: 'logs' }, + }); + const defaultOptions = { - mode: ExploreMode.Metrics, - dataFrames: [timeSeries, table, emptyTable], + dataFrames: [timeSeries, table, emptyTable, logs], graphResult: [] as TimeSeries[], tableResult: new TableModel(), logsResult: { hasUniqueLabels: false, rows: [] as LogRowModel[] }, @@ -45,7 +56,6 @@ const testContext = (options: any = {}) => { const combinedOptions = { ...defaultOptions, ...options }; const state = ({ - mode: combinedOptions.mode, graphResult: combinedOptions.graphResult, tableResult: combinedOptions.tableResult, logsResult: combinedOptions.logsResult, @@ -191,10 +201,9 @@ describe('ResultProcessor', () => { describe('when calling getLogsResult', () => { it('then it should return correct logs result', () => { - const { resultProcessor, dataFrames } = testContext({ mode: ExploreMode.Logs }); - const timeField = dataFrames[0].fields[0]; - const valueField = dataFrames[0].fields[1]; - const logsDataFrame = dataFrames[1]; + const { resultProcessor, dataFrames } = testContext({}); + const logsDataFrame = dataFrames[3]; + const theResult = resultProcessor.getLogsResult(); expect(theResult).toEqual({ @@ -258,24 +267,37 @@ describe('ResultProcessor', () => { ], series: [ { - label: 'A-series', - color: '#7EB26D', - data: [ - [100, 4], - [200, 5], - [300, 6], - ], - info: [], + label: 'unknown', + color: '#8e8e8e', + data: [[0, 3]], isVisible: true, yAxis: { index: 1, + min: 0, + tickDecimals: 0, }, seriesIndex: 0, - timeField, - valueField, - timeStep: 100, + timeField: { + name: 'Time', + type: 'time', + config: { unit: 'time:YYYY-MM-DD HH:mm:ss' }, + values: new ArrayVector([0]), + index: 0, + display: expect.anything(), + }, + valueField: { + name: 'unknown', + type: 'number', + config: { unit: undefined, color: '#8e8e8e' }, + values: new ArrayVector([3]), + labels: undefined, + index: 1, + display: expect.anything(), + }, + timeStep: 0, }, ], + visibleRange: undefined, }); }); }); diff --git a/public/app/features/explore/utils/ResultProcessor.ts b/public/app/features/explore/utils/ResultProcessor.ts index 4bed693db21..9b5158f4c3d 100644 --- a/public/app/features/explore/utils/ResultProcessor.ts +++ b/public/app/features/explore/utils/ResultProcessor.ts @@ -5,7 +5,6 @@ import { FieldType, TimeZone, getDisplayProcessor, - ExploreMode, PreferredVisualisationType, standardTransformers, } from '@grafana/data'; @@ -16,27 +15,51 @@ import { getGraphSeriesModel } from 'app/plugins/panel/graph2/getGraphSeriesMode import { config } from 'app/core/config'; export class ResultProcessor { + graphFrames: DataFrame[] = []; + tableFrames: DataFrame[] = []; + logsFrames: DataFrame[] = []; + traceFrames: DataFrame[] = []; + constructor( private state: ExploreItemState, private dataFrames: DataFrame[], private intervalMs: number, private timeZone: TimeZone - ) {} + ) { + this.classifyFrames(); + } + + private classifyFrames() { + for (const frame of this.dataFrames) { + if (shouldShowInVisualisationTypeStrict(frame, 'logs')) { + this.logsFrames.push(frame); + } else if (shouldShowInVisualisationTypeStrict(frame, 'graph')) { + this.graphFrames.push(frame); + } else if (shouldShowInVisualisationTypeStrict(frame, 'trace')) { + this.traceFrames.push(frame); + } else if (shouldShowInVisualisationTypeStrict(frame, 'table')) { + this.tableFrames.push(frame); + } else if (isTimeSeries(frame, this.state.datasourceInstance?.meta.id)) { + if (shouldShowInVisualisationType(frame, 'graph')) { + this.graphFrames.push(frame); + } + if (shouldShowInVisualisationType(frame, 'table')) { + this.tableFrames.push(frame); + } + } else { + // We fallback to table if we do not have any better meta info about the dataframe. + this.tableFrames.push(frame); + } + } + } getGraphResult(): GraphSeriesXY[] | null { - if (this.state.mode !== ExploreMode.Metrics) { - return null; - } - - const onlyTimeSeries = this.dataFrames.filter(frame => isTimeSeries(frame, this.state.datasourceInstance?.meta.id)); - const timeSeriesToShowInGraph = onlyTimeSeries.filter(frame => shouldShowInVisualisationType(frame, 'graph')); - - if (timeSeriesToShowInGraph.length === 0) { + if (this.graphFrames.length === 0) { return null; } return getGraphSeriesModel( - timeSeriesToShowInGraph, + this.graphFrames, this.timeZone, {}, { showBars: false, showLines: true, showPoints: false }, @@ -45,30 +68,24 @@ export class ResultProcessor { } getTableResult(): DataFrame | null { - if (this.state.mode !== ExploreMode.Metrics) { + if (this.tableFrames.length === 0) { return null; } - const onlyTables = this.dataFrames - .filter((frame: DataFrame) => shouldShowInVisualisationType(frame, 'table')) - .sort((frameA: DataFrame, frameB: DataFrame) => { - const frameARefId = frameA.refId!; - const frameBRefId = frameB.refId!; + this.tableFrames.sort((frameA: DataFrame, frameB: DataFrame) => { + const frameARefId = frameA.refId!; + const frameBRefId = frameB.refId!; - if (frameARefId > frameBRefId) { - return 1; - } - if (frameARefId < frameBRefId) { - return -1; - } - return 0; - }); + if (frameARefId > frameBRefId) { + return 1; + } + if (frameARefId < frameBRefId) { + return -1; + } + return 0; + }); - if (onlyTables.length === 0) { - return null; - } - - const hasOnlyTimeseries = onlyTables.every(df => isTimeSeries(df)); + const hasOnlyTimeseries = this.tableFrames.every(df => isTimeSeries(df)); // If we have only timeseries we do join on default time column which makes more sense. If we are showing // non timeseries or some mix of data we are not trying to join on anything and just try to merge them in @@ -77,7 +94,7 @@ export class ResultProcessor { ? standardTransformers.seriesToColumnsTransformer.transformer({}) : standardTransformers.mergeTransformer.transformer({}); - const data = transformer(onlyTables)[0]; + const data = transformer(this.tableFrames)[0]; // set display processor for (const field of data.fields) { @@ -92,11 +109,11 @@ export class ResultProcessor { } getLogsResult(): LogsModel | null { - if (this.state.mode !== ExploreMode.Logs) { + if (this.logsFrames.length === 0) { return null; } - const newResults = dataFrameToLogsModel(this.dataFrames, this.intervalMs, this.timeZone, this.state.absoluteRange); + const newResults = dataFrameToLogsModel(this.logsFrames, this.intervalMs, this.timeZone, this.state.absoluteRange); const sortOrder = refreshIntervalToSortOrder(this.state.refreshInterval); const sortedNewResults = sortLogsResult(newResults, sortOrder); const rows = sortedNewResults.rows; @@ -128,6 +145,10 @@ function shouldShowInVisualisationType(frame: DataFrame, visualisation: Preferre return true; } +function shouldShowInVisualisationTypeStrict(frame: DataFrame, visualisation: PreferredVisualisationType) { + return frame.meta?.preferredVisualisationType === visualisation; +} + // TEMP: Temporary hack. Remove when logs/metrics unification is done function isTimeSeriesCloudWatch(frame: DataFrame): boolean { return ( diff --git a/public/app/features/explore/utils/links.test.ts b/public/app/features/explore/utils/links.test.ts index c31bd789e6e..9bdca463b97 100644 --- a/public/app/features/explore/utils/links.test.ts +++ b/public/app/features/explore/utils/links.test.ts @@ -49,7 +49,7 @@ describe('getFieldLinksForExplore', () => { const links = getFieldLinksForExplore(field, 0, splitfn, range); expect(links[0].href).toBe( - '/explore?left={"range":{"from":"now-1h","to":"now"},"datasource":"test_ds","queries":[{"query":"query_1"}],"mode":"Metrics","ui":{"showingGraph":true,"showingTable":true,"showingLogs":true}}' + '/explore?left={"range":{"from":"now-1h","to":"now"},"datasource":"test_ds","queries":[{"query":"query_1"}],"ui":{"showingGraph":true,"showingTable":true,"showingLogs":true}}' ); expect(links[0].title).toBe('test_ds'); diff --git a/public/app/plugins/datasource/cloudwatch/components/LogsCheatSheet.tsx b/public/app/plugins/datasource/cloudwatch/components/LogsCheatSheet.tsx index 0f6e6231961..2b331ef05e9 100644 --- a/public/app/plugins/datasource/cloudwatch/components/LogsCheatSheet.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/LogsCheatSheet.tsx @@ -1,13 +1,11 @@ import React, { PureComponent } from 'react'; import { stripIndent, stripIndents } from 'common-tags'; -import { ExploreStartPageProps, ExploreMode } from '@grafana/data'; +import { ExploreStartPageProps } from '@grafana/data'; import Prism from 'prismjs'; import tokenizer from '../syntax'; import { flattenTokens } from '@grafana/ui/src/slate-plugins/slate-prism'; import { css, cx } from 'emotion'; import { CloudWatchLogsQuery } from '../types'; -import { changeModeAction } from 'app/features/explore/state/actionTypes'; -import { dispatch } from 'app/store/store'; interface QueryExample { category: string; @@ -217,19 +215,8 @@ const exampleCategory = css` `; export default class LogsCheatSheet extends PureComponent { - switchToMetrics = (query: CloudWatchLogsQuery) => { - const { onClickExample, exploreId } = this.props; - - dispatch(changeModeAction({ exploreId, mode: ExploreMode.Metrics })); - onClickExample(query); - }; - onClickExample(query: CloudWatchLogsQuery) { - if (query.expression?.includes('stats')) { - this.switchToMetrics(query); - } else { - this.props.onClickExample(query); - } + this.props.onClickExample(query); } renderExpression(expr: string, keyPrefix: string) { diff --git a/public/app/plugins/datasource/cloudwatch/components/LogsQueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/LogsQueryEditor.tsx index 961115e16f1..2c8bf2b4687 100644 --- a/public/app/plugins/datasource/cloudwatch/components/LogsQueryEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/LogsQueryEditor.tsx @@ -20,7 +20,7 @@ const labelClass = css` `; export const CloudWatchLogsQueryEditor = memo(function CloudWatchLogsQueryEditor(props: Props) { - const { query, data, datasource, onRunQuery, onChange, exploreId, exploreMode, allowCustomValue = false } = props; + const { query, data, datasource, onRunQuery, onChange, exploreId, allowCustomValue = false } = props; let absolute: AbsoluteTimeRange; if (data?.request?.range?.from) { @@ -44,7 +44,6 @@ export const CloudWatchLogsQueryEditor = memo(function CloudWatchLogsQueryEditor return ( {}} diff --git a/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx b/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx index 9241759f0b5..eb818020f50 100644 --- a/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx @@ -14,23 +14,20 @@ import { Select, MultiSelect, } from '@grafana/ui'; -import Plain from 'slate-plain-serializer'; // Utils & Services // dom also includes Element polyfills -import { Plugin, Node, Editor, Value } from 'slate'; +import { Plugin, Node, Editor } from 'slate'; import syntax from '../syntax'; // Types -import { ExploreQueryFieldProps, AbsoluteTimeRange, SelectableValue, ExploreMode, AppEvents } from '@grafana/data'; +import { ExploreQueryFieldProps, AbsoluteTimeRange, SelectableValue, AppEvents } from '@grafana/data'; import { CloudWatchQuery, CloudWatchLogsQuery } from '../types'; import { CloudWatchDatasource } from '../datasource'; import Prism, { Grammar } from 'prismjs'; import { CloudWatchLanguageProvider } from '../language_provider'; import { css } from 'emotion'; import { ExploreId } from 'app/types'; -import { dispatch } from 'app/store/store'; -import { changeModeAction } from 'app/features/explore/state/actionTypes'; import { appEvents } from 'app/core/core'; import { InputActionMeta } from '@grafana/ui/src/components/Select/types'; import { getStatsGroups } from '../utils/query/getStatsGroups'; @@ -274,11 +271,6 @@ export class CloudWatchLogsQueryField extends React.PureComponent { - const { exploreId } = this.props; - dispatch(changeModeAction({ exploreId, mode: ExploreMode.Metrics })); - }; - onQueryFieldClick = (_event: Event, _editor: Editor, next: () => any) => { const { selectedLogGroups, loadingLogGroups } = this.state; @@ -299,34 +291,6 @@ export class CloudWatchLogsQueryField extends React.PureComponent { - const { datasource } = this.props; - // TEMP: Remove when logs/metrics unification is complete - if (datasource.languageProvider && this.props.exploreMode === ExploreMode.Logs) { - const cloudwatchLanguageProvider = datasource.languageProvider as CloudWatchLanguageProvider; - const queryUsesStatsCommand = cloudwatchLanguageProvider.isStatsQuery(Plain.serialize(value)); - if (queryUsesStatsCommand) { - this.setState({ - hint: { - message: 'You are trying to run a stats query in Logs mode. ', - fix: { - label: 'Switch to Metrics mode.', - action: this.switchToMetrics, - }, - }, - }); - } else { - this.setState({ - hint: undefined, - }); - } - } - }, 250); - render() { const { ExtraFieldElement, data, query, syntaxLoaded, datasource, allowCustomValue } = this.props; const { @@ -411,7 +375,6 @@ export class CloudWatchLogsQueryField extends React.PureComponent
{ExtraFieldElement} diff --git a/public/app/plugins/datasource/cloudwatch/datasource.ts b/public/app/plugins/datasource/cloudwatch/datasource.ts index f976c12a54f..9266758769f 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.ts @@ -79,7 +79,7 @@ export class CloudWatchDatasource extends DataSourceApi void; debouncedCustomAlert: (title: string, message: string) => void; - logQueries: Record; + logQueries: Record; languageProvider: CloudWatchLanguageProvider; /** @ngInject */ @@ -228,7 +228,11 @@ export class CloudWatchDatasource extends DataSourceApi { this.logQueries = {}; queryParams.forEach(param => { - this.logQueries[param.refId] = { id: param.queryId, region: param.region }; + this.logQueries[param.refId] = { + id: param.queryId, + region: param.region, + statsQuery: param.statsGroups?.length > 0 ?? false, + }; }); let prevRecordsMatched: Record = {}; diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts index b28ff2efa73..b44133ccbe0 100644 --- a/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts +++ b/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts @@ -188,7 +188,12 @@ describe('CloudWatchDatasource', () => { const expectedData = [ { ...fakeFrames[MAX_ATTEMPTS - 1], - meta: { custom: { ...fakeFrames[MAX_ATTEMPTS - 1].meta!.custom, Status: 'Complete' } }, + meta: { + custom: { + ...fakeFrames[MAX_ATTEMPTS - 1].meta!.custom, + Status: 'Complete', + }, + }, }, ]; expect(myResponse).toEqual({ diff --git a/public/app/plugins/datasource/elasticsearch/datasource.test.ts b/public/app/plugins/datasource/elasticsearch/datasource.test.ts index d15793c2114..742e4b1e41d 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.test.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.test.ts @@ -233,8 +233,8 @@ describe('ElasticDatasource', function(this: any) { }, ], }); - // 1 for logs and 1 for counts. - expect(response.data.length).toBe(2); + + expect(response.data.length).toBe(1); const links = response.data[0].fields.find((field: Field) => field.name === 'host').config.links; expect(links.length).toBe(1); expect(links[0].url).toBe('http://localhost:3000/${__value.raw}'); @@ -885,13 +885,13 @@ describe('enhanceDataFrame', () => { }, ]); - expect(df.fields[0].config.links.length).toBe(1); - expect(df.fields[0].config.links[0]).toEqual({ + expect(df.fields[0].config.links?.length).toBe(1); + expect(df.fields[0].config.links?.[0]).toEqual({ title: '', url: 'someUrl', }); - expect(df.fields[1].config.links.length).toBe(1); - expect(df.fields[1].config.links[0]).toEqual({ + expect(df.fields[1].config.links?.length).toBe(1); + expect(df.fields[1].config.links?.[0]).toEqual({ title: '', url: '', internal: { diff --git a/public/app/plugins/datasource/elasticsearch/datasource.ts b/public/app/plugins/datasource/elasticsearch/datasource.ts index fec5cd700e9..1e6b53ce25a 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.ts @@ -365,7 +365,7 @@ export class ElasticDatasource extends DataSourceApi 0) { - const series = createEmptyDataFrame(propNames, this.targets[0].timeField, logMessageField, logLevelField); + let series = createEmptyDataFrame(propNames, this.targets[0].timeField, logMessageField, logLevelField); // Add a row for each document for (const doc of docs) { @@ -443,6 +450,7 @@ export class ElasticResponse { series.add(doc); } + series = addPreferredVisualisationType(series, 'logs'); dataFrame.push(series); } @@ -578,7 +586,7 @@ const createEmptyDataFrame = ( return series; }; -const addPreferredVisualisationType = (series: any, type: string) => { +const addPreferredVisualisationType = (series: any, type: PreferredVisualisationType) => { let s = series; s.meta ? (s.meta.preferredVisualisationType = type) diff --git a/public/app/plugins/datasource/elasticsearch/module.ts b/public/app/plugins/datasource/elasticsearch/module.ts index 2e8c7452a9b..b51eed2df22 100644 --- a/public/app/plugins/datasource/elasticsearch/module.ts +++ b/public/app/plugins/datasource/elasticsearch/module.ts @@ -1,7 +1,6 @@ import { DataSourcePlugin } from '@grafana/data'; import { ElasticDatasource } from './datasource'; import { ElasticQueryCtrl } from './query_ctrl'; -import ElasticsearchQueryField from './components/ElasticsearchQueryField'; import { ConfigEditor } from './configuration/ConfigEditor'; class ElasticAnnotationsQueryCtrl { @@ -11,5 +10,4 @@ class ElasticAnnotationsQueryCtrl { export const plugin = new DataSourcePlugin(ElasticDatasource) .setQueryCtrl(ElasticQueryCtrl) .setConfigEditor(ConfigEditor) - .setExploreLogsQueryField(ElasticsearchQueryField) .setAnnotationQueryCtrl(ElasticAnnotationsQueryCtrl); diff --git a/public/app/plugins/datasource/elasticsearch/query_builder.ts b/public/app/plugins/datasource/elasticsearch/query_builder.ts index f26454f0390..fa61085685b 100644 --- a/public/app/plugins/datasource/elasticsearch/query_builder.ts +++ b/public/app/plugins/datasource/elasticsearch/query_builder.ts @@ -221,7 +221,7 @@ export class ElasticQueryBuilder { * Check if metric type is raw_document. If metric doesn't have size (or size is 0), update size to 500. * Otherwise it will not be a valid query and error will be thrown. */ - if (target.metrics[0].type === 'raw_document') { + if (target.metrics?.[0]?.type === 'raw_document') { metric = target.metrics[0]; const size = (metric.settings && metric.settings.size !== 0 && metric.settings.size) || 500; return this.documentQuery(query, size); diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 1d0c3a38756..3c1b225de8e 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -149,6 +149,8 @@ export default class InfluxDatasource extends DataSourceWithBackend { values: response?.data?.data || [], }, ], + meta: { + preferredVisualisationType: 'trace', + }, }), ], }; @@ -64,6 +67,9 @@ export class JaegerDatasource extends DataSourceApi { values: [], }, ], + meta: { + preferredVisualisationType: 'trace', + }, }), ], }); diff --git a/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx b/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx index 7957eb221ae..56812dc719f 100644 --- a/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx +++ b/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import { shuffle } from 'lodash'; -import { ExploreStartPageProps, DataQuery, ExploreMode } from '@grafana/data'; +import { ExploreStartPageProps, DataQuery } from '@grafana/data'; import LokiLanguageProvider from '../language_provider'; const DEFAULT_EXAMPLES = ['{job="default/prometheus"}']; @@ -46,7 +46,7 @@ export default class LokiCheatSheet extends PureComponent { // Set example from user labels - const provider: LokiLanguageProvider = this.props.datasource.languageProvider; + const provider: LokiLanguageProvider = this.props.datasource?.languageProvider; if (provider.started) { const labels = provider.getLabelKeys() || []; const preferredLabel = PREFERRED_LABELS.find(l => labels.includes(l)); @@ -76,11 +76,11 @@ export default class LokiCheatSheet extends PureComponent +

Loki Cheat Sheet

See your logs
@@ -114,14 +114,6 @@ export default class LokiCheatSheet extends PureComponent
- - ); - } - - renderMetricsCheatSheet() { - return ( -
-

LogQL Cheat Sheet

{LOGQL_EXAMPLES.map(item => (
{item.title}
@@ -132,10 +124,4 @@ export default class LokiCheatSheet extends PureComponent ); } - - render() { - const { exploreMode } = this.props; - - return exploreMode === ExploreMode.Logs ? this.renderLogsCheatSheet() : this.renderMetricsCheatSheet(); - } } diff --git a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx index 76963051550..0da84ca0e7e 100644 --- a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx +++ b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx @@ -87,12 +87,4 @@ describe('LokiExploreQueryEditor', () => { expect(wrapper.find(LokiExploreExtraField).length).toBe(1); }); }); - - it('should render LokiQueryField with no ExtraFieldElement when ExploreMode is not Logs', async () => { - // @ts-ignore strict null error TS2345: Argument of type '() => Promise' is not assignable to parameter of type '() => void | undefined'. - await act(async () => { - const wrapper = setup(mount, { exploreMode: ExploreMode.Metrics }); - expect(wrapper.find(LokiExploreExtraField).length).toBe(0); - }); - }); }); diff --git a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx index 7dafc37b38d..88c3b791411 100644 --- a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx +++ b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx @@ -3,7 +3,7 @@ import React, { memo } from 'react'; import _ from 'lodash'; // Types -import { AbsoluteTimeRange, ExploreQueryFieldProps, ExploreMode } from '@grafana/data'; +import { AbsoluteTimeRange, ExploreQueryFieldProps } from '@grafana/data'; import { LokiDatasource } from '../datasource'; import { LokiQuery, LokiOptions } from '../types'; import { LokiQueryField } from './LokiQueryField'; @@ -12,7 +12,7 @@ import LokiExploreExtraField from './LokiExploreExtraField'; type Props = ExploreQueryFieldProps; export function LokiExploreQueryEditor(props: Props) { - const { query, data, datasource, exploreMode, history, onChange, onRunQuery } = props; + const { query, data, datasource, history, onChange, onRunQuery } = props; let absolute: AbsoluteTimeRange; if (data && data.request) { @@ -72,16 +72,14 @@ export function LokiExploreQueryEditor(props: Props) { data={data} absoluteRange={absolute} ExtraFieldElement={ - exploreMode === ExploreMode.Logs ? ( - - ) : null + } /> ); diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index 66848c60a22..d265d13ce8f 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -1,15 +1,7 @@ import LokiDatasource, { RangeQueryOptions } from './datasource'; import { LokiQuery, LokiResponse, LokiResultType } from './types'; import { getQueryOptions } from 'test/helpers/getQueryOptions'; -import { - AnnotationQueryRequest, - DataFrame, - DataSourceApi, - dateTime, - ExploreMode, - FieldCache, - TimeRange, -} from '@grafana/data'; +import { AnnotationQueryRequest, DataFrame, DataSourceApi, dateTime, FieldCache, TimeRange } from '@grafana/data'; import { TemplateSrv } from 'app/features/templating/template_srv'; import { makeMockLokiDatasource } from './mocks'; import { of } from 'rxjs'; @@ -19,7 +11,7 @@ import { CustomVariableModel } from '../../../features/variables/types'; import { initialCustomVariableModelState } from '../../../features/variables/custom/reducer'; // will use the version in __mocks__ jest.mock('@grafana/runtime', () => ({ - ...((jest.requireActual('@grafana/runtime') as unknown) as object), + ...jest.requireActual('@grafana/runtime'), getBackendSrv: () => backendSrv, })); @@ -110,24 +102,9 @@ describe('LokiDatasource', () => { datasourceRequestMock.mockImplementation(() => Promise.resolve(testResp)); }); - test('should run instant query and range query when in metrics mode', async () => { - const options = getQueryOptions({ - targets: [{ expr: 'rate({job="grafana"}[5m])', refId: 'A' }], - exploreMode: ExploreMode.Metrics, - }); - - ds.runInstantQuery = jest.fn(() => of({ data: [] })); - ds.runRangeQuery = jest.fn(() => of({ data: [] })); - await ds.query(options).toPromise(); - - expect(ds.runInstantQuery).toBeCalled(); - expect(ds.runRangeQuery).toBeCalled(); - }); - test('should just run range query when in logs mode', async () => { const options = getQueryOptions({ targets: [{ expr: '{job="grafana"}', refId: 'B' }], - exploreMode: ExploreMode.Logs, }); ds.runInstantQuery = jest.fn(() => of({ data: [] })); diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index 245e921fd6c..0f4132c09f8 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -19,7 +19,6 @@ import { LoadingState, AnnotationEvent, DataFrameView, - TimeSeries, PluginMeta, DataSourceApi, DataSourceInstanceSettings, @@ -27,7 +26,6 @@ import { DataQueryRequest, DataQueryResponse, AnnotationQueryRequest, - ExploreMode, ScopedVars, } from '@grafana/data'; @@ -92,30 +90,7 @@ export class LokiDatasource extends DataSourceApi { expr: this.templateSrv.replace(target.expr, options.scopedVars, this.interpolateQueryExpr), })); - if (options.exploreMode === ExploreMode.Metrics) { - filteredTargets.forEach(target => - subQueries.push( - this.runInstantQuery(target, options, filteredTargets.length), - this.runRangeQuery(target, options, filteredTargets.length) - ) - ); - } else { - filteredTargets.forEach(target => - subQueries.push( - this.runRangeQuery(target, options, filteredTargets.length).pipe( - map(dataQueryResponse => { - if (options.exploreMode === ExploreMode.Logs && dataQueryResponse.data.find(d => isTimeSeries(d))) { - throw new Error( - 'Logs mode does not support queries that return time series data. Please perform a logs query or switch to Metrics mode.' - ); - } else { - return dataQueryResponse; - } - }) - ) - ) - ); - } + filteredTargets.forEach(target => subQueries.push(this.runRangeQuery(target, options, filteredTargets.length))); // No valid targets, return the empty result to save a round trip. if (isEmpty(subQueries)) { @@ -149,7 +124,10 @@ export class LokiDatasource extends DataSourceApi { filter((response: any) => (response.cancelled ? false : true)), map((response: { data: LokiResponse }) => { if (response.data.data.resultType === LokiResultType.Stream) { - throw new Error('Metrics mode does not support logs. Use an aggregation or switch to Logs mode.'); + return { + data: [], + key: `${target.refId}_instant`, + }; } return { @@ -582,7 +560,3 @@ export function lokiSpecialRegexEscape(value: any) { } export default LokiDatasource; - -function isTimeSeries(data: any): data is TimeSeries { - return data.hasOwnProperty('datapoints'); -} diff --git a/public/app/plugins/datasource/loki/live_streams.ts b/public/app/plugins/datasource/loki/live_streams.ts index ed5f79bc6c6..4e5bbfcff62 100644 --- a/public/app/plugins/datasource/loki/live_streams.ts +++ b/public/app/plugins/datasource/loki/live_streams.ts @@ -1,8 +1,8 @@ import { DataFrame, FieldType, parseLabels, KeyValue, CircularDataFrame } from '@grafana/data'; -import { Observable } from 'rxjs'; +import { Observable, throwError } from 'rxjs'; import { webSocket } from 'rxjs/webSocket'; import { LokiTailResponse } from './types'; -import { finalize, map } from 'rxjs/operators'; +import { finalize, map, catchError } from 'rxjs/operators'; import { appendResponseToBufferedData } from './result_transformer'; /** @@ -35,15 +35,18 @@ export class LiveStreams { data.addField({ name: 'line', type: FieldType.string }).labels = parseLabels(target.query); data.addField({ name: 'labels', type: FieldType.other }); // The labels for each line data.addField({ name: 'id', type: FieldType.string }); + data.meta = { ...data.meta, preferredVisualisationType: 'logs' }; stream = webSocket(target.url).pipe( - finalize(() => { - delete this.streams[target.url]; - }), - map((response: LokiTailResponse) => { appendResponseToBufferedData(response, data); return [data]; + }), + catchError(err => { + return throwError(`error: ${err.reason}`); + }), + finalize(() => { + delete this.streams[target.url]; }) ); this.streams[target.url] = stream; diff --git a/public/app/plugins/datasource/loki/result_transformer.ts b/public/app/plugins/datasource/loki/result_transformer.ts index c448723b42a..9e8ac9711bf 100644 --- a/public/app/plugins/datasource/loki/result_transformer.ts +++ b/public/app/plugins/datasource/loki/result_transformer.ts @@ -323,6 +323,7 @@ export function lokiStreamsToDataframes( limit, stats, custom, + preferredVisualisationType: 'logs', }, }; }); diff --git a/public/app/plugins/datasource/testdata/datasource.ts b/public/app/plugins/datasource/testdata/datasource.ts index 53c3a2ba04f..21bc182ccd8 100644 --- a/public/app/plugins/datasource/testdata/datasource.ts +++ b/public/app/plugins/datasource/testdata/datasource.ts @@ -1,3 +1,5 @@ +import set from 'lodash/set'; + import { ArrayDataFrame, arrowTableToDataFrame, @@ -85,6 +87,11 @@ export class TestDataDataSource extends DataSourceApi { const table = t as TableData; table.refId = query.refId; table.name = query.alias; + + if (query.scenarioId === 'logs') { + set(table, 'meta.preferredVisualisationType', 'logs'); + } + data.push(table); } diff --git a/public/app/plugins/datasource/testdata/runStreams.ts b/public/app/plugins/datasource/testdata/runStreams.ts index db2361f1f50..80f78b7eee5 100644 --- a/public/app/plugins/datasource/testdata/runStreams.ts +++ b/public/app/plugins/datasource/testdata/runStreams.ts @@ -128,8 +128,9 @@ export function runLogsStream( }); data.refId = target.refId; data.name = target.alias || 'Logs ' + target.refId; - data.addField({ name: 'time', type: FieldType.time }); data.addField({ name: 'line', type: FieldType.string }); + data.addField({ name: 'time', type: FieldType.time }); + data.meta = { preferredVisualisationType: 'logs' }; const { speed } = query; diff --git a/public/app/plugins/datasource/zipkin/datasource.ts b/public/app/plugins/datasource/zipkin/datasource.ts index c2b5020731d..b24638aaae5 100644 --- a/public/app/plugins/datasource/zipkin/datasource.ts +++ b/public/app/plugins/datasource/zipkin/datasource.ts @@ -74,6 +74,9 @@ function responseToDataQueryResponse(response: { data: ZipkinSpan[] }): DataQuer values: response?.data ? [transformResponse(response?.data)] : [], }, ], + meta: { + preferredVisualisationType: 'trace', + }, }), ], }; @@ -89,6 +92,9 @@ const emptyDataQueryResponse = { values: [], }, ], + meta: { + preferredVisualisationType: 'trace', + }, }), ], }; diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index a67ff2b114e..ec87d4c6687 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -166,7 +166,6 @@ export interface ExploreItemState { latency: number; supportedModes: ExploreMode[]; - mode: ExploreMode; /** * If true, the view is in live tailing mode. @@ -188,6 +187,11 @@ export interface ExploreItemState { * query of that panel. */ originPanelId?: number | null; + + showLogs?: boolean; + showMetrics?: boolean; + showTable?: boolean; + showTrace?: boolean; } export interface ExploreUpdateState {