Admin Dashboard broken with 2.3.2

AI-generated summary

Admin Dashboard broken with 2.3.2

A regression in Discourse 2.3.2 is causing a 500 error on the admin dashboard, preventing it from loading. bpetty encountered this issue after upgrading from 2.2.5 and reported a TypeError in the error logs.

joffreyjaffeux suspected that the error is caused by a nil value being returned from a code that expects a numeric value. bpetty confirmed this and provided more context, explaining that they run Discourse from a docker container and the installed version field in the dashboard has never shown the actual version.

david provided a workaround for the issue and noted that the unsupported installation method used by bpetty may cause other problems. david also suggested that overriding the version fetching functionality in a plugin is possible, but not guaranteed to work correctly with unsupported installation methods.

I’ve just upgraded from Discourse 2.2.5 to 2.3.2, and there seems to be a regression in processing admin dashboard statistics that’s resulting in a 500 error that prevents me from loading the admin dashboard. All other admin pages (and frontend Discourse pages) still load correctly.

I’ve run all database migrations with the upgrade.

Here’s the error and call stack I get in the error logs (I’m fairly certain this is the relevant error causing the 500):

TypeError (expected numeric)
/home/docker/.gem/ruby/2.5.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date/calculations.rb:104:in `-'
/home/docker/.gem/ruby/2.5.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date/calculations.rb:104:in `minus_with_duration'
/usr/src/app/app/models/admin_dashboard_general_data.rb:5:in `get_json'
/usr/src/app/app/models/admin_dashboard_data.rb:24:in `as_json'
/usr/src/app/app/models/admin_dashboard_data.rb:16:in `fetch_stats'
/usr/src/app/app/models/concerns/stats_cacheable.rb:28:in `refresh_stats'
/usr/src/app/app/models/concerns/stats_cacheable.rb:23:in `fetch_cached_stats'
/usr/src/app/app/controllers/admin/dashboard_controller.rb:19:in `general'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_controller/metal/basic_implicit_render.rb:6:in `send_action'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/abstract_controller/base.rb:194:in `process_action'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_controller/metal/rendering.rb:30:in `process_action'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/abstract_controller/callbacks.rb:42:in `block in process_action'
/home/docker/.gem/ruby/2.5.0/gems/activesupport-5.2.3/lib/active_support/callbacks.rb:132:in `run_callbacks'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/abstract_controller/callbacks.rb:41:in `process_action'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_controller/metal/rescue.rb:22:in `process_action'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_controller/metal/instrumentation.rb:34:in `block in process_action'
/home/docker/.gem/ruby/2.5.0/gems/activesupport-5.2.3/lib/active_support/notifications.rb:168:in `block in instrument'
/home/docker/.gem/ruby/2.5.0/gems/activesupport-5.2.3/lib/active_support/notifications/instrumenter.rb:23:in `instrument'
/home/docker/.gem/ruby/2.5.0/gems/activesupport-5.2.3/lib/active_support/notifications.rb:168:in `instrument'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_controller/metal/instrumentation.rb:32:in `process_action'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_controller/metal/params_wrapper.rb:256:in `process_action'
/home/docker/.gem/ruby/2.5.0/gems/activerecord-5.2.3/lib/active_record/railties/controller_runtime.rb:24:in `process_action'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/abstract_controller/base.rb:134:in `process'
/home/docker/.gem/ruby/2.5.0/gems/actionview-5.2.3/lib/action_view/rendering.rb:32:in `process'
/home/docker/.gem/ruby/2.5.0/gems/rack-mini-profiler-1.0.2/lib/mini_profiler/profiling_methods.rb:104:in `block in profile_method'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_controller/metal.rb:191:in `dispatch'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_controller/metal.rb:252:in `dispatch'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/route_set.rb:52:in `dispatch'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/route_set.rb:34:in `serve'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/mapper.rb:18:in `block in <class:Constraints>'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/mapper.rb:48:in `serve'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/router.rb:52:in `block in serve'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/router.rb:35:in `each'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/router.rb:35:in `serve'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/route_set.rb:840:in `call'
/home/docker/.gem/ruby/2.5.0/gems/rack-protection-2.0.5/lib/rack/protection/frame_options.rb:31:in `call'
/usr/src/app/lib/middleware/omniauth_bypass_middleware.rb:32:in `call'
/home/docker/.gem/ruby/2.5.0/gems/rack-2.0.7/lib/rack/tempfile_reaper.rb:15:in `call'
/home/docker/.gem/ruby/2.5.0/gems/rack-2.0.7/lib/rack/conditional_get.rb:25:in `call'
/home/docker/.gem/ruby/2.5.0/gems/rack-2.0.7/lib/rack/head.rb:12:in `call'
/usr/src/app/lib/content_security_policy/middleware.rb:12:in `call'
/usr/src/app/lib/middleware/anonymous_cache.rb:220:in `call'
/home/docker/.gem/ruby/2.5.0/gems/rack-2.0.7/lib/rack/session/abstract/id.rb:232:in `context'
/home/docker/.gem/ruby/2.5.0/gems/rack-2.0.7/lib/rack/session/abstract/id.rb:226:in `call'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/cookies.rb:670:in `call'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/callbacks.rb:28:in `block in call'
/home/docker/.gem/ruby/2.5.0/gems/activesupport-5.2.3/lib/active_support/callbacks.rb:98:in `run_callbacks'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/callbacks.rb:26:in `call'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/debug_exceptions.rb:61:in `call'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
/home/docker/.gem/ruby/2.5.0/gems/logster-2.3.0/lib/logster/middleware/reporter.rb:30:in `call'
/home/docker/.gem/ruby/2.5.0/gems/railties-5.2.3/lib/rails/rack/logger.rb:38:in `call_app'
/home/docker/.gem/ruby/2.5.0/gems/railties-5.2.3/lib/rails/rack/logger.rb:28:in `call'
/usr/src/app/config/initializers/100-quiet_logger.rb:18:in `call'
/usr/src/app/config/initializers/100-silence_logger.rb:31:in `call'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/request_id.rb:27:in `call'
/usr/src/app/lib/middleware/enforce_hostname.rb:17:in `call'
/home/docker/.gem/ruby/2.5.0/gems/rack-2.0.7/lib/rack/method_override.rb:22:in `call'
/home/docker/.gem/ruby/2.5.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/executor.rb:14:in `call'
/home/docker/.gem/ruby/2.5.0/gems/rack-2.0.7/lib/rack/sendfile.rb:111:in `call'
/home/docker/.gem/ruby/2.5.0/gems/rack-mini-profiler-1.0.2/lib/mini_profiler/profiler.rb:281:in `call'
/home/docker/.gem/ruby/2.5.0/gems/message_bus-2.2.0/lib/message_bus/rack/middleware.rb:57:in `call'
/usr/src/app/lib/middleware/request_tracker.rb:163:in `call'
/home/docker/.gem/ruby/2.5.0/gems/railties-5.2.3/lib/rails/engine.rb:524:in `call'
/home/docker/.gem/ruby/2.5.0/gems/railties-5.2.3/lib/rails/railtie.rb:190:in `public_send'
/home/docker/.gem/ruby/2.5.0/gems/railties-5.2.3/lib/rails/railtie.rb:190:in `method_missing'
/home/docker/.gem/ruby/2.5.0/gems/rack-2.0.7/lib/rack/urlmap.rb:68:in `block in call'
/home/docker/.gem/ruby/2.5.0/gems/rack-2.0.7/lib/rack/urlmap.rb:53:in `each'
/home/docker/.gem/ruby/2.5.0/gems/rack-2.0.7/lib/rack/urlmap.rb:53:in `call'
/usr/lib/ruby/vendor_ruby/phusion_passenger/rack/thread_handler_extension.rb:97:in `process_request'
/usr/lib/ruby/vendor_ruby/phusion_passenger/request_handler/thread_handler.rb:149:in `accept_and_process_next_request'
/usr/lib/ruby/vendor_ruby/phusion_passenger/request_handler/thread_handler.rb:110:in `main_loop'
/usr/lib/ruby/vendor_ruby/phusion_passenger/request_handler.rb:415:in `block (3 levels) in start_threads'
/usr/lib/ruby/vendor_ruby/phusion_passenger/utils.rb:113:in `block in create_thread_and_abort_on_exception'

Hello,

reading the stacktrace, I imagine it’s happening because this code is returning nil:

DateTime.now - nil # TypeError: expected numeric

I guess our code is not very robust here as we acknowledge it’s possible this will return nil and still use it as if it could not be nil.

@david could you have a look please ? FEATURE: Display 'last updated' on dashboard, improve release notes l… · discourse/discourse@5666316 · GitHub

5 Likes

That does look like it’s the likely problem. To give a bit more context to this, I do run Discourse from a docker container (as you might guess from the stack trace), but it’s not using the docker-compose based discourse_docker project, and the docker image I use simply downloads the tarball release directly from GitHub, instead of using a git clone, so none of the git-based logic in the application will work.

I’m quite used to that “installed version” field in the dashboard not showing the actual version I have installed. It’s been like that for as long as I can remember, though at least it didn’t break the dashboard before.

Out of curiosity, could that functionality have any kind of fallback on fetching the version from somewhere other than git? Not a big deal if not, it doesn’t really bother me that I can’t tell what version I’m running from the dashboard.

This should work around the problem for you:
FIX: Allow dashboard to load even when git version cannot be found · discourse/discourse@ca9ef58 · GitHub

You could override it in a plugin. But note that the only installation method we support is this one. Since you are using an unsupported installation method, we can’t guarantee everything will work correctly.

4 Likes

Thanks David, I’ll reach out if I have any problems with that.