diff --git a/app/assets/javascripts/ui.js b/app/assets/javascripts/ui.js index b7ef28e7e7..5a1776babe 100644 --- a/app/assets/javascripts/ui.js +++ b/app/assets/javascripts/ui.js @@ -446,6 +446,17 @@ $(document).on('turbo:load', function () { BK.s('comment')[0].scrollIntoView() }) + $('[data-behavior~=expand_inspector]').on('click', e => { + const inspector = $('#inspector-container') + if (inspector.parent().hasClass('inspector--expanded')) { + inspector.parent().removeClass('inspector--expanded') + inspector.slideUp() + } else { + inspector.parent().addClass('inspector--expanded') + inspector.slideDown() + } + }) + $('.input-group').on('click', e => { // focus on the input when clicking on the input-group e.currentTarget.querySelector('input').focus() diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index dd6d6cb60f..f94e400b70 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -1407,6 +1407,36 @@ def referral_program_create def active_teenagers_leaderboard end + def inspect + redirect_to inspect_resource_admin_index_path(resource: params[:resource_type], id: params[:resource_id]) + end + + def inspect_resource + @resource_type = params[:resource] + @resource_id = params[:id] + + Zeitwerk::Loader.eager_load_all + + # get all named classes extending ApplicationRecord + @all_resource_types = ObjectSpace.each_object(Class).select { |c| c < ApplicationRecord }.select(&:name).map(&:name) + @associations = {} + + begin + @resource = Inspector.find_object(@resource_type, @resource_id) + + if @resource.nil? + flash.now[:error] = "Resource not found." and return + end + + rescue NameError + flash.now[:error] = "Invalid resource type." + rescue ActiveRecord::RecordNotFound => e + flash.now[:error] = "Resource not found." + ensure + @associations = Inspector.find_relations(@resource) if @resource.present? + end + end + private def stream_data(content_type, filename, data, download = true) diff --git a/app/models/inspector.rb b/app/models/inspector.rb new file mode 100644 index 0000000000..bc76c844f2 --- /dev/null +++ b/app/models/inspector.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module Inspector + def self.resource_types + Zeitwerk::Loader.eager_load_all + + descendants = ObjectSpace.each_object(Class).select { |c| c < ApplicationRecord } + + descendants.filter_map(&:name) + end + + def self.find_relations(object) + if object.class < ApplicationRecord + object = object.attributes + end + + resource_type_keys = resource_types.index_by { |type| "#{type.underscore}_id" } + + association_keys = object.keys.select do |key| + key.ends_with?("_id") && object[key].present? && resource_type_keys[key].present? + end + + association_keys.map do |key| + [resource_type_keys[key], object[key]] + end.to_h + end + + def self.find_object(resource, id) + return nil unless resource.in?(resource_types) + + klass = resource.constantize + + object = klass.find_by(id: id) + object ||= klass.try(:find_by_public_id, id) + object ||= klass.try(:find_by_hashid, id) + object ||= klass.find_by(hcb_code: id) if "hcb_code".in? klass.columns.collect(&:name) + object ||= klass.try(:friendly)&.find(id, allow_nil: true) + object ||= klass.try(:find_by_public_id, id) + object ||= klass.try(:search_name, id) + object ||= klass.try(:search_memo, id) + object ||= klass.try(:search_recipient, id) + object ||= klass.try(:search_description, id) + + object = object.first if object.class < Enumerable + + object + end + + def self.object_for(path) + route = Rails.application.routes.recognize_path(path) + model_name = route[:controller].singularize.classify + + if model_name.in?(resource_types) + find_object(model_name, route["#{model_name.underscore}_id".to_sym] || route[:id]) + end + end +end diff --git a/app/views/admin/inspect_resource.html.erb b/app/views/admin/inspect_resource.html.erb new file mode 100644 index 0000000000..53875989ec --- /dev/null +++ b/app/views/admin/inspect_resource.html.erb @@ -0,0 +1,21 @@ +<%= turbo_frame_tag "inspect_frame" do %> + + <%= form_with url: inspect_admin_index_path, method: :get, local: true, data: { turbo_frame: "inspect_frame" }, class: "flex flex-row items-center gap-2 mb-3 border border-smoke border-b-1 rounded-xl" do |form| %> + <%= form.select :resource_type, options_for_select(@all_resource_types, @resource_type), {}, { class: "mb-0 !border-none !bg-transparent" } %> +
+ <%= form.text_field :resource_id, value: @resource_id, class: "mb-0 !border-none !bg-transparent flex-grow", style: "max-width: unset!important" %> +