diff --git a/CHANGELOG.md b/CHANGELOG.md index 7164f5d99a9..1a19957b3ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Minor * **Pushover**: Adds support for images in pushover notifier [#10780](https://github.com/grafana/grafana/issues/10780), thx [@jpenalbae](https://github.com/jpenalbae) +* **Cloudwatch**: Add AWS/Neptune metrics [#14231](https://github.com/grafana/grafana/issues/14231), thx [@tcpatterson](https://github.com/tcpatterson) # 6.0.0-beta1 (2019-01-30) diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index fb0f0938573..6debaca89a1 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -242,10 +242,7 @@ func (ss *SqlStore) buildConnectionString() (string, error) { cnnstr += ss.buildExtraConnectionString('&') case migrator.POSTGRES: - host, port, err := util.SplitIPPort(ss.dbCfg.Host, "5432") - if err != nil { - return "", err - } + host, port := util.SplitHostPortDefault(ss.dbCfg.Host, "127.0.0.1", "5432") if ss.dbCfg.Pwd == "" { ss.dbCfg.Pwd = "''" } diff --git a/pkg/tsdb/cloudwatch/metric_find_query.go b/pkg/tsdb/cloudwatch/metric_find_query.go index dfa03d2dfa9..f898a65f911 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query.go +++ b/pkg/tsdb/cloudwatch/metric_find_query.go @@ -95,6 +95,7 @@ func init() { "AWS/Logs": {"IncomingBytes", "IncomingLogEvents", "ForwardedBytes", "ForwardedLogEvents", "DeliveryErrors", "DeliveryThrottling"}, "AWS/ML": {"PredictCount", "PredictFailureCount"}, "AWS/NATGateway": {"PacketsOutToDestination", "PacketsOutToSource", "PacketsInFromSource", "PacketsInFromDestination", "BytesOutToDestination", "BytesOutToSource", "BytesInFromSource", "BytesInFromDestination", "ErrorPortAllocation", "ActiveConnectionCount", "ConnectionAttemptCount", "ConnectionEstablishedCount", "IdleTimeoutCount", "PacketsDropCount"}, + "AWS/Neptune": {"CPUUtilization", "ClusterReplicaLag", "ClusterReplicaLagMaximum", "ClusterReplicaLagMinimum", "EngineUptime", "FreeableMemory", "FreeLocalStorage", "GremlinHttp1xx", "GremlinHttp2xx", "GremlinHttp4xx", "GremlinHttp5xx", "GremlinErrors", "GremlinRequests", "GremlinRequestsPerSec", "GremlinWebSocketSuccess", "GremlinWebSocketClientErrors", "GremlinWebSocketServerErrors", "GremlinWebSocketAvailableConnections", "Http1xx", "Http2xx", "Http4xx", "Http5xx", "Http100", "Http101", "Http200", "Http400", "Http403", "Http405", "Http413", "Http429", "Http500", "Http501", "LoaderErrors", "LoaderRequests", "NetworkReceiveThroughput", "NetworkThroughput", "NetworkTransmitThroughput", "SparqlHttp1xx", "SparqlHttp2xx", "SparqlHttp4xx", "SparqlHttp5xx", "SparqlErrors", "SparqlRequests", "SparqlRequestsPerSec", "StatusErrors", "StatusRequests", "VolumeBytesUsed", "VolumeReadIOPs", "VolumeWriteIOPs"}, "AWS/NetworkELB": {"ActiveFlowCount", "ConsumedLCUs", "HealthyHostCount", "NewFlowCount", "ProcessedBytes", "TCP_Client_Reset_Count", "TCP_ELB_Reset_Count", "TCP_Target_Reset_Count", "UnHealthyHostCount"}, "AWS/OpsWorks": {"cpu_idle", "cpu_nice", "cpu_system", "cpu_user", "cpu_waitio", "load_1", "load_5", "load_15", "memory_buffers", "memory_cached", "memory_free", "memory_swap", "memory_total", "memory_used", "procs"}, "AWS/Redshift": {"CPUUtilization", "DatabaseConnections", "HealthStatus", "MaintenanceMode", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "PercentageDiskSpaceUsed", "QueriesCompletedPerSecond", "QueryDuration", "QueryRuntimeBreakdown", "ReadIOPS", "ReadLatency", "ReadThroughput", "WLMQueriesCompletedPerSecond", "WLMQueryDuration", "WLMQueueLength", "WriteIOPS", "WriteLatency", "WriteThroughput"}, @@ -149,6 +150,7 @@ func init() { "AWS/Logs": {"LogGroupName", "DestinationType", "FilterName"}, "AWS/ML": {"MLModelId", "RequestMode"}, "AWS/NATGateway": {"NatGatewayId"}, + "AWS/Neptune": {"DBClusterIdentifier", "Role", "DatabaseClass", "EngineName"}, "AWS/NetworkELB": {"LoadBalancer", "TargetGroup", "AvailabilityZone"}, "AWS/OpsWorks": {"StackId", "LayerId", "InstanceId"}, "AWS/Redshift": {"NodeID", "ClusterIdentifier", "latency", "service class", "wmlid"}, diff --git a/pkg/tsdb/mssql/mssql.go b/pkg/tsdb/mssql/mssql.go index bd4510f6cf3..12f2b6c03c9 100644 --- a/pkg/tsdb/mssql/mssql.go +++ b/pkg/tsdb/mssql/mssql.go @@ -49,10 +49,7 @@ func generateConnectionString(datasource *models.DataSource) (string, error) { } } - server, port, err := util.SplitIPPort(datasource.Url, "1433") - if err != nil { - return "", err - } + server, port := util.SplitHostPortDefault(datasource.Url, "localhost", "1433") encrypt := datasource.JsonData.Get("encrypt").MustString("false") connStr := fmt.Sprintf("server=%s;port=%s;database=%s;user id=%s;password=%s;", diff --git a/pkg/util/ip.go b/pkg/util/ip.go deleted file mode 100644 index d3809318191..00000000000 --- a/pkg/util/ip.go +++ /dev/null @@ -1,25 +0,0 @@ -package util - -import ( - "net" -) - -// SplitIPPort splits the ip string and port. -func SplitIPPort(ipStr string, portDefault string) (ip string, port string, err error) { - ipAddr := net.ParseIP(ipStr) - - if ipAddr == nil { - // Port was included - ip, port, err = net.SplitHostPort(ipStr) - - if err != nil { - return "", "", err - } - } else { - // No port was included - ip = ipAddr.String() - port = portDefault - } - - return ip, port, nil -} diff --git a/pkg/util/ip_address.go b/pkg/util/ip_address.go index d8d95ef3acd..b5ffb361e0b 100644 --- a/pkg/util/ip_address.go +++ b/pkg/util/ip_address.go @@ -7,23 +7,48 @@ import ( // ParseIPAddress parses an IP address and removes port and/or IPV6 format func ParseIPAddress(input string) string { - s := input - lastIndex := strings.LastIndex(input, ":") + host, _ := SplitHostPort(input) - if lastIndex != -1 { - if lastIndex > 0 && input[lastIndex-1:lastIndex] != ":" { - s = input[:lastIndex] - } + ip := net.ParseIP(host) + + if ip == nil { + return host } - s = strings.Replace(s, "[", "", -1) - s = strings.Replace(s, "]", "", -1) - - ip := net.ParseIP(s) - if ip.IsLoopback() { return "127.0.0.1" } return ip.String() } + +// SplitHostPortDefault splits ip address/hostname string by host and port. Defaults used if no match found +func SplitHostPortDefault(input, defaultHost, defaultPort string) (host string, port string) { + port = defaultPort + s := input + lastIndex := strings.LastIndex(input, ":") + + if lastIndex != -1 { + if lastIndex > 0 && input[lastIndex-1:lastIndex] != ":" { + s = input[:lastIndex] + port = input[lastIndex+1:] + } else if lastIndex == 0 { + s = defaultHost + port = input[lastIndex+1:] + } + } else { + port = defaultPort + } + + s = strings.Replace(s, "[", "", -1) + s = strings.Replace(s, "]", "", -1) + port = strings.Replace(port, "[", "", -1) + port = strings.Replace(port, "]", "", -1) + + return s, port +} + +// SplitHostPort splits ip address/hostname string by host and port +func SplitHostPort(input string) (host string, port string) { + return SplitHostPortDefault(input, "", "") +} diff --git a/pkg/util/ip_address_test.go b/pkg/util/ip_address_test.go index fd3e3ea8587..b926de1a36b 100644 --- a/pkg/util/ip_address_test.go +++ b/pkg/util/ip_address_test.go @@ -9,8 +9,90 @@ import ( func TestParseIPAddress(t *testing.T) { Convey("Test parse ip address", t, func() { So(ParseIPAddress("192.168.0.140:456"), ShouldEqual, "192.168.0.140") + So(ParseIPAddress("192.168.0.140"), ShouldEqual, "192.168.0.140") So(ParseIPAddress("[::1:456]"), ShouldEqual, "127.0.0.1") So(ParseIPAddress("[::1]"), ShouldEqual, "127.0.0.1") - So(ParseIPAddress("192.168.0.140"), ShouldEqual, "192.168.0.140") + So(ParseIPAddress("::1"), ShouldEqual, "127.0.0.1") + So(ParseIPAddress("::1:123"), ShouldEqual, "127.0.0.1") + }) +} + +func TestSplitHostPortDefault(t *testing.T) { + Convey("Test split ip address to host and port", t, func() { + host, port := SplitHostPortDefault("192.168.0.140:456", "", "") + So(host, ShouldEqual, "192.168.0.140") + So(port, ShouldEqual, "456") + + host, port = SplitHostPortDefault("192.168.0.140", "", "123") + So(host, ShouldEqual, "192.168.0.140") + So(port, ShouldEqual, "123") + + host, port = SplitHostPortDefault("[::1:456]", "", "") + So(host, ShouldEqual, "::1") + So(port, ShouldEqual, "456") + + host, port = SplitHostPortDefault("[::1]", "", "123") + So(host, ShouldEqual, "::1") + So(port, ShouldEqual, "123") + + host, port = SplitHostPortDefault("::1:123", "", "") + So(host, ShouldEqual, "::1") + So(port, ShouldEqual, "123") + + host, port = SplitHostPortDefault("::1", "", "123") + So(host, ShouldEqual, "::1") + So(port, ShouldEqual, "123") + + host, port = SplitHostPortDefault(":456", "1.2.3.4", "") + So(host, ShouldEqual, "1.2.3.4") + So(port, ShouldEqual, "456") + + host, port = SplitHostPortDefault("xyz.rds.amazonaws.com", "", "123") + So(host, ShouldEqual, "xyz.rds.amazonaws.com") + So(port, ShouldEqual, "123") + + host, port = SplitHostPortDefault("xyz.rds.amazonaws.com:123", "", "") + So(host, ShouldEqual, "xyz.rds.amazonaws.com") + So(port, ShouldEqual, "123") + }) +} + +func TestSplitHostPort(t *testing.T) { + Convey("Test split ip address to host and port", t, func() { + host, port := SplitHostPort("192.168.0.140:456") + So(host, ShouldEqual, "192.168.0.140") + So(port, ShouldEqual, "456") + + host, port = SplitHostPort("192.168.0.140") + So(host, ShouldEqual, "192.168.0.140") + So(port, ShouldEqual, "") + + host, port = SplitHostPort("[::1:456]") + So(host, ShouldEqual, "::1") + So(port, ShouldEqual, "456") + + host, port = SplitHostPort("[::1]") + So(host, ShouldEqual, "::1") + So(port, ShouldEqual, "") + + host, port = SplitHostPort("::1:123") + So(host, ShouldEqual, "::1") + So(port, ShouldEqual, "123") + + host, port = SplitHostPort("::1") + So(host, ShouldEqual, "::1") + So(port, ShouldEqual, "") + + host, port = SplitHostPort(":456") + So(host, ShouldEqual, "") + So(port, ShouldEqual, "456") + + host, port = SplitHostPort("xyz.rds.amazonaws.com") + So(host, ShouldEqual, "xyz.rds.amazonaws.com") + So(port, ShouldEqual, "") + + host, port = SplitHostPort("xyz.rds.amazonaws.com:123") + So(host, ShouldEqual, "xyz.rds.amazonaws.com") + So(port, ShouldEqual, "123") }) } diff --git a/pkg/util/ip_test.go b/pkg/util/ip_test.go deleted file mode 100644 index 3a62a080e26..00000000000 --- a/pkg/util/ip_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package util - -import ( - "testing" - - . "github.com/smartystreets/goconvey/convey" -) - -func TestSplitIPPort(t *testing.T) { - - Convey("When parsing an IPv4 without explicit port", t, func() { - ip, port, err := SplitIPPort("1.2.3.4", "5678") - - So(err, ShouldEqual, nil) - So(ip, ShouldEqual, "1.2.3.4") - So(port, ShouldEqual, "5678") - }) - - Convey("When parsing an IPv6 without explicit port", t, func() { - ip, port, err := SplitIPPort("::1", "5678") - - So(err, ShouldEqual, nil) - So(ip, ShouldEqual, "::1") - So(port, ShouldEqual, "5678") - }) - - Convey("When parsing an IPv4 with explicit port", t, func() { - ip, port, err := SplitIPPort("1.2.3.4:56", "78") - - So(err, ShouldEqual, nil) - So(ip, ShouldEqual, "1.2.3.4") - So(port, ShouldEqual, "56") - }) - - Convey("When parsing an IPv6 with explicit port", t, func() { - ip, port, err := SplitIPPort("[::1]:56", "78") - - So(err, ShouldEqual, nil) - So(ip, ShouldEqual, "::1") - So(port, ShouldEqual, "56") - }) - -} diff --git a/public/app/core/utils/explore.test.ts b/public/app/core/utils/explore.test.ts index 32135eab90a..1c00142c3b8 100644 --- a/public/app/core/utils/explore.test.ts +++ b/public/app/core/utils/explore.test.ts @@ -13,6 +13,11 @@ const DEFAULT_EXPLORE_STATE: ExploreUrlState = { datasource: null, queries: [], range: DEFAULT_RANGE, + ui: { + showingGraph: true, + showingTable: true, + showingLogs: true, + } }; describe('state functions', () => { @@ -69,9 +74,11 @@ describe('state functions', () => { to: 'now', }, }; + expect(serializeStateToUrlParam(state)).toBe( '{"datasource":"foo","queries":[{"expr":"metric{test=\\"a/b\\"}"},' + - '{"expr":"super{foo=\\"x/z\\"}"}],"range":{"from":"now-5h","to":"now"}}' + '{"expr":"super{foo=\\"x/z\\"}"}],"range":{"from":"now-5h","to":"now"},' + + '"ui":{"showingGraph":true,"showingTable":true,"showingLogs":true}}' ); }); @@ -93,7 +100,7 @@ describe('state functions', () => { }, }; expect(serializeStateToUrlParam(state, true)).toBe( - '["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"}]' + '["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"},{"ui":[true,true,true]}]' ); }); }); @@ -118,7 +125,28 @@ describe('state functions', () => { }; const serialized = serializeStateToUrlParam(state); const parsed = parseUrlState(serialized); + expect(state).toMatchObject(parsed); + }); + it('can parse the compact serialized state into the original state', () => { + const state = { + ...DEFAULT_EXPLORE_STATE, + datasource: 'foo', + queries: [ + { + expr: 'metric{test="a/b"}', + }, + { + expr: 'super{foo="x/z"}', + }, + ], + range: { + from: 'now - 5h', + to: 'now', + }, + }; + const serialized = serializeStateToUrlParam(state, true); + const parsed = parseUrlState(serialized); expect(state).toMatchObject(parsed); }); }); diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index efa54b7bc23..107f411353c 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -27,6 +27,12 @@ export const DEFAULT_RANGE = { to: 'now', }; +export const DEFAULT_UI_STATE = { + showingTable: true, + showingGraph: true, + showingLogs: true, +}; + const MAX_HISTORY_ITEMS = 100; export const LAST_USED_DATASOURCE_KEY = 'grafana.explore.datasource'; @@ -147,7 +153,12 @@ export function buildQueryTransaction( export const clearQueryKeys: ((query: DataQuery) => object) = ({ key, refId, ...rest }) => rest; +const isMetricSegment = (segment: { [key: string]: string }) => segment.hasOwnProperty('expr'); +const isUISegment = (segment: { [key: string]: string }) => segment.hasOwnProperty('ui'); + export function parseUrlState(initial: string | undefined): ExploreUrlState { + let uiState = DEFAULT_UI_STATE; + if (initial) { try { const parsed = JSON.parse(decodeURI(initial)); @@ -160,20 +171,41 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState { to: parsed[1], }; const datasource = parsed[2]; - const queries = parsed.slice(3); - return { datasource, queries, range }; + let queries = []; + + parsed.slice(3).forEach(segment => { + if (isMetricSegment(segment)) { + queries = [...queries, segment]; + } + + if (isUISegment(segment)) { + uiState = { + showingGraph: segment.ui[0], + showingLogs: segment.ui[1], + showingTable: segment.ui[2], + }; + } + }); + + return { datasource, queries, range, ui: uiState }; } return parsed; } catch (e) { console.error(e); } } - return { datasource: null, queries: [], range: DEFAULT_RANGE }; + return { datasource: null, queries: [], range: DEFAULT_RANGE, ui: uiState }; } export function serializeStateToUrlParam(urlState: ExploreUrlState, compact?: boolean): string { if (compact) { - return JSON.stringify([urlState.range.from, urlState.range.to, urlState.datasource, ...urlState.queries]); + return JSON.stringify([ + urlState.range.from, + urlState.range.to, + urlState.datasource, + ...urlState.queries, + { ui: [!!urlState.ui.showingGraph, !!urlState.ui.showingLogs, !!urlState.ui.showingTable] }, + ]); } return JSON.stringify(urlState); } diff --git a/public/app/features/dashboard/components/SaveModals/index.ts b/public/app/features/dashboard/components/SaveModals/index.ts index afab0796d28..6f55cc2ce06 100644 --- a/public/app/features/dashboard/components/SaveModals/index.ts +++ b/public/app/features/dashboard/components/SaveModals/index.ts @@ -1,2 +1,3 @@ export { SaveDashboardAsModalCtrl } from './SaveDashboardAsModalCtrl'; export { SaveDashboardModalCtrl } from './SaveDashboardModalCtrl'; +export { SaveProvisionedDashboardModalCtrl } from './SaveProvisionedDashboardModalCtrl'; diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 2012a52c338..8eb177b8ad4 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -24,7 +24,7 @@ import { changeSize, changeTime, initializeExplore, modifyQueries, scanStart, se import { RawTimeRange, TimeRange, DataQuery, ExploreStartPageProps } from '@grafana/ui'; import { ExploreItemState, ExploreUrlState, RangeScanner, ExploreId } from 'app/types/explore'; import { StoreState } from 'app/types'; -import { LAST_USED_DATASOURCE_KEY, ensureQueries, DEFAULT_RANGE } from 'app/core/utils/explore'; +import { LAST_USED_DATASOURCE_KEY, ensureQueries, DEFAULT_RANGE, DEFAULT_UI_STATE } from 'app/core/utils/explore'; import { Emitter } from 'app/core/utils/emitter'; import { ExploreToolbar } from './ExploreToolbar'; import { scanStopAction } from './state/actionTypes'; @@ -100,18 +100,20 @@ export class Explore extends React.PureComponent { // Don't initialize on split, but need to initialize urlparameters when present if (!initialized) { // Load URL state and parse range - const { datasource, queries, range = DEFAULT_RANGE } = (urlState || {}) as ExploreUrlState; + const { datasource, queries, range = DEFAULT_RANGE, ui = DEFAULT_UI_STATE } = (urlState || {}) as ExploreUrlState; const initialDatasource = datasource || store.get(LAST_USED_DATASOURCE_KEY); const initialQueries: DataQuery[] = ensureQueries(queries); const initialRange = { from: parseTime(range.from), to: parseTime(range.to) }; const width = this.el ? this.el.offsetWidth : 0; + this.props.initializeExplore( exploreId, initialDatasource, initialQueries, initialRange, width, - this.exploreEvents + this.exploreEvents, + ui ); } } diff --git a/public/app/features/explore/state/actionTypes.ts b/public/app/features/explore/state/actionTypes.ts index 05ef661a8e5..98af5e8076e 100644 --- a/public/app/features/explore/state/actionTypes.ts +++ b/public/app/features/explore/state/actionTypes.ts @@ -15,6 +15,7 @@ import { RangeScanner, ResultType, QueryTransaction, + ExploreUIState, } from 'app/types/explore'; import { actionCreatorFactory, noPayloadActionCreatorFactory, ActionOf } from 'app/core/redux/actionCreatorFactory'; @@ -93,6 +94,7 @@ export interface InitializeExplorePayload { exploreDatasources: DataSourceSelectItem[]; queries: DataQuery[]; range: RawTimeRange; + ui: ExploreUIState; } export interface LoadDatasourceFailurePayload { diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index 8530e7678ad..a41c0701994 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -32,7 +32,7 @@ import { QueryHint, QueryFixAction, } from '@grafana/ui/src/types'; -import { ExploreId, ExploreUrlState, RangeScanner, ResultType, QueryOptions } from 'app/types/explore'; +import { ExploreId, ExploreUrlState, RangeScanner, ResultType, QueryOptions, ExploreUIState } from 'app/types/explore'; import { Action, updateDatasourceInstanceAction, @@ -59,13 +59,16 @@ import { setQueriesAction, splitCloseAction, splitOpenAction, + addQueryRowAction, + AddQueryRowPayload, toggleGraphAction, toggleLogsAction, toggleTableAction, - addQueryRowAction, - AddQueryRowPayload, + ToggleGraphPayload, + ToggleLogsPayload, + ToggleTablePayload, } from './actionTypes'; -import { ActionOf } from 'app/core/redux/actionCreatorFactory'; +import { ActionOf, ActionCreator } from 'app/core/redux/actionCreatorFactory'; type ThunkResult = ThunkAction; @@ -89,7 +92,15 @@ export function changeDatasource(exploreId: ExploreId, datasource: string): Thun await dispatch(importQueries(exploreId, queries, currentDataSourceInstance, newDataSourceInstance)); dispatch(updateDatasourceInstanceAction({ exploreId, datasourceInstance: newDataSourceInstance })); - dispatch(loadDatasource(exploreId, newDataSourceInstance)); + + try { + await dispatch(loadDatasource(exploreId, newDataSourceInstance)); + } catch (error) { + console.error(error); + return; + } + + dispatch(runQueries(exploreId)); }; } @@ -158,7 +169,8 @@ export function initializeExplore( queries: DataQuery[], range: RawTimeRange, containerWidth: number, - eventBridge: Emitter + eventBridge: Emitter, + ui: ExploreUIState ): ThunkResult { return async dispatch => { const exploreDatasources: DataSourceSelectItem[] = getDatasourceSrv() @@ -177,6 +189,7 @@ export function initializeExplore( exploreDatasources, queries, range, + ui, }) ); @@ -196,7 +209,14 @@ export function initializeExplore( } dispatch(updateDatasourceInstanceAction({ exploreId, datasourceInstance: instance })); - dispatch(loadDatasource(exploreId, instance)); + + try { + await dispatch(loadDatasource(exploreId, instance)); + } catch (error) { + console.error(error); + return; + } + dispatch(runQueries(exploreId, true)); } else { dispatch(loadDatasourceMissingAction({ exploreId })); } @@ -271,8 +291,8 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T // Keep ID to track selection dispatch(loadDatasourcePendingAction({ exploreId, requestedDatasourceName: datasourceName })); - let datasourceError = null; + try { const testResult = await instance.testDatasource(); datasourceError = testResult.status === 'success' ? null : testResult.message; @@ -282,7 +302,7 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T if (datasourceError) { dispatch(loadDatasourceFailureAction({ exploreId, error: datasourceError })); - return; + return Promise.reject(`${datasourceName} loading failed`); } if (datasourceName !== getState().explore[exploreId].requestedDatasourceName) { @@ -300,7 +320,7 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T } dispatch(loadDatasourceSuccess(exploreId, instance)); - dispatch(runQueries(exploreId)); + return Promise.resolve(); }; } @@ -470,7 +490,7 @@ export function queryTransactionSuccess( /** * Main action to run queries and dispatches sub-actions based on which result viewers are active */ -export function runQueries(exploreId: ExploreId) { +export function runQueries(exploreId: ExploreId, ignoreUIState = false) { return (dispatch, getState) => { const { datasourceInstance, @@ -494,7 +514,7 @@ export function runQueries(exploreId: ExploreId) { const interval = datasourceInstance.interval; // Keep table queries first since they need to return quickly - if (showingTable && supportsTable) { + if ((ignoreUIState || showingTable) && supportsTable) { dispatch( runQueriesForType( exploreId, @@ -509,7 +529,7 @@ export function runQueries(exploreId: ExploreId) { ) ); } - if (showingGraph && supportsGraph) { + if ((ignoreUIState || showingGraph) && supportsGraph) { dispatch( runQueriesForType( exploreId, @@ -523,9 +543,10 @@ export function runQueries(exploreId: ExploreId) { ) ); } - if (showingLogs && supportsLogs) { + if ((ignoreUIState || showingLogs) && supportsLogs) { dispatch(runQueriesForType(exploreId, 'Logs', { interval, format: 'logs' })); } + dispatch(stateSave()); }; } @@ -651,6 +672,11 @@ export function stateSave() { datasource: left.datasourceInstance.name, queries: left.initialQueries.map(clearQueryKeys), range: left.range, + ui: { + showingGraph: left.showingGraph, + showingLogs: left.showingLogs, + showingTable: left.showingTable, + }, }; urlStates.left = serializeStateToUrlParam(leftUrlState, true); if (split) { @@ -658,45 +684,64 @@ export function stateSave() { datasource: right.datasourceInstance.name, queries: right.initialQueries.map(clearQueryKeys), range: right.range, + ui: { + showingGraph: right.showingGraph, + showingLogs: right.showingLogs, + showingTable: right.showingTable, + }, }; + urlStates.right = serializeStateToUrlParam(rightUrlState, true); } + dispatch(updateLocation({ query: urlStates })); }; } /** - * Expand/collapse the graph result viewer. When collapsed, graph queries won't be run. + * Creates action to collapse graph/logs/table panel. When panel is collapsed, + * queries won't be run */ -export function toggleGraph(exploreId: ExploreId): ThunkResult { +const togglePanelActionCreator = ( + actionCreator: + | ActionCreator + | ActionCreator + | ActionCreator +) => (exploreId: ExploreId) => { return (dispatch, getState) => { - dispatch(toggleGraphAction({ exploreId })); - if (getState().explore[exploreId].showingGraph) { + let shouldRunQueries; + dispatch(actionCreator); + dispatch(stateSave()); + + switch (actionCreator.type) { + case toggleGraphAction.type: + shouldRunQueries = getState().explore[exploreId].showingGraph; + break; + case toggleLogsAction.type: + shouldRunQueries = getState().explore[exploreId].showingLogs; + break; + case toggleTableAction.type: + shouldRunQueries = getState().explore[exploreId].showingTable; + break; + } + + if (shouldRunQueries) { dispatch(runQueries(exploreId)); } }; -} +}; + +/** + * Expand/collapse the graph result viewer. When collapsed, graph queries won't be run. + */ +export const toggleGraph = togglePanelActionCreator(toggleGraphAction); /** * Expand/collapse the logs result viewer. When collapsed, log queries won't be run. */ -export function toggleLogs(exploreId: ExploreId): ThunkResult { - return (dispatch, getState) => { - dispatch(toggleLogsAction({ exploreId })); - if (getState().explore[exploreId].showingLogs) { - dispatch(runQueries(exploreId)); - } - }; -} +export const toggleLogs = togglePanelActionCreator(toggleLogsAction); /** * Expand/collapse the table result viewer. When collapsed, table queries won't be run. */ -export function toggleTable(exploreId: ExploreId): ThunkResult { - return (dispatch, getState) => { - dispatch(toggleTableAction({ exploreId })); - if (getState().explore[exploreId].showingTable) { - dispatch(runQueries(exploreId)); - } - }; -} +export const toggleTable = togglePanelActionCreator(toggleTableAction); diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index 86c263e39e9..7c0a729d0ed 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -179,7 +179,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta .addMapper({ filter: initializeExploreAction, mapper: (state, action): ExploreItemState => { - const { containerWidth, eventBridge, exploreDatasources, queries, range } = action.payload; + const { containerWidth, eventBridge, exploreDatasources, queries, range, ui } = action.payload; return { ...state, containerWidth, @@ -189,6 +189,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta initialQueries: queries, initialized: true, queryKeys: getQueryKeys(queries, state.datasourceInstance), + ...ui, }; }, }) diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 8faf0d2ed09..af4ae6183d2 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -239,10 +239,17 @@ export interface ExploreItemState { queryKeys: string[]; } +export interface ExploreUIState { + showingTable: boolean; + showingGraph: boolean; + showingLogs: boolean; +} + export interface ExploreUrlState { datasource: string; queries: any[]; // Should be a DataQuery, but we're going to strip refIds, so typing makes less sense range: RawTimeRange; + ui: ExploreUIState; } export interface HistoryItem {