diff --git a/Gemfile b/Gemfile index 462f772..7cf8556 100644 --- a/Gemfile +++ b/Gemfile @@ -153,4 +153,3 @@ gem "premailer-rails", "~> 1.12" gem "openssl", "~> 3.3" -gem "rails_pulse" diff --git a/Gemfile.lock b/Gemfile.lock index d2094a3..11926b9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -162,7 +162,6 @@ GEM countries (7.1.1) unaccent (~> 0.3) crass (1.0.6) - css-zero (1.1.15) css_parser (1.21.1) addressable csv (3.3.5) @@ -250,8 +249,6 @@ GEM fugit (>= 1.11.0) railties (>= 6.1.0) thor (>= 1.0.0) - groupdate (6.7.0) - activesupport (>= 7.1) hashid-rails (1.4.1) activerecord (>= 4.0) hashids (~> 1.0) @@ -368,9 +365,6 @@ GEM nokogiri (~> 1.13) openssl (3.3.2) ostruct (0.6.1) - pagy (43.2.2) - json - yaml paper_trail (16.0.0) activerecord (>= 6.1) request_store (~> 1.4) @@ -453,14 +447,6 @@ GEM rails-html-sanitizer (1.6.2) loofah (~> 2.21) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - rails_pulse (0.2.4) - css-zero (~> 1.1, >= 1.1.4) - groupdate (~> 6.0) - pagy (>= 8, < 44) - rails (>= 7.1.0, < 9.0.0) - ransack (~> 4.0) - request_store (~> 1.5) - turbo-rails (~> 2.0.11) rails_semantic_logger (4.17.0) rack railties (>= 5.1) @@ -475,10 +461,6 @@ GEM zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.2.1) - ransack (4.4.1) - activerecord (>= 7.2) - activesupport (>= 7.2) - i18n rdoc (6.14.0) erb psych (>= 4.0.0) @@ -610,7 +592,6 @@ GEM websocket-extensions (0.1.5) wicked (2.0.0) railties (>= 3.0.7) - yaml (0.4.0) zeitwerk (2.7.3) PLATFORMS @@ -677,7 +658,6 @@ DEPENDENCIES pundit (~> 2.5) rack-attack (~> 6.7) rails (~> 8.0.2) - rails_pulse rails_semantic_logger (~> 4.17) redcarpet (~> 3.6) rexml diff --git a/app/lib/shortcodes.rb b/app/lib/shortcodes.rb index 92b74b2..68d1e3e 100644 --- a/app/lib/shortcodes.rb +++ b/app/lib/shortcodes.rb @@ -42,8 +42,7 @@ module Shortcodes Shortcode.new(code: "USRS", label: "Backend users", controller: "backend/users", action: "index", icon: "⭢", role: :super_admin, path_override: nil), Shortcode.new(code: "JOBS", label: "Job queue", controller: "good_job/jobs", action: "index", icon: "⭢", role: :super_admin, path_override: "/backend/good_job"), Shortcode.new(code: "FLIP", label: "Feature flags", controller: "flipper/features", action: "index", icon: "⭢", role: :super_admin, path_override: "/backend/flipper"), - Shortcode.new(code: "ORWL", label: "Console audit", controller: "audits1984/audits", action: "index", icon: "⭢", role: :super_admin, path_override: "/backend/console_audit"), - Shortcode.new(code: "PLSE", label: "Rails Pulse", controller: "rails_pulse/dashboard", action: "index", icon: "⭢", role: :super_admin, path_override: "/backend/rails_pulse") + Shortcode.new(code: "ORWL", label: "Console audit", controller: "audits1984/audits", action: "index", icon: "⭢", role: :super_admin, path_override: "/backend/console_audit") ] end diff --git a/app/views/backend/static_pages/index.html.erb b/app/views/backend/static_pages/index.html.erb index 1f7d9fe..d4e74f1 100644 --- a/app/views/backend/static_pages/index.html.erb +++ b/app/views/backend/static_pages/index.html.erb @@ -21,12 +21,6 @@

  [ JOBS ]

<% end %> -
- <%= render Components::Backend::Item.new(icon: "⭢", href: "/backend/rails_pulse") do %> - Rails Pulse -

  [ PLSE ]

- <% end %> -
<%= render Components::Backend::Item.new(icon: "⭢", href: backend_users_path) do %> Manage users diff --git a/config/database.yml b/config/database.yml index 0f7b6f0..f6cb1a9 100644 --- a/config/database.yml +++ b/config/database.yml @@ -25,10 +25,6 @@ development: primary: <<: *default database: identity_vault_development - rails_pulse: - <<: *default - database: identity_vault_rails_pulse_development - migrations_paths: db/rails_pulse_migrate # The specified database role being used to connect to PostgreSQL. # To create additional roles in PostgreSQL see `$ createuser --help`. @@ -64,10 +60,6 @@ test: primary: <<: *default database: identity_vault_test - rails_pulse: - <<: *default - database: identity_vault_rails_pulse_test - migrations_paths: db/rails_pulse_migrate # As with config/credentials.yml, you never want to store sensitive information, # like your database password, in your source code. If your source code is @@ -93,16 +85,8 @@ production: primary: <<: *default database: identity_vault_prod - rails_pulse: - <<: *default - database: identity_vault_rails_pulse_production - migrations_paths: db/rails_pulse_migrate staging: primary: <<: *default database: identity_vault_staging - rails_pulse: - <<: *default - database: identity_vault_rails_pulse_staging - migrations_paths: db/rails_pulse_migrate diff --git a/config/initializers/good_job.rb b/config/initializers/good_job.rb index aaabb77..ea9d149 100644 --- a/config/initializers/good_job.rb +++ b/config/initializers/good_job.rb @@ -3,14 +3,6 @@ Rails.application.configure do expire_draft_aadhaar_verifications: { cron: "*/5 * * * *", # Run every 5 minutes class: "Verification::ExpireDraftAadhaarVerificationsJob" - }, - rails_pulse_summary: { - cron: "5 * * * *", # Run 5 minutes past every hour - class: "RailsPulse::SummaryJob" - }, - rails_pulse_cleanup: { - cron: "0 1 * * *", # Run daily at 1:00 AM - class: "RailsPulse::CleanupJob" } } config.good_job.enable_cron = true diff --git a/config/initializers/rails_pulse.rb b/config/initializers/rails_pulse.rb deleted file mode 100644 index d604baa..0000000 --- a/config/initializers/rails_pulse.rb +++ /dev/null @@ -1,208 +0,0 @@ -RailsPulse.configure do |config| - # ==================================================================================================== - # GLOBAL CONFIGURATION - # ==================================================================================================== - - # Enable or disable Rails Pulse - config.enabled = true - - # ==================================================================================================== - # THRESHOLDS - # ==================================================================================================== - # These thresholds are used to determine if a route, request, or query is slow, very slow, or critical. - # Values are in milliseconds (ms). Adjust these based on your application's performance requirements. - - # Thresholds for an individual route - config.route_thresholds = { - slow: 500, - very_slow: 1500, - critical: 3000 - } - - # Thresholds for an individual request - config.request_thresholds = { - slow: 700, - very_slow: 2000, - critical: 4000 - } - - # Thresholds for an individual database query - config.query_thresholds = { - slow: 100, - very_slow: 500, - critical: 1000 - } - - # ==================================================================================================== - # FILTERING - # ==================================================================================================== - - # Asset Tracking Configuration - # By default, Rails Pulse ignores asset requests (images, CSS, JS files) to focus on application performance. - # Set track_assets to true if you want to monitor asset delivery performance. - config.track_assets = false - - # Custom asset patterns to ignore (in addition to the built-in defaults) - # Only applies when track_assets is false. Add patterns for app-specific asset paths. - config.custom_asset_patterns = [ - # Example: ignore specific asset directories - # %r{^/uploads/}, - # %r{^/media/}, - # "/special-assets/" - ] - - # Rails Pulse Mount Path (optional) - # If Rails Pulse is mounted at a custom path, specify it here to prevent - # Rails Pulse from tracking its own requests. Leave as nil for default '/rails_pulse'. - # Examples: - # config.mount_path = "/admin/monitoring" - config.mount_path = "/backend/rails_pulse" - - # Manual route filtering - # Specify additional routes, requests, or queries to ignore from performance tracking. - # Each array can include strings (exact matches) or regular expressions. - # - # Examples: - # config.ignored_routes = ["/health_check", %r{^/admin}] - # config.ignored_requests = ["GET /status", %r{POST /api/v1/.*}] - # config.ignored_queries = ["SELECT 1", %r{FROM \"schema_migrations\"}] - - config.ignored_routes = [] - config.ignored_requests = [] - config.ignored_queries = [] - - # ==================================================================================================== - # TAGGING - # ==================================================================================================== - # Define custom tags for categorizing routes, requests, and queries. - # You can add any custom tags you want for filtering and organization. - # - # Tag names should be in present tense and describe the current state or category. - # Examples of good tag names: - # - "critical" (for high-priority endpoints) - # - "experimental" (for routes under development) - # - "deprecated" (for routes being phased out) - # - "external" (for third-party API calls) - # - "background" (for async job-related operations) - # - "admin" (for administrative routes) - # - "public" (for public-facing routes) - # - # Example configuration: - # config.tags = ["ignored", "critical", "experimental", "deprecated", "external", "admin"] - - config.tags = [ "ignored", "critical", "experimental" ] - - # ==================================================================================================== - # DATABASE CONFIGURATION - # ==================================================================================================== - # Configure Rails Pulse to use a separate database for performance monitoring data. - # This is optional but recommended for production applications to isolate performance - # data from your main application database. - # - # Uncomment and configure one of the following patterns: - - # Option 1: Separate single database for Rails Pulse - config.connects_to = { - database: { writing: :rails_pulse, reading: :rails_pulse } - } - - # Option 2: Primary/replica configuration for Rails Pulse - # config.connects_to = { - # database: { writing: :rails_pulse_primary, reading: :rails_pulse_replica } - # } - - # Don't forget to add the database configuration to config/database.yml: - # - # production: - # # ... your main database config ... - # rails_pulse: - # adapter: postgresql # or mysql2, sqlite3 - # database: myapp_rails_pulse_production - # username: rails_pulse_user - # password: <%= Rails.application.credentials.dig(:rails_pulse, :database_password) %> - # host: localhost - # pool: 5 - - # ==================================================================================================== - # AUTHENTICATION - # ==================================================================================================== - # Configure authentication to secure access to the Rails Pulse dashboard. - # Authentication is ENABLED BY DEFAULT in production environments for security. - # - # If no authentication method is configured, Rails Pulse will use HTTP Basic Auth - # with credentials from RAILS_PULSE_USERNAME (default: 'admin') and RAILS_PULSE_PASSWORD - # environment variables. Set RAILS_PULSE_PASSWORD to enable this fallback. - # - # Uncomment and configure one of the following patterns based on your authentication system: - - # Enable/disable authentication (enabled by default in production) - config.authentication_enabled = false - # we have SuperAdminConstraint in routes/ - - # Where to redirect unauthorized users - # config.authentication_redirect_path = "/" - - # Custom authentication method - choose one of the examples below: - - # Example 1: Devise with admin role check - # config.authentication_method = proc { - # unless user_signed_in? && current_user.admin? - # redirect_to main_app.root_path, alert: "Access denied" - # end - # } - - # Example 2: Custom session-based authentication - # config.authentication_method = proc { - # unless session[:user_id] && User.find_by(id: session[:user_id])&.admin? - # redirect_to main_app.login_path, alert: "Please log in as an admin" - # end - # } - - # Example 3: Warden authentication - # config.authentication_method = proc { - # warden.authenticate!(:scope => :admin) - # } - - # Example 4: Basic HTTP authentication - # config.authentication_method = proc { - # authenticate_or_request_with_http_basic do |username, password| - # username == ENV['RAILS_PULSE_USERNAME'] && password == ENV['RAILS_PULSE_PASSWORD'] - # end - # } - - # Example 5: Custom authorization check - # config.authentication_method = proc { - # current_user = User.find_by(id: session[:user_id]) - # unless current_user&.can_access_rails_pulse? - # render plain: "Forbidden", status: :forbidden - # end - # } - - # ==================================================================================================== - # DATA CLEANUP - # ==================================================================================================== - # Configure automatic cleanup of old performance data to manage database size. - # Rails Pulse provides two cleanup mechanisms that work together: - # - # 1. Time-based cleanup: Delete records older than the retention period - # 2. Count-based cleanup: Keep only the specified number of records per table - # - # Cleanup order respects foreign key constraints: - # operations → requests → queries/routes - - # Enable or disable automatic data cleanup - config.archiving_enabled = true - - # Time-based retention - delete records older than this period - config.full_retention_period = 2.weeks - - # Count-based retention - maximum records to keep per table - # After time-based cleanup, if tables still exceed these limits, - # the oldest remaining records will be deleted to stay under the limit - config.max_table_records = { - rails_pulse_requests: 10000, # HTTP requests (moderate volume) - rails_pulse_operations: 50000, # Operations within requests (high volume) - rails_pulse_routes: 1000, # Unique routes (low volume) - rails_pulse_queries: 500 # Normalized SQL queries (low volume) - } -end diff --git a/config/routes.rb b/config/routes.rb index 570d969..b9c623b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -185,7 +185,6 @@ Rails.application.routes.draw do mount GoodJob::Engine => "good_job" mount Audits1984::Engine => "/console_audit" mount Flipper::UI.app(Flipper) => "/flipper", as: :flipper - mount RailsPulse::Engine => "rails_pulse" end resources :audit_logs, only: [ :index ] get "dashboard", to: "dashboard#show", as: :dashboard diff --git a/db/rails_pulse_migrate/.keep b/db/rails_pulse_migrate/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/db/rails_pulse_migrate/20251222190545_install_rails_pulse_tables.rb b/db/rails_pulse_migrate/20251222190545_install_rails_pulse_tables.rb deleted file mode 100644 index 582770b..0000000 --- a/db/rails_pulse_migrate/20251222190545_install_rails_pulse_tables.rb +++ /dev/null @@ -1,15 +0,0 @@ -class InstallRailsPulseTables < ActiveRecord::Migration[8.0] - def change - schema_file = Rails.root.join("db", "rails_pulse_schema.rb") - - unless File.exist?(schema_file) - raise "Rails Pulse schema file not found at #{schema_file}" - end - - say_with_time "Loading Rails Pulse schema from #{schema_file}" do - load schema_file - say "Rails Pulse tables created successfully" - say "The schema file #{schema_file} remains as your single source of truth" - end - end -end diff --git a/db/rails_pulse_schema.rb b/db/rails_pulse_schema.rb deleted file mode 100644 index fea512e..0000000 --- a/db/rails_pulse_schema.rb +++ /dev/null @@ -1,116 +0,0 @@ -# This file is auto-generated from the current state of the database. Instead -# of editing this file, please use the migrations feature of Active Record to -# incrementally modify your database, and then regenerate this schema definition. -# -# This file is the source Rails uses to define your schema when running `bin/rails -# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to -# be faster and is potentially less error prone than running all of your -# migrations from scratch. Old migrations may fail to apply correctly if those -# migrations use external dependencies or application code. -# -# It's strongly recommended that you check this file into your version control system. - -ActiveRecord::Schema[8.0].define(version: 2025_12_22_190545) do - # These are extensions that must be enabled in order to support this database - enable_extension "pg_catalog.plpgsql" - - create_table "rails_pulse_operations", force: :cascade do |t| - t.bigint "request_id", null: false, comment: "Link to the request" - t.bigint "query_id", comment: "Link to the normalized SQL query" - t.string "operation_type", null: false, comment: "Type of operation (e.g., database, view, gem_call)" - t.string "label", null: false, comment: "Descriptive name (e.g., SELECT FROM users WHERE id = 1, render layout)" - t.decimal "duration", precision: 15, scale: 6, null: false, comment: "Operation duration in milliseconds" - t.string "codebase_location", comment: "File and line number (e.g., app/models/user.rb:25)" - t.float "start_time", default: 0.0, null: false, comment: "Operation start time in milliseconds" - t.datetime "occurred_at", precision: nil, null: false, comment: "When the request started" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["created_at", "query_id"], name: "idx_operations_for_aggregation" - t.index ["created_at"], name: "idx_operations_created_at" - t.index ["occurred_at", "duration", "operation_type"], name: "index_rails_pulse_operations_on_time_duration_type" - t.index ["occurred_at"], name: "index_rails_pulse_operations_on_occurred_at" - t.index ["operation_type"], name: "index_rails_pulse_operations_on_operation_type" - t.index ["query_id", "duration", "occurred_at"], name: "index_rails_pulse_operations_query_performance" - t.index ["query_id", "occurred_at"], name: "index_rails_pulse_operations_on_query_and_time" - t.index ["query_id"], name: "index_rails_pulse_operations_on_query_id" - t.index ["request_id"], name: "index_rails_pulse_operations_on_request_id" - end - - create_table "rails_pulse_queries", force: :cascade do |t| - t.string "normalized_sql", limit: 1000, null: false, comment: "Normalized SQL query string (e.g., SELECT * FROM users WHERE id = ?)" - t.datetime "analyzed_at", comment: "When query analysis was last performed" - t.text "explain_plan", comment: "EXPLAIN output from actual SQL execution" - t.text "issues", comment: "JSON array of detected performance issues" - t.text "metadata", comment: "JSON object containing query complexity metrics" - t.text "query_stats", comment: "JSON object with query characteristics analysis" - t.text "backtrace_analysis", comment: "JSON object with call chain and N+1 detection" - t.text "index_recommendations", comment: "JSON array of database index recommendations" - t.text "n_plus_one_analysis", comment: "JSON object with enhanced N+1 query detection results" - t.text "suggestions", comment: "JSON array of optimization recommendations" - t.text "tags", comment: "JSON array of tags for filtering and categorization" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["normalized_sql"], name: "index_rails_pulse_queries_on_normalized_sql", unique: true - end - - create_table "rails_pulse_requests", force: :cascade do |t| - t.bigint "route_id", null: false, comment: "Link to the route" - t.decimal "duration", precision: 15, scale: 6, null: false, comment: "Total request duration in milliseconds" - t.integer "status", null: false, comment: "HTTP status code (e.g., 200, 500)" - t.boolean "is_error", default: false, null: false, comment: "True if status >= 500" - t.string "request_uuid", null: false, comment: "Unique identifier for the request (e.g., UUID)" - t.string "controller_action", comment: "Controller and action handling the request (e.g., PostsController#show)" - t.datetime "occurred_at", precision: nil, null: false, comment: "When the request started" - t.text "tags", comment: "JSON array of tags for filtering and categorization" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["created_at", "route_id"], name: "idx_requests_for_aggregation" - t.index ["created_at"], name: "idx_requests_created_at" - t.index ["occurred_at"], name: "index_rails_pulse_requests_on_occurred_at" - t.index ["request_uuid"], name: "index_rails_pulse_requests_on_request_uuid", unique: true - t.index ["route_id", "occurred_at"], name: "index_rails_pulse_requests_on_route_id_and_occurred_at" - t.index ["route_id"], name: "index_rails_pulse_requests_on_route_id" - end - - create_table "rails_pulse_routes", force: :cascade do |t| - t.string "method", null: false, comment: "HTTP method (e.g., GET, POST)" - t.string "path", null: false, comment: "Request path (e.g., /posts/index)" - t.text "tags", comment: "JSON array of tags for filtering and categorization" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["method", "path"], name: "index_rails_pulse_routes_on_method_and_path", unique: true - end - - create_table "rails_pulse_summaries", force: :cascade do |t| - t.datetime "period_start", null: false, comment: "Start of the aggregation period" - t.datetime "period_end", null: false, comment: "End of the aggregation period" - t.string "period_type", null: false, comment: "Aggregation period type: hour, day, week, month" - t.string "summarizable_type", null: false - t.bigint "summarizable_id", null: false, comment: "Link to Route or Query" - t.integer "count", default: 0, null: false, comment: "Total number of requests/operations" - t.float "avg_duration", comment: "Average duration in milliseconds" - t.float "min_duration", comment: "Minimum duration in milliseconds" - t.float "max_duration", comment: "Maximum duration in milliseconds" - t.float "p50_duration", comment: "50th percentile duration" - t.float "p95_duration", comment: "95th percentile duration" - t.float "p99_duration", comment: "99th percentile duration" - t.float "total_duration", comment: "Total duration in milliseconds" - t.float "stddev_duration", comment: "Standard deviation of duration" - t.integer "error_count", default: 0, comment: "Number of error responses (5xx)" - t.integer "success_count", default: 0, comment: "Number of successful responses" - t.integer "status_2xx", default: 0, comment: "Number of 2xx responses" - t.integer "status_3xx", default: 0, comment: "Number of 3xx responses" - t.integer "status_4xx", default: 0, comment: "Number of 4xx responses" - t.integer "status_5xx", default: 0, comment: "Number of 5xx responses" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["created_at"], name: "index_rails_pulse_summaries_on_created_at" - t.index ["period_type", "period_start"], name: "index_rails_pulse_summaries_on_period" - t.index ["summarizable_type", "summarizable_id", "period_type", "period_start"], name: "idx_pulse_summaries_unique", unique: true - t.index ["summarizable_type", "summarizable_id"], name: "index_rails_pulse_summaries_on_summarizable" - end - - add_foreign_key "rails_pulse_operations", "rails_pulse_queries", column: "query_id" - add_foreign_key "rails_pulse_operations", "rails_pulse_requests", column: "request_id" - add_foreign_key "rails_pulse_requests", "rails_pulse_routes", column: "route_id" -end