From e9695d09609a95eda01ae79dc4cd45a4cf15eb18 Mon Sep 17 00:00:00 2001 From: aaccensi Date: Tue, 20 Jan 2026 14:39:38 +0100 Subject: [PATCH 1/5] Add Sentry JS profiling. --- app/controllers/application_controller.rb | 7 ++++ app/helpers/application_helper.rb | 5 +++ app/javascript/packs/app.ts | 5 +++ app/javascript/sentry.ts | 36 ++++++++++++++++++ config/initializers/sentry.rb | 2 +- config/settings.yml | 2 +- package.json | 1 + yarn.lock | 46 +++++++++++++++++++++++ 8 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 app/javascript/sentry.ts diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b04c7e02cc..fdb9db334c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -12,6 +12,7 @@ class ApplicationController < ActionController::Base before_action :ensure_modern_browser after_action :teardown_current + after_action :set_document_policy_header rescue_from YModel::RecordNotFound do render_not_found @@ -127,6 +128,12 @@ def current_user private + # Sets the Document-Policy header required for Sentry browser profiling. + # See: https://docs.sentry.io/platforms/javascript/profiling/ + def set_document_policy_header + response.headers['Document-Policy'] = 'js-profiling' + end + # Internal: Renders a 404 page. # # thing - An optional noun, describing what thing could not be found. Leave diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 129de9a153..1ac0bd3db3 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -138,6 +138,7 @@ def js_globals json.api_proxy_url Settings.api_proxy_url json.disable_cors Settings.disable_cors json.standalone Settings.standalone + json.version Settings.version json.settings settings_as_json(Current.setting) json.debug_js admin? json.env Rails.env @@ -163,6 +164,10 @@ def js_globals else json.api_session_id nil end + + if Settings.sentry_dsn + json.sentry_dsn Settings.sentry_dsn + end end.html_safe end end diff --git a/app/javascript/packs/app.ts b/app/javascript/packs/app.ts index 878edf95cc..5ef65599ed 100644 --- a/app/javascript/packs/app.ts +++ b/app/javascript/packs/app.ts @@ -86,3 +86,8 @@ window.MultiCurveChooserView = MultiCurveChooserView; import { onClick as saveAsPNGClick } from '../charts/utils/saveAsPNG'; window.BaseChartView.saveAsPNG = saveAsPNGClick; + +// Sentry Browser Profiling +// ------------------------ + +import '../sentry'; diff --git a/app/javascript/sentry.ts b/app/javascript/sentry.ts new file mode 100644 index 0000000000..a441858945 --- /dev/null +++ b/app/javascript/sentry.ts @@ -0,0 +1,36 @@ +import * as Sentry from '@sentry/browser'; + +declare const globals: { + sentry_dsn?: string; + version?: string; + env?: string; +}; + +const enabledEnvs = ['production', 'staging']; + +if (globals && globals.sentry_dsn && enabledEnvs.includes(globals.env)) { + Sentry.init({ + dsn: globals.sentry_dsn, + release: globals.version, + environment: globals.env, + integrations: [ + Sentry.browserTracingIntegration(), + Sentry.browserProfilingIntegration(), + ], + + // Capture 10% of transactions for tracing + tracesSampleRate: 0.1, + + // Propagate traces to ETEngine and MyETM for end-to-end profiling + tracePropagationTargets: [ + /^https:\/\/.*\.energytransitionmodel\.com/, + 'localhost', + ], + + // Profile 100% of sampled transactions + profileSessionSampleRate: 1.0, + + // Automatically start/stop profiler based on tracing spans + profileLifecycle: 'trace', + }); +} diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb index d9c50399a6..5498c0638c 100644 --- a/config/initializers/sentry.rb +++ b/config/initializers/sentry.rb @@ -3,7 +3,7 @@ if Settings.sentry_dsn Sentry.init do |config| # Set release version - config.release = 'stable.01' + config.release = Settings.version config.dsn = Settings.sentry_dsn config.enabled_environments = %w[production staging] diff --git a/config/settings.yml b/config/settings.yml index ca6ae40d34..e173973ec7 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -54,7 +54,7 @@ feedback_email: info@energytransitionmodel.com # Sentry # ------ -# Optionally send error messages to the Sentry service by providing your +# Optionally send error messages and profiling data to the Sentry service by providing your # Sentry DSN: sentry_dsn: <%= ENV['SENTRY_DSN'] %> diff --git a/package.json b/package.json index 7081a91a0b..2e891aaebe 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@babel/preset-env": "7", "@babel/preset-typescript": "^7.22.5", "@babel/runtime": "7", + "@sentry/browser": "^10.35.0", "babel-loader": "8", "babel-plugin-macros": "^3.1.0", "compression-webpack-plugin": "9", diff --git a/yarn.lock b/yarn.lock index 32285740d9..fe37baf292 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1326,6 +1326,52 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@sentry-internal/browser-utils@10.35.0": + version "10.35.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-10.35.0.tgz#6f1238fd11800a79b2ec0b99519049ed9d18d271" + integrity sha512-YjVbyqpJu6E6U/BCdOgIUuUQPUDZ7XdFiBYXtGy59xqQB1qSqNfei163hkfnXxIN90csDubxWNrnit+W5Wo/uQ== + dependencies: + "@sentry/core" "10.35.0" + +"@sentry-internal/feedback@10.35.0": + version "10.35.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-10.35.0.tgz#fc4de2357f5f806e68cb4807391a3216b4d803b5" + integrity sha512-h/rtGcgvGtZIY9njxnzHHMzMwFYAYG/UwDaNtpf8jN63JD6cTQDQ8wNWp0arD9gmUr96YjER55BNRRF8oSg6Fw== + dependencies: + "@sentry/core" "10.35.0" + +"@sentry-internal/replay-canvas@10.35.0": + version "10.35.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-10.35.0.tgz#864b4c9f4eeac905415b4054b572f6cf5a2f92c1" + integrity sha512-efaz8ETDLd0rSpoqX4m8fMnq7abzUJAdqeChz9Jdq6OgvHeBgM6tTfqWSes6sFnSCvFUVkdFngZQfgmBxWGuEA== + dependencies: + "@sentry-internal/replay" "10.35.0" + "@sentry/core" "10.35.0" + +"@sentry-internal/replay@10.35.0": + version "10.35.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-10.35.0.tgz#1bc7c0506231b9e360a7144288ce322550b01b9b" + integrity sha512-9hGP3lD+7o/4ovGTdwv3T9K2t9LxSlR/CAcRQeFApW2c0AGsjTdcglOxsgxYei4YmaISx0CBJ/YqJfQVYxaxWw== + dependencies: + "@sentry-internal/browser-utils" "10.35.0" + "@sentry/core" "10.35.0" + +"@sentry/browser@^10.35.0": + version "10.35.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-10.35.0.tgz#814e6540e031a2904a04735364780dfcd1c42bb8" + integrity sha512-3wCdmKOTqg6Fvmb9HLHzCVIpSSYCPhXFQ95VaYsb1rESIgL7BMS9nyqhecPcPR3oJppU2a/TqZk4YH3nFrPXmA== + dependencies: + "@sentry-internal/browser-utils" "10.35.0" + "@sentry-internal/feedback" "10.35.0" + "@sentry-internal/replay" "10.35.0" + "@sentry-internal/replay-canvas" "10.35.0" + "@sentry/core" "10.35.0" + +"@sentry/core@10.35.0": + version "10.35.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-10.35.0.tgz#caf84bdd4ac630e6c37b787029de50736b06f594" + integrity sha512-lEK1WFqt6oHtMq5dDLVE/FDzHDGs1PlYT5cZH4aBirYtJVyUiTf0NknKFob4a2zTywczlq7SbLv6Ba8UMU9dYg== + "@types/body-parser@*": version "1.19.2" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" From 20666093e2ecf9c78a71e7a0c80455207b250505 Mon Sep 17 00:00:00 2001 From: aaccensi Date: Tue, 20 Jan 2026 17:16:06 +0100 Subject: [PATCH 2/5] Make sentry traces and profiles percentages configurable with environment variables --- app/helpers/application_helper.rb | 2 ++ app/javascript/sentry.ts | 10 ++++++---- config/settings.yml | 4 +++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 1ac0bd3db3..655cd925f6 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -167,6 +167,8 @@ def js_globals if Settings.sentry_dsn json.sentry_dsn Settings.sentry_dsn + json.sentry_traces Settings.sentry_traces + json.sentry_profiles Settings.sentry_profiles end end.html_safe end diff --git a/app/javascript/sentry.ts b/app/javascript/sentry.ts index a441858945..ed01304933 100644 --- a/app/javascript/sentry.ts +++ b/app/javascript/sentry.ts @@ -2,6 +2,8 @@ import * as Sentry from '@sentry/browser'; declare const globals: { sentry_dsn?: string; + sentry_traces?: number; + sentry_profiles?: number; version?: string; env?: string; }; @@ -18,8 +20,8 @@ if (globals && globals.sentry_dsn && enabledEnvs.includes(globals.env)) { Sentry.browserProfilingIntegration(), ], - // Capture 10% of transactions for tracing - tracesSampleRate: 0.1, + // Percentage of transactions to capture for tracing + tracesSampleRate: globals.sentry_traces, // Propagate traces to ETEngine and MyETM for end-to-end profiling tracePropagationTargets: [ @@ -27,8 +29,8 @@ if (globals && globals.sentry_dsn && enabledEnvs.includes(globals.env)) { 'localhost', ], - // Profile 100% of sampled transactions - profileSessionSampleRate: 1.0, + // Percentage of sampled transactions to profile + profileSessionSampleRate: globals.sentry_profiles, // Automatically start/stop profiler based on tracing spans profileLifecycle: 'trace', diff --git a/config/settings.yml b/config/settings.yml index e173973ec7..e83f4aa7d1 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -55,8 +55,10 @@ feedback_email: info@energytransitionmodel.com # ------ # Optionally send error messages and profiling data to the Sentry service by providing your -# Sentry DSN: +# Sentry details: sentry_dsn: <%= ENV['SENTRY_DSN'] %> +sentry_traces: <%= ENV.fetch('SENTRY_TRACES', 0.1) %> +sentry_profiles: <%= ENV.fetch('SENTRY_PROFILES', 0.1) %> # Mailchimp # --------- From 1c7153ad61ebfaaca2faddfde156cf0f80158176 Mon Sep 17 00:00:00 2001 From: aaccensi Date: Wed, 21 Jan 2026 13:48:49 +0100 Subject: [PATCH 3/5] Disable trace propagation --- app/javascript/sentry.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/javascript/sentry.ts b/app/javascript/sentry.ts index ed01304933..19f9246095 100644 --- a/app/javascript/sentry.ts +++ b/app/javascript/sentry.ts @@ -23,12 +23,6 @@ if (globals && globals.sentry_dsn && enabledEnvs.includes(globals.env)) { // Percentage of transactions to capture for tracing tracesSampleRate: globals.sentry_traces, - // Propagate traces to ETEngine and MyETM for end-to-end profiling - tracePropagationTargets: [ - /^https:\/\/.*\.energytransitionmodel\.com/, - 'localhost', - ], - // Percentage of sampled transactions to profile profileSessionSampleRate: globals.sentry_profiles, From a82a9963502c08063164eceedcd8b76bfd2785cd Mon Sep 17 00:00:00 2001 From: aaccensi Date: Thu, 22 Jan 2026 17:37:45 +0100 Subject: [PATCH 4/5] Add sentry metrics to data export csv downloads. References #4633 --- Gemfile.lock | 12 +++++------- app/controllers/api_passthru_controller.rb | 11 +++++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 14275c980a..385681926a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -556,14 +556,12 @@ GEM rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) semantic_range (3.0.0) - sentry-rails (5.3.0) - railties (>= 5.0) - sentry-ruby-core (~> 5.3.0) - sentry-ruby (5.3.0) + sentry-rails (6.3.0) + railties (>= 5.2.0) + sentry-ruby (~> 6.3.0) + sentry-ruby (6.3.0) + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) - sentry-ruby-core (= 5.3.0) - sentry-ruby-core (5.3.0) - concurrent-ruby sexp_processor (4.16.0) shakapacker (6.0.0) activesupport (>= 5.2) diff --git a/app/controllers/api_passthru_controller.rb b/app/controllers/api_passthru_controller.rb index 291a5ba816..e5b7669387 100644 --- a/app/controllers/api_passthru_controller.rb +++ b/app/controllers/api_passthru_controller.rb @@ -9,11 +9,22 @@ def passthru url.path = "/api/v3/scenarios/#{params[:id]}/#{params[:rest]}" url.query = { access_token: identity_access_token.token }.to_query if signed_in? + track_csv_download + redirect_to url.to_s, allow_other_host: true end private + def track_csv_download + return unless params[:rest]&.end_with?('.csv') + + Sentry.metrics.count( + 'csv_download', + attributes: { type: params[:rest] } + ) + end + # Allows the browser to make requests to this endpoint only from within ETModel. def set_cors_headers url = URI(request.url) From 811cafd15d941593ab4fdfb2c4560495c79bccfc Mon Sep 17 00:00:00 2001 From: aaccensi Date: Mon, 26 Jan 2026 09:28:17 +0100 Subject: [PATCH 5/5] Add sentry metrics also to the inputs.csv download. References #4633 --- app/controllers/scenarios_controller.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/controllers/scenarios_controller.rb b/app/controllers/scenarios_controller.rb index cf08c89e5e..661f821235 100644 --- a/app/controllers/scenarios_controller.rb +++ b/app/controllers/scenarios_controller.rb @@ -156,6 +156,8 @@ def play_multi_year_charts end def inputs + track_csv_download('inputs.csv') + default_values = @scenario.inputs(engine_client) csv = CSV.generate do |row| @@ -206,6 +208,13 @@ def inputs private + def track_csv_download(type) + Sentry.metrics.count( + 'csv_download', + attributes: { type: type } + ) + end + # Finds the scenario from id def find_scenario @scenario = FetchAPIScenario.call(engine_client, params.require(:id).to_i).or do