diff --git a/conf/defaults.ini b/conf/defaults.ini index 0c0eecdcf23..15e761d8896 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -316,6 +316,9 @@ feedback_links_enabled = true # Static context that is being added to analytics events reporting_static_context = +# Logs interaction events to the browser javascript console, intended for development only +browser_console_reporter = false + #################################### Security ############################ [security] # disable creation of admin user on first start of grafana diff --git a/conf/sample.ini b/conf/sample.ini index 644449780b2..5a9f078fe73 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -315,6 +315,9 @@ # Static context that is being added to analytics events ;reporting_static_context = grafanaInstance=12, os=linux +# Logs interaction events to the browser javascript console, intended for development only +;browser_console_reporter = false + #################################### Security #################################### [security] # disable creation of admin user on first start of grafana diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index 1cf37071e23..09d93375e89 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -226,6 +226,7 @@ export interface GrafanaConfig { rudderstackSdkUrl: string | undefined; rudderstackConfigUrl: string | undefined; rudderstackIntegrationsUrl: string | undefined; + analyticsConsoleReporting: boolean; sqlConnectionLimits: SqlConnectionLimits; sharedWithMeFolderUID?: string; rootFolderUID?: string; diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index 299db15ec35..9c06b25cfba 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -183,6 +183,7 @@ export class GrafanaBootConfig implements GrafanaConfig { rudderstackSdkUrl: undefined; rudderstackConfigUrl: undefined; rudderstackIntegrationsUrl: undefined; + analyticsConsoleReporting = false; sqlConnectionLimits = { maxOpenConns: 100, maxIdleConns: 100, diff --git a/pkg/api/dtos/frontend_settings.go b/pkg/api/dtos/frontend_settings.go index c0900e8ab47..0799c4814fe 100644 --- a/pkg/api/dtos/frontend_settings.go +++ b/pkg/api/dtos/frontend_settings.go @@ -187,6 +187,8 @@ type FrontendSettingsDTO struct { RudderstackConfigUrl string `json:"rudderstackConfigUrl"` RudderstackIntegrationsUrl string `json:"rudderstackIntegrationsUrl"` + AnalyticsConsoleReporting bool `json:"analyticsConsoleReporting"` + FeedbackLinksEnabled bool `json:"feedbackLinksEnabled"` ApplicationInsightsConnectionString string `json:"applicationInsightsConnectionString"` ApplicationInsightsEndpointUrl string `json:"applicationInsightsEndpointUrl"` diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 2f09a859356..3441ec56f0e 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -213,6 +213,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro RudderstackSdkUrl: hs.Cfg.RudderstackSDKURL, RudderstackConfigUrl: hs.Cfg.RudderstackConfigURL, RudderstackIntegrationsUrl: hs.Cfg.RudderstackIntegrationsURL, + AnalyticsConsoleReporting: hs.Cfg.FrontendAnalyticsConsoleReporting, FeedbackLinksEnabled: hs.Cfg.FeedbackLinksEnabled, ApplicationInsightsConnectionString: hs.Cfg.ApplicationInsightsConnectionString, ApplicationInsightsEndpointUrl: hs.Cfg.ApplicationInsightsEndpointUrl, diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 0e3b65a5902..ee70dd2b2af 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -382,6 +382,7 @@ type Cfg struct { RudderstackConfigURL string RudderstackIntegrationsURL string IntercomSecret string + FrontendAnalyticsConsoleReporting bool // LDAP LDAPAuthEnabled bool @@ -1158,6 +1159,7 @@ func (cfg *Cfg) parseINIFile(iniFile *ini.File) error { cfg.RudderstackConfigURL = analytics.Key("rudderstack_config_url").String() cfg.RudderstackIntegrationsURL = analytics.Key("rudderstack_integrations_url").String() cfg.IntercomSecret = analytics.Key("intercom_secret").String() + cfg.FrontendAnalyticsConsoleReporting = analytics.Key("browser_console_reporter").MustBool(false) cfg.ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true) cfg.ReportingDistributor = analytics.Key("reporting_distributor").MustString("grafana-labs") diff --git a/public/app/app.ts b/public/app/app.ts index c824f6c4db1..052a3382a58 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -67,6 +67,7 @@ import { Echo } from './core/services/echo/Echo'; import { reportPerformance } from './core/services/echo/EchoSrv'; import { PerformanceBackend } from './core/services/echo/backends/PerformanceBackend'; import { ApplicationInsightsBackend } from './core/services/echo/backends/analytics/ApplicationInsightsBackend'; +import { BrowserConsoleBackend } from './core/services/echo/backends/analytics/BrowseConsoleBackend'; import { GA4EchoBackend } from './core/services/echo/backends/analytics/GA4Backend'; import { GAEchoBackend } from './core/services/echo/backends/analytics/GABackend'; import { RudderstackBackend } from './core/services/echo/backends/analytics/RudderstackBackend'; @@ -382,6 +383,10 @@ function initEchoSrv() { }) ); } + + if (config.analyticsConsoleReporting) { + registerEchoBackend(new BrowserConsoleBackend()); + } } /** diff --git a/public/app/core/config.ts b/public/app/core/config.ts index db165baf96b..5a37877421d 100644 --- a/public/app/core/config.ts +++ b/public/app/core/config.ts @@ -1,6 +1,5 @@ import { PluginState } from '@grafana/data'; import { config, GrafanaBootConfig } from '@grafana/runtime'; -// Legacy binding paths export { config, GrafanaBootConfig as Settings }; let grafanaConfig: GrafanaBootConfig = config; diff --git a/public/app/core/services/echo/backends/analytics/BrowseConsoleBackend.ts b/public/app/core/services/echo/backends/analytics/BrowseConsoleBackend.ts new file mode 100644 index 00000000000..922c9256408 --- /dev/null +++ b/public/app/core/services/echo/backends/analytics/BrowseConsoleBackend.ts @@ -0,0 +1,50 @@ +/* eslint-disable no-console */ +import { + EchoBackend, + EchoEventType, + isExperimentViewEvent, + isInteractionEvent, + isPageviewEvent, + PageviewEchoEvent, +} from '@grafana/runtime'; + +export class BrowserConsoleBackend implements EchoBackend { + options = {}; + supportedEvents = [EchoEventType.Pageview, EchoEventType.Interaction, EchoEventType.ExperimentView]; + + constructor() {} + + addEvent = (e: PageviewEchoEvent) => { + if (isPageviewEvent(e)) { + console.log('[EchoSrv:pageview]', e.payload.page); + } + + if (isInteractionEvent(e)) { + const eventName = e.payload.interactionName; + console.log('[EchoSrv:event]', eventName, e.payload.properties); + + // Warn for non-scalar property values. We're not yet making this a hard a + const invalidTypeProperties = Object.entries(e.payload.properties ?? {}).filter(([_, value]) => { + const valueType = typeof value; + const isValidType = + valueType === 'string' || valueType === 'number' || valueType === 'boolean' || valueType === 'undefined'; + return !isValidType; + }); + + if (invalidTypeProperties.length > 0) { + console.warn( + 'Event', + eventName, + 'has invalid property types. Event properties should only be string, number or boolean. Invalid properties:', + Object.fromEntries(invalidTypeProperties) + ); + } + } + + if (isExperimentViewEvent(e)) { + console.log('[EchoSrv:experiment]', e.payload); + } + }; + + flush = () => {}; +}