diff --git a/app/controllers/config_controller.rb b/app/controllers/config_controller.rb new file mode 100644 index 00000000..d507e1c9 --- /dev/null +++ b/app/controllers/config_controller.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class ConfigController < ApplicationController + def index + end + + def show + @definition = Sites.from_enum(params[:id]) + end + + def modify + custom_config = params.fetch(:config, {}).permit! + custom_config[:scraper_request_rate_limit] = custom_config[:scraper_request_rate_limit].tr(",", ".").to_f if custom_config[:scraper_request_rate_limit] + + Config.write_custom_config(custom_config) + Config.force_reload + redirect_back fallback_location: config_index_path + end +end diff --git a/app/logical/config.rb b/app/logical/config.rb index e4873f27..f3646327 100644 --- a/app/logical/config.rb +++ b/app/logical/config.rb @@ -15,6 +15,15 @@ def custom_config_path Rails.root.join("config/reverser_custom_config.yml") end + def merge_custom_config(new_values) + custom_config.merge(new_values).transform_keys(&:to_s) + end + + def write_custom_config(new_values) + data = Psych.safe_dump(merge_custom_config(new_values)) + File.write(custom_config_path, data) + end + def force_reload @default_config = nil @custom_config = nil @@ -27,7 +36,10 @@ def missing_values def method_missing(method) raise NoMethodError, "Unknown config #{method}" unless respond_to_missing?(method) - if custom_config.key?(method) + bool_key = method.to_s.delete_suffix("?").to_sym + if custom_config.key?(bool_key) && method.end_with?("?") + custom_config[bool_key] == "true" + elsif custom_config.key?(method) custom_config[method] else default_config[method] diff --git a/app/logical/sites/scraper_definition.rb b/app/logical/sites/scraper_definition.rb index 132a5753..52e91496 100644 --- a/app/logical/sites/scraper_definition.rb +++ b/app/logical/sites/scraper_definition.rb @@ -23,6 +23,8 @@ def missing_config_keys @scraper.required_config_keys.select { |key| Config.send(key).blank? } end + delegate :all_config_keys, to: :@scraper + def cached_values @scraper.cached_methods.filter_map do |method| key = @scraper.cache_key(method) diff --git a/app/views/application/_navbar.html.erb b/app/views/application/_navbar.html.erb index 2a3714eb..2ae30383 100644 --- a/app/views/application/_navbar.html.erb +++ b/app/views/application/_navbar.html.erb @@ -8,6 +8,7 @@ <%= nav_link_to "Submission Files", submission_files_path %> <%= nav_link_to "Archive Importer", new_archive_import_path %> <%= nav_link_to "Stats", stats_path %> + <%= nav_link_to "Config", config_index_path %> <%= nav_link_to "Selenium", "http://localhost:#{DockerEnv.exposed_vnc_port}" %> <%= nav_link_to "GoodJob", "/good_job" %> diff --git a/app/views/config/_secondary_links.html.erb b/app/views/config/_secondary_links.html.erb new file mode 100644 index 00000000..383c2241 --- /dev/null +++ b/app/views/config/_secondary_links.html.erb @@ -0,0 +1 @@ +<%= subnav_link_to "Listing", config_index_path %> diff --git a/app/views/config/index.html.erb b/app/views/config/index.html.erb new file mode 100644 index 00000000..5dc8df17 --- /dev/null +++ b/app/views/config/index.html.erb @@ -0,0 +1,13 @@ +

General Settings

+ +<%= simple_form_for(:config, url: modify_config_index_path, method: :put) do |f| %> + <%= f.input :app_name, input_html: { value: Config.app_name } %> + <%= f.input :log_scraper_requests, as: :select, selected: Config.log_scraper_requests? %> + <%= f.input :scraper_request_rate_limit, input_html: { value: Config.scraper_request_rate_limit } %> + <%= f.input :time_zone, collection: ActiveSupport::TimeZone.all.map { |tz| [tz.to_s, tz.name] }, selected: Config.time_zone %> + <%= f.submit %> +<% end %> + +<% Sites.scraper_definitions.each do |definition| %> +
<%= link_to "#{definition.display_name} settings", config_path(definition.enum_value) %>
+<% end %> diff --git a/app/views/config/show.html.erb b/app/views/config/show.html.erb new file mode 100644 index 00000000..fade6492 --- /dev/null +++ b/app/views/config/show.html.erb @@ -0,0 +1,9 @@ +

Settings for <%= @definition.display_name %>

+<%= link_to @definition.homepage, @definition.homepage %>
+<%= simple_form_for(:config, url: modify_config_index_path, method: :put) do |f| %> + <% @definition.all_config_keys.each do |key| %> + <%= f.input key, label: key.to_s.delete_prefix("#{@definition.enum_value}_"), input_html: { value: Config.send(key) } %> + <% end %> + <%= f.input "#{@definition.enum_value}_disabled", as: :select, label: "disabled", selected: Config.send("#{@definition.enum_value}_disabled?").to_s %> + <%= f.submit %> +<% end %> diff --git a/app/views/stats/index.html.erb b/app/views/stats/index.html.erb index c438250a..b2489c8b 100644 --- a/app/views/stats/index.html.erb +++ b/app/views/stats/index.html.erb @@ -15,7 +15,7 @@ <% @definitions.each do |definition| %> <% entry = @counts[definition.enum_value] || {} %> - <%= definition.display_name %> + <%= link_to_if(definition.enum_value != "manual", definition.display_name, config_path(definition.enum_value)) %> <%= link_to(entry["artist_count"] || 0, artists_path(search: { site_type: definition.enum_value })) %> <%= link_to(entry["url_count"] || 0, artist_urls_path(search: { site_type: definition.enum_value })) %> <%= link_to(entry["submission_count"] || 0, submission_files_path(search: { site_type: definition.enum_value })) %> diff --git a/config/routes.rb b/config/routes.rb index 18f372f2..ca5e5c77 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -38,5 +38,10 @@ resources :log_events, only: %i[index show] resources :archive_imports, only: %i[new create] resources :stats, only: :index + resources :config, controller: "config", only: %i[index show] do + collection do + put :modify + end + end root to: "artists#index" end diff --git a/docker-compose.yml b/docker-compose.yml index 0c5d1ed4..cd13a287 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -79,6 +79,8 @@ services: tests: image: reverser + environment: + EXPOSED_VNC_PORT: "123" volumes: - .:/app entrypoint: bundle exec rails test diff --git a/test/controllers/config_controller_test.rb b/test/controllers/config_controller_test.rb new file mode 100644 index 00000000..23b8422e --- /dev/null +++ b/test/controllers/config_controller_test.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "test_helper" + +class ConfigControllerTest < ActionDispatch::IntegrationTest + test "index renders" do + get config_index_path + assert_response :success + end + + test "show renders" do + get config_path("twitter") + assert_response :success + end +end diff --git a/test/logical/config_test.rb b/test/logical/config_test.rb index 1ff381e3..c9ae9e0d 100644 --- a/test/logical/config_test.rb +++ b/test/logical/config_test.rb @@ -4,7 +4,7 @@ class ConfigTest < ActiveSupport::TestCase setup do - Config.stubs(:default_config).returns(app_name: "DefaultName") + Config.stubs(:default_config).returns(app_name: "DefaultName", bool?: true) Config.force_reload end @@ -19,6 +19,7 @@ def stub_custom_config(**params, &) f.flush Config.unstub(:custom_config) yield + Config.force_reload end end @@ -48,4 +49,20 @@ def stub_custom_config(**params, &) assert_equal("OverwrittenName", Config.app_name) end end + + it "merges the config correctly" do + stub_custom_config(app_name: "OverwrittenName", other_key: "abc") do + assert_equal({ "app_name" => "NewName", "other_key" => "abc" }, Config.merge_custom_config("app_name" => "NewName")) + assert_equal({ "app_name" => "NewName", "other_key" => "abc" }, Config.merge_custom_config(app_name: "NewName")) + end + end + + it "handles booleans" do + stub_custom_config(bool: "true") do + assert_predicate(Config, :bool?) + end + stub_custom_config(bool: "false") do + assert_not_predicate(Config, :bool?) + end + end end