From f612c9b00f08e82d9c00dd4ccd530a44ea3fc3ef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Feb 2026 06:41:53 +0000 Subject: [PATCH 1/6] Initial plan From ff7dfd038a881b88510ef0d9b6d470d7c3bbc58b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Feb 2026 06:47:49 +0000 Subject: [PATCH 2/6] Add Blazer gem and install configuration Co-authored-by: maebeale <7607813+maebeale@users.noreply.github.com> --- Gemfile | 3 + Gemfile.lock | 8 +++ config/blazer.yml | 79 +++++++++++++++++++++ config/routes.rb | 1 + db/migrate/20260215064655_install_blazer.rb | 47 ++++++++++++ 5 files changed, 138 insertions(+) create mode 100644 config/blazer.yml create mode 100644 db/migrate/20260215064655_install_blazer.rb diff --git a/Gemfile b/Gemfile index cdd06c41f..9d088be3b 100644 --- a/Gemfile +++ b/Gemfile @@ -42,6 +42,9 @@ gem "groupdate" # Charts and graphs gem "chartkick" +# Business intelligence for database queries +gem "blazer" + # Geocoding for charts and other features gem "geocoder" diff --git a/Gemfile.lock b/Gemfile.lock index 6df8d0773..f5b1a8b6f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -131,6 +131,12 @@ GEM rack (>= 0.9.0) rouge (>= 1.0.0) bigdecimal (4.0.1) + blazer (3.3.0) + activerecord (>= 7.1) + chartkick (>= 5) + csv + railties (>= 7.1) + safely_block (>= 0.4) bootsnap (1.22.0) msgpack (~> 1.2) brakeman (8.0.2) @@ -603,6 +609,7 @@ DEPENDENCIES bcrypt (= 3.1.16) better_errors binding_of_caller! + blazer bootsnap brakeman (~> 8.0.1) bullet @@ -689,6 +696,7 @@ CHECKSUMS better_errors (2.10.1) sha256=f798f1bac93f3e775925b7fcb24cffbcf0bb62ee2210f5350f161a6b75fc0a73 bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7 binding_of_caller (1.0.1) + blazer (3.3.0) sha256=01e151091ce1e7d27c156243916b2f13109ef2ef1a16cfa62bef67f4c5fd169f bootsnap (1.22.0) sha256=5820c9d42c2efef095bee6565484bdc511f1223bf950140449c9385ae775793e brakeman (8.0.2) sha256=7b02065ce8b1de93949cefd3f2ad78e8eb370e644b95c8556a32a912a782426a builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f diff --git a/config/blazer.yml b/config/blazer.yml new file mode 100644 index 000000000..1ad0d7fb7 --- /dev/null +++ b/config/blazer.yml @@ -0,0 +1,79 @@ +# see https://github.com/ankane/blazer for more info + +data_sources: + main: + url: <%= ENV["BLAZER_DATABASE_URL"] %> + + # statement timeout, in seconds + # none by default + # timeout: 15 + + # caching settings + # can greatly improve speed + # off by default + # cache: + # mode: slow # or all + # expires_in: 60 # min + # slow_threshold: 15 # sec, only used in slow mode + + # wrap queries in a transaction for safety + # not necessary if you use a read-only user + # true by default + # use_transaction: false + + smart_variables: + # zone_id: "SELECT id, name FROM zones ORDER BY name ASC" + # period: ["day", "week", "month"] + # status: {0: "Active", 1: "Archived"} + + linked_columns: + # user_id: "/admin/users/{value}" + + smart_columns: + # user_id: "SELECT id, name FROM users WHERE id IN {value}" + +# create audits +audit: true + +# change the time zone +# time_zone: "Pacific Time (US & Canada)" + +# class name of the user model +# user_class: User + +# method name for the current user +# user_method: current_user + +# method name for the display name +# user_name: name + +# custom before_action to use for auth +# before_action_method: require_admin + +# email to send checks from +# from_email: blazer@example.org + +# webhook for Slack +# slack_webhook_url: <%= ENV["BLAZER_SLACK_WEBHOOK_URL"] %> + +check_schedules: + - "1 day" + - "1 hour" + - "5 minutes" + +# enable anomaly detection +# note: with trend, time series are sent to https://trendapi.org +# anomaly_checks: prophet / trend / anomaly_detection + +# enable forecasting +# note: with trend, time series are sent to https://trendapi.org +# forecasting: prophet / trend + +# enable map +# mapbox_access_token: <%= ENV["MAPBOX_ACCESS_TOKEN"] %> + +# enable uploads +# uploads: +# url: <%= ENV["BLAZER_UPLOADS_URL"] %> +# schema: uploads +# data_source: main diff --git a/config/routes.rb b/config/routes.rb index 2c19c6d22..1950b039b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -18,6 +18,7 @@ # mount Ckeditor::Engine, at: '/admin/ckeditor', as: 'ckeditor' apipie + mount Blazer::Engine, at: "blazer" devise_for :users, controllers: { registrations: "registrations", confirmations: "confirmations", diff --git a/db/migrate/20260215064655_install_blazer.rb b/db/migrate/20260215064655_install_blazer.rb new file mode 100644 index 000000000..2f4393e6c --- /dev/null +++ b/db/migrate/20260215064655_install_blazer.rb @@ -0,0 +1,47 @@ +class InstallBlazer < ActiveRecord::Migration[8.1] + def change + create_table :blazer_queries do |t| + t.references :creator + t.string :name + t.text :description + t.text :statement + t.string :data_source + t.string :status + t.timestamps null: false + end + + create_table :blazer_audits do |t| + t.references :user + t.references :query + t.text :statement + t.string :data_source + t.datetime :created_at + end + + create_table :blazer_dashboards do |t| + t.references :creator + t.string :name + t.timestamps null: false + end + + create_table :blazer_dashboard_queries do |t| + t.references :dashboard + t.references :query + t.integer :position + t.timestamps null: false + end + + create_table :blazer_checks do |t| + t.references :creator + t.references :query + t.string :state + t.string :schedule + t.text :emails + t.text :slack_channels + t.string :check_type + t.text :message + t.datetime :last_run_at + t.timestamps null: false + end + end +end From 2464338ae625a00218c41634d5059c69709f72e1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Feb 2026 06:49:43 +0000 Subject: [PATCH 3/6] Add Blazer authentication and foreign key constraints Co-authored-by: maebeale <7607813+maebeale@users.noreply.github.com> --- config/blazer.yml | 8 ++++---- config/initializers/blazer.rb | 13 +++++++++++++ db/migrate/20260215064655_install_blazer.rb | 16 ++++++++-------- 3 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 config/initializers/blazer.rb diff --git a/config/blazer.yml b/config/blazer.yml index 1ad0d7fb7..d7ce4eb1d 100644 --- a/config/blazer.yml +++ b/config/blazer.yml @@ -39,16 +39,16 @@ audit: true # time_zone: "Pacific Time (US & Canada)" # class name of the user model -# user_class: User +user_class: User # method name for the current user -# user_method: current_user +user_method: current_user # method name for the display name -# user_name: name +user_name: email # custom before_action to use for auth -# before_action_method: require_admin +before_action_method: require_blazer_access # email to send checks from # from_email: blazer@example.org diff --git a/config/initializers/blazer.rb b/config/initializers/blazer.rb new file mode 100644 index 000000000..773c964cb --- /dev/null +++ b/config/initializers/blazer.rb @@ -0,0 +1,13 @@ +# Blazer authentication configuration +# Only allow super_users to access Blazer +Rails.application.config.to_prepare do + Blazer::BaseController.class_eval do + before_action :authenticate_user! + + private + + def require_blazer_access + redirect_to root_path, alert: "You are not authorized to access this page." unless current_user&.super_user? + end + end +end diff --git a/db/migrate/20260215064655_install_blazer.rb b/db/migrate/20260215064655_install_blazer.rb index 2f4393e6c..49330d057 100644 --- a/db/migrate/20260215064655_install_blazer.rb +++ b/db/migrate/20260215064655_install_blazer.rb @@ -1,7 +1,7 @@ class InstallBlazer < ActiveRecord::Migration[8.1] def change create_table :blazer_queries do |t| - t.references :creator + t.references :creator, foreign_key: { to_table: :users } t.string :name t.text :description t.text :statement @@ -11,29 +11,29 @@ def change end create_table :blazer_audits do |t| - t.references :user - t.references :query + t.references :user, foreign_key: true + t.references :query, foreign_key: { to_table: :blazer_queries } t.text :statement t.string :data_source t.datetime :created_at end create_table :blazer_dashboards do |t| - t.references :creator + t.references :creator, foreign_key: { to_table: :users } t.string :name t.timestamps null: false end create_table :blazer_dashboard_queries do |t| - t.references :dashboard - t.references :query + t.references :dashboard, foreign_key: { to_table: :blazer_dashboards } + t.references :query, foreign_key: { to_table: :blazer_queries } t.integer :position t.timestamps null: false end create_table :blazer_checks do |t| - t.references :creator - t.references :query + t.references :creator, foreign_key: { to_table: :users } + t.references :query, foreign_key: { to_table: :blazer_queries } t.string :state t.string :schedule t.text :emails From bed3a453b2fdd858f777c641f0a6e839db1c6d6b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Feb 2026 06:50:14 +0000 Subject: [PATCH 4/6] Explicitly call require_blazer_access before_action Co-authored-by: maebeale <7607813+maebeale@users.noreply.github.com> --- config/initializers/blazer.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/initializers/blazer.rb b/config/initializers/blazer.rb index 773c964cb..96570c410 100644 --- a/config/initializers/blazer.rb +++ b/config/initializers/blazer.rb @@ -3,6 +3,7 @@ Rails.application.config.to_prepare do Blazer::BaseController.class_eval do before_action :authenticate_user! + before_action :require_blazer_access private From fe24632b6003fd0cbd13010f9cd131e548e189a8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Feb 2026 06:50:47 +0000 Subject: [PATCH 5/6] Improve migration consistency and audit timestamp Co-authored-by: maebeale <7607813+maebeale@users.noreply.github.com> --- db/migrate/20260215064655_install_blazer.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db/migrate/20260215064655_install_blazer.rb b/db/migrate/20260215064655_install_blazer.rb index 49330d057..cbb866477 100644 --- a/db/migrate/20260215064655_install_blazer.rb +++ b/db/migrate/20260215064655_install_blazer.rb @@ -11,11 +11,11 @@ def change end create_table :blazer_audits do |t| - t.references :user, foreign_key: true + t.references :user, foreign_key: { to_table: :users } t.references :query, foreign_key: { to_table: :blazer_queries } t.text :statement t.string :data_source - t.datetime :created_at + t.timestamp :created_at, null: false end create_table :blazer_dashboards do |t| From 542f1c48c322fe6d8a98b08431e872cceb82c74c Mon Sep 17 00:00:00 2001 From: maebeale Date: Mon, 16 Feb 2026 16:21:12 -0500 Subject: [PATCH 6/6] Add blazer gem for sql quering --- .env.sample | 1 + config/blazer.yml | 4 +- config/routes.rb | 4 +- db/migrate/20260215064655_install_blazer.rb | 8 +-- db/schema.rb | 66 ++++++++++++++++++++- 5 files changed, 75 insertions(+), 8 deletions(-) diff --git a/.env.sample b/.env.sample index a6e0f97aa..93edb89a0 100644 --- a/.env.sample +++ b/.env.sample @@ -20,3 +20,4 @@ APP_HOST=localhost:3000 RAILS_SERVE_STATIC_FILES=true ORGANIZATION_NAME=A Window Between Worlds REPLY_TO_EMAIL=umberto.user@example.com +BLAZER_DATABASE_URL=db_url # Optional if you want to use a different db for Blazer diff --git a/config/blazer.yml b/config/blazer.yml index d7ce4eb1d..5bdb4571e 100644 --- a/config/blazer.yml +++ b/config/blazer.yml @@ -2,7 +2,7 @@ data_sources: main: - url: <%= ENV["BLAZER_DATABASE_URL"] %> + url: <%= ENV["BLAZER_DATABASE_URL"].presence || ENV["DATABASE_URL"].presence || "trilogy://root@127.0.0.1/awbw_development" %> # statement timeout, in seconds # none by default @@ -48,7 +48,7 @@ user_method: current_user user_name: email # custom before_action to use for auth -before_action_method: require_blazer_access +# auth handled via Devise route constraint in routes.rb # email to send checks from # from_email: blazer@example.org diff --git a/config/routes.rb b/config/routes.rb index 1950b039b..90a47df0d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -18,7 +18,9 @@ # mount Ckeditor::Engine, at: '/admin/ckeditor', as: 'ckeditor' apipie - mount Blazer::Engine, at: "blazer" + authenticate :user, ->(user) { user.super_user? } do + mount Blazer::Engine, at: "blazer" + end devise_for :users, controllers: { registrations: "registrations", confirmations: "confirmations", diff --git a/db/migrate/20260215064655_install_blazer.rb b/db/migrate/20260215064655_install_blazer.rb index cbb866477..f0e55b43f 100644 --- a/db/migrate/20260215064655_install_blazer.rb +++ b/db/migrate/20260215064655_install_blazer.rb @@ -1,7 +1,7 @@ class InstallBlazer < ActiveRecord::Migration[8.1] def change create_table :blazer_queries do |t| - t.references :creator, foreign_key: { to_table: :users } + t.references :creator, type: :integer, foreign_key: { to_table: :users } t.string :name t.text :description t.text :statement @@ -11,7 +11,7 @@ def change end create_table :blazer_audits do |t| - t.references :user, foreign_key: { to_table: :users } + t.references :user, type: :integer, foreign_key: { to_table: :users } t.references :query, foreign_key: { to_table: :blazer_queries } t.text :statement t.string :data_source @@ -19,7 +19,7 @@ def change end create_table :blazer_dashboards do |t| - t.references :creator, foreign_key: { to_table: :users } + t.references :creator, type: :integer, foreign_key: { to_table: :users } t.string :name t.timestamps null: false end @@ -32,7 +32,7 @@ def change end create_table :blazer_checks do |t| - t.references :creator, foreign_key: { to_table: :users } + t.references :creator, type: :integer, foreign_key: { to_table: :users } t.references :query, foreign_key: { to_table: :blazer_queries } t.string :state t.string :schedule diff --git a/db/schema.rb b/db/schema.rb index 68b9fc3c4..851a6ebed 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2026_02_14_230901) do +ActiveRecord::Schema[8.1].define(version: 2026_02_16_120927) do create_table "action_text_mentions", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| t.bigint "action_text_rich_text_id", null: false t.datetime "created_at", null: false @@ -202,6 +202,62 @@ t.index ["updated_by_id"], name: "index_banners_on_updated_by_id" end + create_table "blazer_audits", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| + t.timestamp "created_at", null: false + t.string "data_source" + t.bigint "query_id" + t.text "statement" + t.integer "user_id" + t.index ["query_id"], name: "index_blazer_audits_on_query_id" + t.index ["user_id"], name: "index_blazer_audits_on_user_id" + end + + create_table "blazer_checks", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| + t.string "check_type" + t.datetime "created_at", null: false + t.integer "creator_id" + t.text "emails" + t.datetime "last_run_at" + t.text "message" + t.bigint "query_id" + t.string "schedule" + t.text "slack_channels" + t.string "state" + t.datetime "updated_at", null: false + t.index ["creator_id"], name: "index_blazer_checks_on_creator_id" + t.index ["query_id"], name: "index_blazer_checks_on_query_id" + end + + create_table "blazer_dashboard_queries", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| + t.datetime "created_at", null: false + t.bigint "dashboard_id" + t.integer "position" + t.bigint "query_id" + t.datetime "updated_at", null: false + t.index ["dashboard_id"], name: "index_blazer_dashboard_queries_on_dashboard_id" + t.index ["query_id"], name: "index_blazer_dashboard_queries_on_query_id" + end + + create_table "blazer_dashboards", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| + t.datetime "created_at", null: false + t.integer "creator_id" + t.string "name" + t.datetime "updated_at", null: false + t.index ["creator_id"], name: "index_blazer_dashboards_on_creator_id" + end + + create_table "blazer_queries", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| + t.datetime "created_at", null: false + t.integer "creator_id" + t.string "data_source" + t.text "description" + t.string "name" + t.text "statement" + t.string "status" + t.datetime "updated_at", null: false + t.index ["creator_id"], name: "index_blazer_queries_on_creator_id" + end + create_table "bookmark_annotations", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| t.text "annotation", size: :long t.integer "bookmark_id" @@ -1164,6 +1220,14 @@ add_foreign_key "age_ranges", "windows_types" add_foreign_key "banners", "users", column: "created_by_id" add_foreign_key "banners", "users", column: "updated_by_id" + add_foreign_key "blazer_audits", "blazer_queries", column: "query_id" + add_foreign_key "blazer_audits", "users" + add_foreign_key "blazer_checks", "blazer_queries", column: "query_id" + add_foreign_key "blazer_checks", "users", column: "creator_id" + add_foreign_key "blazer_dashboard_queries", "blazer_dashboards", column: "dashboard_id" + add_foreign_key "blazer_dashboard_queries", "blazer_queries", column: "query_id" + add_foreign_key "blazer_dashboards", "users", column: "creator_id" + add_foreign_key "blazer_queries", "users", column: "creator_id" add_foreign_key "bookmark_annotations", "bookmarks" add_foreign_key "bookmarks", "users" add_foreign_key "categories", "category_types"