diff --git a/pkg/tsdb/influxdb/flux/executor.go b/pkg/tsdb/influxdb/flux/executor.go index 256678e4f99..99c644d5be7 100644 --- a/pkg/tsdb/influxdb/flux/executor.go +++ b/pkg/tsdb/influxdb/flux/executor.go @@ -85,7 +85,9 @@ func readDataFrames(result *api.QueryTableResult, maxPoints int, maxSeries int) } } - // Attach any errors (may be null) - dr.Error = result.Err() + // result.Err() is probably more important then the other errors + if result.Err() != nil { + dr.Error = result.Err() + } return dr } diff --git a/pkg/tsdb/influxdb/flux/flux.go b/pkg/tsdb/influxdb/flux/flux.go index 7e747f93fbf..a7312e2a2b0 100644 --- a/pkg/tsdb/influxdb/flux/flux.go +++ b/pkg/tsdb/influxdb/flux/flux.go @@ -39,7 +39,9 @@ func Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQ continue } - res := executeQuery(ctx, *qm, r, 50) + // If the default changes also update labels/placeholder in config page. + maxSeries := dsInfo.JsonData.Get("maxSeries").MustInt(1000) + res := executeQuery(ctx, *qm, r, maxSeries) tRes.Results[query.RefId] = backendDataResponseToTSDBResponse(&res, query.RefId) } diff --git a/public/app/features/explore/state/query.test.ts b/public/app/features/explore/state/query.test.ts index 268deb32f21..02464c21899 100644 --- a/public/app/features/explore/state/query.test.ts +++ b/public/app/features/explore/state/query.test.ts @@ -4,15 +4,19 @@ import { cancelQueriesAction, queryReducer, removeQueryRowAction, + runQueries, scanStartAction, scanStopAction, } from './query'; import { ExploreId, ExploreItemState } from 'app/types'; -import { interval } from 'rxjs'; -import { RawTimeRange, toUtc } from '@grafana/data'; +import { interval, of } from 'rxjs'; +import { ArrayVector, DataQueryResponse, DefaultTimeZone, MutableDataFrame, RawTimeRange, toUtc } from '@grafana/data'; import { thunkTester } from 'test/core/thunk/thunkTester'; import { makeExplorePaneState } from './utils'; import { reducerTester } from '../../../../test/core/redux/reducerTester'; +import { configureStore } from '../../../store/configureStore'; +import { setTimeSrv } from '../../dashboard/services/TimeSrv'; +import Mock = jest.Mock; const QUERY_KEY_REGEX = /Q-(?:[a-z0-9]+-){5}(?:[0-9]+)/; const t = toUtc(); @@ -24,6 +28,58 @@ const testRange = { to: t, }, }; +const defaultInitialState = { + user: { + orgId: '1', + timeZone: DefaultTimeZone, + }, + explore: { + [ExploreId.left]: { + datasourceInstance: { + query: jest.fn(), + meta: { + id: 'something', + }, + }, + initialized: true, + containerWidth: 1920, + eventBridge: { emit: () => {} } as any, + queries: [{ expr: 'test' }] as any[], + range: testRange, + refreshInterval: { + label: 'Off', + value: 0, + }, + }, + }, +}; + +describe('runQueries', () => { + it('should pass dataFrames to state even if there is error in response', async () => { + setTimeSrv({ + init() {}, + } as any); + const store = configureStore({ + ...(defaultInitialState as any), + }); + (store.getState().explore[ExploreId.left].datasourceInstance?.query as Mock).mockReturnValueOnce( + of({ + error: { message: 'test error' }, + data: [ + new MutableDataFrame({ + fields: [{ name: 'test', values: new ArrayVector() }], + meta: { + preferredVisualisationType: 'graph', + }, + }), + ], + } as DataQueryResponse) + ); + await store.dispatch(runQueries(ExploreId.left)); + expect(store.getState().explore[ExploreId.left].showMetrics).toBeTruthy(); + expect(store.getState().explore[ExploreId.left].graphResult).toBeDefined(); + }); +}); describe('running queries', () => { it('should cancel running query when cancelQueries is dispatched', async () => { diff --git a/public/app/features/explore/state/query.ts b/public/app/features/explore/state/query.ts index 7c365d3f127..dc7f37123f8 100644 --- a/public/app/features/explore/state/query.ts +++ b/public/app/features/explore/state/query.ts @@ -664,16 +664,6 @@ export const processQueryResponse = ( // For Angular editors state.eventBridge.emit(PanelEvents.dataError, error); - - return { - ...state, - loading: loadingState === LoadingState.Loading || loadingState === LoadingState.Streaming, - queryResponse: response, - graphResult: null, - tableResult: null, - logsResult: null, - update: makeInitialUpdateState(), - }; } if (!request) { diff --git a/public/app/features/explore/utils/decorators.test.ts b/public/app/features/explore/utils/decorators.test.ts index 24c17aed6c6..93469e33175 100644 --- a/public/app/features/explore/utils/decorators.test.ts +++ b/public/app/features/explore/utils/decorators.test.ts @@ -132,7 +132,7 @@ describe('decorateWithGraphLogsTraceAndTable', () => { }); }); - it('should handle query error', () => { + it('should return frames even if there is an error', () => { const { timeSeries, logs, table } = getTestContext(); const series: DataFrame[] = [timeSeries, logs, table]; const panelData: PanelData = { @@ -147,9 +147,9 @@ describe('decorateWithGraphLogsTraceAndTable', () => { error: {}, state: LoadingState.Error, timeRange: {}, - graphFrames: [], - tableFrames: [], - logsFrames: [], + graphFrames: [timeSeries], + tableFrames: [table], + logsFrames: [logs], traceFrames: [], nodeGraphFrames: [], graphResult: null, @@ -171,10 +171,10 @@ describe('decorateWithGraphResult', () => { expect(decorateWithGraphResult(panelData).graphResult).toBeNull(); }); - it('returns null if panelData has error', () => { + it('returns data if panelData has error', () => { const { timeSeries } = getTestContext(); const panelData = createExplorePanelData({ error: {}, graphFrames: [timeSeries] }); - expect(decorateWithGraphResult(panelData).graphResult).toBeNull(); + expect(decorateWithGraphResult(panelData).graphResult).toMatchObject([timeSeries]); }); }); @@ -272,11 +272,11 @@ describe('decorateWithTableResult', () => { expect(panelResult.tableResult).toBeNull(); }); - it('returns null if panelData has error', async () => { + it('returns data if panelData has error', async () => { const { table, emptyTable } = getTestContext(); const panelData = createExplorePanelData({ error: {}, tableFrames: [table, emptyTable] }); const panelResult = await decorateWithTableResult(panelData).toPromise(); - expect(panelResult.tableResult).toBeNull(); + expect(panelResult.tableResult).not.toBeNull(); }); }); @@ -386,9 +386,9 @@ describe('decorateWithLogsResult', () => { expect(decorateWithLogsResult()(panelData).logsResult).toBeNull(); }); - it('returns null if panelData has error', () => { + it('returns data if panelData has error', () => { const { logs } = getTestContext(); const panelData = createExplorePanelData({ error: {}, logsFrames: [logs] }); - expect(decorateWithLogsResult()(panelData).logsResult).toBeNull(); + expect(decorateWithLogsResult()(panelData).logsResult).not.toBeNull(); }); }); diff --git a/public/app/features/explore/utils/decorators.ts b/public/app/features/explore/utils/decorators.ts index d81851e7224..a9bae4d5725 100644 --- a/public/app/features/explore/utils/decorators.ts +++ b/public/app/features/explore/utils/decorators.ts @@ -21,20 +21,6 @@ import { ExplorePanelData } from '../../../types'; * Observable pipeline, it decorates the existing panelData to pass the results to later processing stages. */ export const decorateWithFrameTypeMetadata = (data: PanelData): ExplorePanelData => { - if (data.error) { - return { - ...data, - graphFrames: [], - tableFrames: [], - logsFrames: [], - traceFrames: [], - nodeGraphFrames: [], - graphResult: null, - tableResult: null, - logsResult: null, - }; - } - const graphFrames: DataFrame[] = []; const tableFrames: DataFrame[] = []; const logsFrames: DataFrame[] = []; @@ -83,7 +69,7 @@ export const decorateWithFrameTypeMetadata = (data: PanelData): ExplorePanelData }; export const decorateWithGraphResult = (data: ExplorePanelData): ExplorePanelData => { - if (data.error || !data.graphFrames.length) { + if (!data.graphFrames.length) { return { ...data, graphResult: null }; } @@ -96,10 +82,6 @@ export const decorateWithGraphResult = (data: ExplorePanelData): ExplorePanelDat * multiple results and so this should be used with mergeMap or similar to unbox the internal observable. */ export const decorateWithTableResult = (data: ExplorePanelData): Observable => { - if (data.error) { - return of({ ...data, tableResult: null }); - } - if (data.tableFrames.length === 0) { return of({ ...data, tableResult: null }); } @@ -149,10 +131,6 @@ export const decorateWithTableResult = (data: ExplorePanelData): Observable (data: ExplorePanelData): ExplorePanelData => { - if (data.error) { - return { ...data, logsResult: null }; - } - if (data.logsFrames.length === 0) { return { ...data, logsResult: null }; } diff --git a/public/app/plugins/datasource/influxdb/components/ConfigEditor.tsx b/public/app/plugins/datasource/influxdb/components/ConfigEditor.tsx index 748981b49aa..8721dfef83b 100644 --- a/public/app/plugins/datasource/influxdb/components/ConfigEditor.tsx +++ b/public/app/plugins/datasource/influxdb/components/ConfigEditor.tsx @@ -7,8 +7,9 @@ import { onUpdateDatasourceJsonDataOption, onUpdateDatasourceJsonDataOptionSelect, onUpdateDatasourceSecureJsonDataOption, + updateDatasourcePluginJsonDataOption, } from '@grafana/data'; -import { DataSourceHttpSettings, InfoBox, InlineFormLabel, LegacyForms } from '@grafana/ui'; +import { DataSourceHttpSettings, InfoBox, InlineField, InlineFormLabel, LegacyForms } from '@grafana/ui'; const { Select, Input, SecretFormField } = LegacyForms; import { InfluxOptions, InfluxSecureJsonData, InfluxVersion } from '../types'; @@ -31,8 +32,20 @@ const versions = [ ] as Array>; export type Props = DataSourcePluginOptionsEditorProps; +type State = { + maxSeries: string | undefined; +}; + +export class ConfigEditor extends PureComponent { + state = { + maxSeries: '', + }; + + constructor(props: Props) { + super(props); + this.state.maxSeries = props.options.jsonData.maxSeries?.toString() || ''; + } -export class ConfigEditor extends PureComponent { // 1x onResetPassword = () => { updateDatasourcePluginResetOption(this.props, 'password'); @@ -67,33 +80,12 @@ export class ConfigEditor extends PureComponent { }; renderInflux2x() { - const { options, onOptionsChange } = this.props; + const { options } = this.props; const { secureJsonFields } = options; const secureJsonData = (options.secureJsonData || {}) as InfluxSecureJsonData; return ( -
-
- -
Support for flux in Grafana is currently in beta
-

- Please report any issues to:
- - https://github.com/grafana/grafana/issues - -

-
-
-
- - - -

InfluxDB Details

+ <>
Organization @@ -152,125 +144,111 @@ export class ConfigEditor extends PureComponent {
- + ); } renderInflux1x() { - const { options, onOptionsChange } = this.props; + const { options } = this.props; const { secureJsonFields } = options; const secureJsonData = (options.secureJsonData || {}) as InfluxSecureJsonData; return ( -
- - -

InfluxDB Details

-
-
-
- Database -
- -
-
-
-
-
- User -
- -
-
-
-
-
- + +
Database Access
+

+ Setting the database for this datasource does not deny access to other databases. The InfluxDB query syntax + allows switching the database in the query. For example: + SHOW MEASUREMENTS ON _internal or + SELECT * FROM "_internal".."database" LIMIT 10 +
+
+ To support data isolation and security, make sure appropriate permissions are configured in InfluxDB. +

+
+
+
+ Database +
+
-
-
- +
+ User +
+ +
+
+
+
+
+ +
+
+
+
+ - HTTP Method - - httpMode.value === options.jsonData.httpMode)} + options={httpModes} + defaultValue={options.jsonData.httpMode} + onChange={onUpdateDatasourceJsonDataOptionSelect(this.props, 'httpMode')} + /> +
+
+ +
+
+ + Min time interval + +
+ httpMode.value === options.jsonData.httpMode)} - options={httpModes} - defaultValue={options.jsonData.httpMode} - onChange={onUpdateDatasourceJsonDataOptionSelect(this.props, 'httpMode')} + placeholder="10s" + value={options.jsonData.timeInterval || ''} + onChange={onUpdateDatasourceJsonDataOption(this.props, 'timeInterval')} />
- -
- -
Database Access
-

- Setting the database for this datasource does not deny access to other databases. The InfluxDB query - syntax allows switching the database in the query. For example: - SHOW MEASUREMENTS ON _internal or - SELECT * FROM "_internal".."database" LIMIT 10 -
-
- To support data isolation and security, make sure appropriate permissions are configured in InfluxDB. -

-
-
-
-
-
- - Min time interval - -
- -
-
-
-
-
+ ); } render() { - const { options } = this.props; + const { options, onOptionsChange } = this.props; return ( <> @@ -289,7 +267,52 @@ export class ConfigEditor extends PureComponent {
- {options.jsonData.version === InfluxVersion.Flux ? this.renderInflux2x() : this.renderInflux1x()} + {options.jsonData.version === InfluxVersion.Flux && ( + +
Support for Flux in Grafana is currently in beta
+

+ Please report any issues to:
+ + https://github.com/grafana/grafana/issues + +

+
+ )} + + + +
+
+

InfluxDB Details

+
+ {options.jsonData.version === InfluxVersion.Flux ? this.renderInflux2x() : this.renderInflux1x()} +
+ + { + // We duplicate this state so that we allow to write freely inside the input. We don't have + // any influence over saving so this seems to be only way to do this. + this.setState({ maxSeries: event.currentTarget.value }); + const val = parseInt(event.currentTarget.value, 10); + updateDatasourcePluginJsonDataOption(this.props, 'maxSeries', Number.isFinite(val) ? val : undefined); + }} + /> + +
+
); } diff --git a/public/app/plugins/datasource/influxdb/components/__snapshots__/ConfigEditor.test.tsx.snap b/public/app/plugins/datasource/influxdb/components/__snapshots__/ConfigEditor.test.tsx.snap index 40248a90417..19c79f995b5 100644 --- a/public/app/plugins/datasource/influxdb/components/__snapshots__/ConfigEditor.test.tsx.snap +++ b/public/app/plugins/datasource/influxdb/components/__snapshots__/ConfigEditor.test.tsx.snap @@ -71,217 +71,226 @@ exports[`Render should disable basic auth password input 1`] = `
-
- -

- InfluxDB Details -

+ } + defaultUrl="http://localhost:8086" + onChange={[MockFunction]} + showAccessOptions={true} + /> +
+
+

+ InfluxDB Details +

+
+ +
+ Database Access +
+

+ Setting the database for this datasource does not deny access to other databases. The InfluxDB query syntax allows switching the database in the query. For example: + + SHOW MEASUREMENTS ON _internal + + or + + SELECT * FROM "_internal".."database" LIMIT 10 + +
+
+ To support data isolation and security, make sure appropriate permissions are configured in InfluxDB. +

+
-
- - Database - -
+
+ - -
-
-
-
-
- - User - -
- -
-
-
-
-
-
+
+
-
- - HTTP Method - -
- -
- Database Access -
-

- Setting the database for this datasource does not deny access to other databases. The InfluxDB query syntax allows switching the database in the query. For example: - - SHOW MEASUREMENTS ON _internal - - or - - SELECT * FROM "_internal".."database" LIMIT 10 - -
-
- To support data isolation and security, make sure appropriate permissions are configured in InfluxDB. -

-
-
-
-
+
+
+
+
+ - +
+
+
+
+ - Min time interval - -
+ Min time interval + +
+ - -
+ onChange={[Function]} + placeholder="10s" + value="4" + />
+
+ + + +
`; @@ -357,217 +366,226 @@ exports[`Render should hide basic auth fields when switch off 1`] = `
-
- -

- InfluxDB Details -

+ } + defaultUrl="http://localhost:8086" + onChange={[MockFunction]} + showAccessOptions={true} + /> +
+
+

+ InfluxDB Details +

+
+ +
+ Database Access +
+

+ Setting the database for this datasource does not deny access to other databases. The InfluxDB query syntax allows switching the database in the query. For example: + + SHOW MEASUREMENTS ON _internal + + or + + SELECT * FROM "_internal".."database" LIMIT 10 + +
+
+ To support data isolation and security, make sure appropriate permissions are configured in InfluxDB. +

+
-
- - Database - -
+
+ - -
-
-
-
-
- - User - -
- -
-
-
-
-
-
+
+
-
- - HTTP Method - -
- -
- Database Access -
-

- Setting the database for this datasource does not deny access to other databases. The InfluxDB query syntax allows switching the database in the query. For example: - - SHOW MEASUREMENTS ON _internal - - or - - SELECT * FROM "_internal".."database" LIMIT 10 - -
-
- To support data isolation and security, make sure appropriate permissions are configured in InfluxDB. -

-
-
-
-
+
+
+
+
+ - +
+
+
+
+ - Min time interval - -
+ Min time interval + +
+ - -
+ onChange={[Function]} + placeholder="10s" + value="4" + />
+
+ + + +
`; @@ -643,217 +661,226 @@ exports[`Render should hide white listed cookies input when browser access chose
-
- -

- InfluxDB Details -

+ } + defaultUrl="http://localhost:8086" + onChange={[MockFunction]} + showAccessOptions={true} + /> +
+
+

+ InfluxDB Details +

+
+ +
+ Database Access +
+

+ Setting the database for this datasource does not deny access to other databases. The InfluxDB query syntax allows switching the database in the query. For example: + + SHOW MEASUREMENTS ON _internal + + or + + SELECT * FROM "_internal".."database" LIMIT 10 + +
+
+ To support data isolation and security, make sure appropriate permissions are configured in InfluxDB. +

+
-
- - Database - -
+
+ - -
-
-
-
-
- - User - -
- -
-
-
-
-
-
+
+
-
- - HTTP Method - -
- -
- Database Access -
-

- Setting the database for this datasource does not deny access to other databases. The InfluxDB query syntax allows switching the database in the query. For example: - - SHOW MEASUREMENTS ON _internal - - or - - SELECT * FROM "_internal".."database" LIMIT 10 - -
-
- To support data isolation and security, make sure appropriate permissions are configured in InfluxDB. -

-
-
-
-
+
+
+
+
+ - +
+
+
+
+ - Min time interval - -
+ Min time interval + +
+ - -
+ onChange={[Function]} + placeholder="10s" + value="4" + />
+
+ + + +
`; @@ -929,217 +956,226 @@ exports[`Render should render component 1`] = `
-
- -

- InfluxDB Details -

+ } + defaultUrl="http://localhost:8086" + onChange={[MockFunction]} + showAccessOptions={true} + /> +
+
+

+ InfluxDB Details +

+
+ +
+ Database Access +
+

+ Setting the database for this datasource does not deny access to other databases. The InfluxDB query syntax allows switching the database in the query. For example: + + SHOW MEASUREMENTS ON _internal + + or + + SELECT * FROM "_internal".."database" LIMIT 10 + +
+
+ To support data isolation and security, make sure appropriate permissions are configured in InfluxDB. +

+
-
- - Database - -
+
+ - -
-
-
-
-
- - User - -
- -
-
-
-
-
-
+
+
-
- - HTTP Method - -
- -
- Database Access -
-

- Setting the database for this datasource does not deny access to other databases. The InfluxDB query syntax allows switching the database in the query. For example: - - SHOW MEASUREMENTS ON _internal - - or - - SELECT * FROM "_internal".."database" LIMIT 10 - -
-
- To support data isolation and security, make sure appropriate permissions are configured in InfluxDB. -

-
-
-
-
+
+
+
+
+ - +
+
+
+
+ - Min time interval - -
+ Min time interval + +
+ - -
+ onChange={[Function]} + placeholder="10s" + value="4" + />
+
+ + + +
`; diff --git a/public/app/store/configureStore.ts b/public/app/store/configureStore.ts index 7479cd851ed..25bf0996137 100644 --- a/public/app/store/configureStore.ts +++ b/public/app/store/configureStore.ts @@ -14,7 +14,7 @@ export function addRootReducer(reducers: any) { addReducer(reducers); } -export function configureStore() { +export function configureStore(initialState?: Partial) { const logger = createLogger({ predicate: (getState) => { return getState().application.logActions; @@ -35,6 +35,7 @@ export function configureStore() { devTools: process.env.NODE_ENV !== 'production', preloadedState: { navIndex: buildInitialState(), + ...initialState, }, }); @@ -42,7 +43,7 @@ export function configureStore() { return store; } -/* +/* function getActionsToIgnoreSerializableCheckOn() { return [ 'dashboard/setPanelAngularComponent', @@ -58,7 +59,7 @@ function getActionsToIgnoreSerializableCheckOn() { } function getPathsToIgnoreMutationAndSerializableCheckOn() { - return [ + return [ 'plugins.panels', 'dashboard.panels', 'dashboard.getModel', @@ -75,7 +76,7 @@ function getPathsToIgnoreMutationAndSerializableCheckOn() { 'explore.right.eventBridge', 'explore.right.range', 'explore.left.querySubscription', - 'explore.right.querySubscription', + 'explore.right.querySubscription', ]; } */