+<% end %>
diff --git a/config/routes.rb b/config/routes.rb
index fb6170803..0f9fc8ef0 100755
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -116,6 +116,9 @@
resources :websocket_tester, only: [ :index ] do
post 'test_background_job', on: :collection
end
+ resources :query_runner, only: [ :index ] do
+ post 'run_queries', on: :collection
+ end
end
# preview routes for mailers
From ae6ba9f77094ceee5b1a02bd670008afa56be9e0 Mon Sep 17 00:00:00 2001
From: Eric Pugh
Date: Fri, 5 Jul 2024 14:34:09 +0200
Subject: [PATCH 006/120] good progress on TDD based approach to the fetch
service.
---
.../admin/query_runner_controller.rb | 10 +-
app/controllers/proxy_controller.rb | 11 +
app/jobs/query_runner_job.rb | 34 ++-
app/services/fetch_service.rb | 267 +++++++++---------
app/services/snapshot_manager.rb | 4 +-
config/routes.rb | 2 +-
test/fixtures/search_endpoints.yml | 2 -
test/lib/solr_arg_parser_test.rb | 2 +
test/services/fetch_service_test.rb | 126 +++++++++
test/support/webmock.rb | 29 ++
10 files changed, 328 insertions(+), 159 deletions(-)
create mode 100644 test/services/fetch_service_test.rb
diff --git a/app/controllers/admin/query_runner_controller.rb b/app/controllers/admin/query_runner_controller.rb
index f514d0055..b1bf9a0c6 100644
--- a/app/controllers/admin/query_runner_controller.rb
+++ b/app/controllers/admin/query_runner_controller.rb
@@ -3,16 +3,16 @@
module Admin
class QueryRunnerController < ApplicationController
before_action :set_case, only: [ :run_queries ]
-
+
def index
-
end
def run_queries
- @case = Case.find(params["case_id"]) # any case is accessible!
- @try = @case.tries.where(try_number: params["try_number"]).first
+ @case = Case.find(params['case_id']) # any case is accessible!
+ @try = @case.tries.where(try_number: params['try_number']).first
QueryRunnerJob.perform_later @case, @try
- redirect_to admin_query_runner_index_path, notice: "Query Runner Job was queued up for case #{@case.case_name} and try #{@try.name}."
+ redirect_to admin_query_runner_index_path,
+ notice: "Query Runner Job was queued up for case #{@case.case_name} and try #{@try.name}."
end
end
end
diff --git a/app/controllers/proxy_controller.rb b/app/controllers/proxy_controller.rb
index 2afc84c51..90ecd9b85 100644
--- a/app/controllers/proxy_controller.rb
+++ b/app/controllers/proxy_controller.rb
@@ -115,6 +115,17 @@ def proxy_url_params
params.require(:url)
end
+ private
+
+ def rack_header? header_name
+ predefined_rack_headers = %w[
+ HTTP_VERSION HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
+ HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_CONNECTION HTTP_HOST
+ HTTP_REFERER HTTP_USER_AGENT HTTP_X_REQUEST_ID
+ ]
+
+ predefined_rack_headers.include?(header_name)
+ end
end
# rubocop:enable Layout/LineLength
# rubocop:enable Metrics/AbcSize
diff --git a/app/jobs/query_runner_job.rb b/app/jobs/query_runner_job.rb
index 9d49eed01..5a84fc8ea 100644
--- a/app/jobs/query_runner_job.rb
+++ b/app/jobs/query_runner_job.rb
@@ -6,11 +6,11 @@
class QueryRunnerJob < ApplicationJob
queue_as :bulk_processing
-
- def perform acase, try
+ # rubocop:disable Metrics/MethodLength
+ def perform acase, _try
query_count = acase.queries.count
-
+
acase.queries.each_with_index do |query, counter|
Turbo::StreamsChannel.broadcast_render_to(
:notifications,
@@ -18,23 +18,23 @@ def perform acase, try
partial: 'admin/query_runner/notification',
locals: { query: query, query_count: query_count, counter: counter }
)
-
-
sleep(2)
end
-
+
Turbo::StreamsChannel.broadcast_render_to(
:notifications,
target: 'notifications',
partial: 'admin/query_runner/notification',
locals: { query: nil, query_count: query_count, counter: 0 }
)
-
-
end
-
-
+ # rubocop:enable Metrics/MethodLength
+
+ # rubocop:disable Metrics/AbcSize
+ # rubocop:disable Metrics/CyclomaticComplexity
+ # rubocop:disable Metrics/MethodLength
+ # rubocop:disable Metrics/PerceivedComplexity
def fetch
excluded_keys = [ :url, :action, :controller, :proxy_debug ]
@@ -103,7 +103,8 @@ def fetch
response = connection.post do |req|
req.path = uri.path
query_params = request.query_parameters.except(*excluded_keys)
- body_params = request.request_parameters.except(*query_params.keys) # not sure about this and the request.raw_post
+ # not sure about this and the request.raw_post
+ body_params = request.request_parameters.except(*query_params.keys)
query_params.each do |param|
req.params[param.first] = param.second
end
@@ -117,14 +118,17 @@ def fetch
begin
data = JSON.parse(response.body)
- return {json: data, status: response.status}
+ { json: data, status: response.status }
rescue JSON::ParserError
# sometimes the API is returning plain old text, like a "Unauthorized" type message.
- return {json: {response: response.body}, status: response.status}
+ { json: { response: response.body }, status: response.status }
end
rescue Faraday::ConnectionFailed => e
- return {json: { proxy_error: e.message }, status: :internal_server_error}
+ { json: { proxy_error: e.message }, status: :internal_server_error }
end
end
-
+ # rubocop:enable Metrics/AbcSize
+ # rubocop:enable Metrics/CyclomaticComplexity
+ # rubocop:enable Metrics/MethodLength
+ # rubocop:enable Metrics/PerceivedComplexity
end
diff --git a/app/services/fetch_service.rb b/app/services/fetch_service.rb
index 29953a292..4a9e7f7c8 100644
--- a/app/services/fetch_service.rb
+++ b/app/services/fetch_service.rb
@@ -5,165 +5,164 @@ class FetchService
attr_reader :logger, :options
- def initialize url,proxy_debug, opts = {}
+ def initialize opts = {}
default_options = {
- logger: Rails.logger
+ logger: Rails.logger,
+ snapshot_limit: 10,
+ debug_mode: false,
}
- @options = default_options.merge(opts.deep_symbolize_keys)
+ @options = default_options.merge(opts)
- @url = url
- @proxy_debug = proxy_debug
@logger = @options[:logger]
end
- # rubocop:disable Metrics/AbcSize
- # rubocop:disable Metrics/CyclomaticComplexity
- # rubocop:disable Metrics/MethodLength
- # rubocop:disable Metrics/PerceivedComplexity
- def validate
- params_to_use = @data_to_process
- scorer_name = params_to_use[:scorer][:name]
- scorer = Scorer.find_by(name: scorer_name)
- if scorer.nil?
- @book.errors.add(:scorer, "with name '#{scorer_name}' needs to be migrated over first.")
- else
- @book.scorer = scorer
- end
+ def begin acase, atry
+ @case = acase
+ @try = atry
- selection_strategy_name = params_to_use[:selection_strategy][:name]
- selection_strategy = SelectionStrategy.find_by(name: selection_strategy_name)
- if selection_strategy.nil?
- @book.errors.add(:selection_strategy,
- "Selection strategy with name '#{selection_strategy_name}' needs to be migrated over first.")
- else
- @book.selection_strategy = selection_strategy
- end
-
- if params_to_use[:query_doc_pairs]
- list_of_emails_of_users = []
- params_to_use[:query_doc_pairs].each do |query_doc_pair|
- next unless query_doc_pair[:judgements]
-
- query_doc_pair[:judgements].each do |judgement|
- list_of_emails_of_users << judgement[:user_email] if judgement[:user_email].present?
- end
- end
- list_of_emails_of_users.uniq!
- list_of_emails_of_users.each do |email|
- unless User.exists?(email: email)
- if options[:force_create_users]
- User.invite!({ email: email, password: '', skip_invitation: true }, @current_user)
- else
- @book.errors.add(:base, "User with email '#{email}' needs to be migrated over first.")
- end
- end
- end
- end
+ @snapshot = @case.snapshots.build(name: 'Fetch [BEGUN]')
+ @snapshot.scorer = @case.scorer
+ @snapshot.try = @try
+ @snapshot.save!
+ @snapshot
end
- def fetch
- excluded_keys = [ :url, :action, :controller, :proxy_debug ]
-
- url_param = @url
-
- proxy_debug = 'true' == @proxy_debug
+ def store_query_results query, docs
+ snapshot_query = @snapshot.snapshot_queries.create(query: query, number_of_results: results.count)
- uri = URI.parse(url_param)
- url_without_path = "#{uri.scheme}://#{uri.host}"
- url_without_path += ":#{uri.port}" unless uri.port.nil?
+ snapshot_manager = SnapshotManager.new(@snapshot)
+ snapshot_manager.setup_docs_for_query(snapshot_query, docs)
+ snapshot_query
+ end
- connection = Faraday.new(url: url_without_path) do |faraday|
- # Configure the connection options, such as headers or middleware
- faraday.response :follow_redirects
- faraday.response :logger, nil, { headers: proxy_debug, bodies: proxy_debug, errors: !Rails.env.test? }
- faraday.ssl.verify = false
- faraday.request :url_encoded
+ def complete
+ @snapshot.name = 'Fetch [COMPLETED]'
+ @snapshot.save!
- matching_headers = request.headers.select { |name, _| name.start_with?('HTTP') && !rack_header?(name) }
+ # Keep the first snapshot, and the most recents, deleting the ones out of the middle.
+ # Not the best sampling!
+ snapshot_to_delete = @case.snapshots[1..((@options[:snapshot_limit] * -1) + @case.snapshots.count)]
+ snapshot_to_delete&.each(&:destroy)
+ end
- matching_headers.each do |name, value|
- converted_name = name.sub('HTTP_', '')
- converted_name = converted_name.tr('_', '-')
- faraday.headers[converted_name] = value
- end
+ # rubocop:disable Metrics/MethodLength
+ def get_connection url, debug_mode, credentials, custom_headers
+ if @connection.nil?
+ @connection = Faraday.new(url: url) do |faraday|
+ # Configure the connection options, such as headers or middleware
+ faraday.response :follow_redirects
+ faraday.response :logger, nil, { headers: debug_mode, bodies: debug_mode, errors: !Rails.env.test? }
+ faraday.ssl.verify = false
+ # faraday.request :url_encoded
+
+ # matching_headers = request.headers.select { |name, _| name.start_with?('HTTP') && !rack_header?(name) }
+
+ # matching_headers.each do |name, value|
+ # converted_name = name.sub('HTTP_', '')
+ # converted_name = converted_name.tr('_', '-')
+ # faraday.headers[converted_name] = value
+ # end
+
+ faraday.headers['Content-Type'] = 'application/json'
+ unless credentials.nil?
+ username, password = credentials.split(':')
+ faraday.headers['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
+ end
- faraday.headers['Content-Type'] = 'application/json'
- has_credentials = !uri.userinfo.nil?
- if has_credentials
- username, password = uri.userinfo.split(':')
- faraday.headers['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
+ unless custom_headers.nil?
+ puts JSON.parse(custom_headers).to_h
+ JSON.parse(custom_headers).to_h.each do |key, value|
+ faraday.headers[key] = value
+ end
+ end
+ faraday.adapter Faraday.default_adapter
end
- faraday.adapter Faraday.default_adapter
end
+ @connection
+ end
+ # rubocop:enable Metrics/MethodLength
- begin
- if request.get?
- response = connection.get do |req|
- req.path = uri.path
- query_params = request.query_parameters.except(*excluded_keys)
- body_params = request.request_parameters.except(*query_params.keys)
-
- query_params.each do |param|
- req.params[param.first] = param.second
- end
-
- # the url parameter often has a format like
- # http://myserver.com/search?query=text, and when this is passed in
- # we get http://localhost:3000/proxy/fetch?url=http://myserver.com/search?query=text&rows=10
- # which means the parameter "query=text" is lost because the URL is parsed and this part is dropped,
- # so here we add this one parameter back in if we have it.
- if url_param.include?('?')
- # sometimes our url looks like http://myserver.com/search?q=tiger
- # But it could also be http://myserver.com/search?q=tiger? and that needs handling via the special .split
- extra_query_param = url_param.split('?', 2).last.split('=')
-
- req.params[extra_query_param.first] = extra_query_param.second
- end
- unless body_params.empty?
-
- json_query = body_params.first.first
- req.body = json_query
+ # rubocop:disable Metrics/AbcSize
+ # rubocop:disable Metrics/CyclomaticComplexity
+ # rubocop:disable Metrics/MethodLength
+ # rubocop:disable Metrics/PerceivedComplexity
+ def create_request atry, query
+ search_endpoint = atry.search_endpoint
+ debug_mode = true
+ connection = get_connection search_endpoint.endpoint_url, debug_mode, search_endpoint.basic_auth_credential,
+ search_endpoint.custom_headers
+
+ http_verb = search_endpoint.api_method # .to_sym
+
+ # TODO: Make api_method a enum
+ http_verb = :get if 'JSONP' == http_verb
+ http_verb = :get if 'GET' == http_verb
+ http_verb = :post if 'POST' == http_verb
+ http_verb = :put if 'PUT' == http_verb
+ puts "Running query with #{http_verb}"
+
+ args = atry.args
+
+ response = nil
+ # response = connection.run_request(http_verb, search_endpoint.endpoint_url) do |req|
+ case http_verb
+ when :get
+ response = connection.get do |req|
+ req.url search_endpoint.endpoint_url
+ args.each_value do |value|
+ value.map! { |val| val.gsub("\#$query##", query.query_text) }
+
+ puts "Here is the key: #{key}"
+ puts "Here is the value: #{value}"
+ # if value.is_a?(Array)
+ # very unsure how we are handling the value, it may always be a array?
+ value.each do |item|
+ req.params[key] = item
end
end
- elsif request.post?
- response = connection.post do |req|
- req.path = uri.path
- query_params = request.query_parameters.except(*excluded_keys)
- body_params = request.request_parameters.except(*query_params.keys) # not sure about this and the request.raw_post
- query_params.each do |param|
- req.params[param.first] = param.second
- end
- unless body_params.empty?
- json_query = request.raw_post
- req.body = json_query
- end
- end
+ # Add any additional headers, params, or other options as needed
end
-
- begin
- data = JSON.parse(response.body)
- render json: data, status: response.status
- rescue JSON::ParserError
- # sometimes the API is returning plain old text, like a "Unauthorized" type message.
- render plain: response.body, status: response.status
+ when :post
+ response = connection.post do |req|
+ req.url search_endpoint.endpoint_url
+ args = replace_values(args, query.query_text)
+ req.body = args
+ # Add any additional headers, params, or other options as needed
+ end
+ when :put
+ response = connection.put do |req|
+ req.url search_endpoint.endpoint_url
+ args = replace_values(args, query.query_text)
+ req.body = args
+ # Add any additional headers, params, or other options as needed
end
- rescue Faraday::ConnectionFailed => e
- render json: { proxy_error: e.message }, status: :internal_server_error
+ else
+ raise "Invalid HTTP verb: #{http_verb}"
end
+
+ response
end
+ # rubocop:enable Metrics/AbcSize
+ # rubocop:enable Metrics/CyclomaticComplexity
+ # rubocop:enable Metrics/MethodLength
+ # rubocop:enable Metrics/PerceivedComplexity
-
- private
-
- def rack_header? header_name
- predefined_rack_headers = %w[
- HTTP_VERSION HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
- HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_CONNECTION HTTP_HOST
- HTTP_REFERER HTTP_USER_AGENT HTTP_X_REQUEST_ID
- ]
-
- predefined_rack_headers.include?(header_name)
+ # rubocop:disable Metrics/PerceivedComplexity
+ def replace_values data, query_text
+ if data.is_a?(Hash)
+ data.each do |key, value|
+ if "\#$query##" == value
+ data[key] = query_text
+ elsif value.is_a?(Hash) || value.is_a?(Array)
+ replace_values(value, query_text)
+ end
+ end
+ elsif data.is_a?(Array)
+ data.each { |item| replace_values(item, query_text) }
+ end
+ data
end
+ # rubocop:enable Metrics/PerceivedComplexity
end
diff --git a/app/services/snapshot_manager.rb b/app/services/snapshot_manager.rb
index 5d7f24229..c62b2544e 100644
--- a/app/services/snapshot_manager.rb
+++ b/app/services/snapshot_manager.rb
@@ -165,8 +165,6 @@ def csv_to_queries_hash docs
query_docs
end
- private
-
# rubocop:disable Metrics/MethodLength
def setup_docs_for_query query, docs
results = []
@@ -193,6 +191,8 @@ def setup_docs_for_query query, docs
end
# rubocop:enable Metrics/MethodLength
+ private
+
def extract_doc_info row
case @options[:format]
when :csv
diff --git a/config/routes.rb b/config/routes.rb
index 0f9fc8ef0..d105e1dae 100755
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -118,7 +118,7 @@
end
resources :query_runner, only: [ :index ] do
post 'run_queries', on: :collection
- end
+ end
end
# preview routes for mailers
diff --git a/test/fixtures/search_endpoints.yml b/test/fixtures/search_endpoints.yml
index e090f4c6d..d613b85ca 100644
--- a/test/fixtures/search_endpoints.yml
+++ b/test/fixtures/search_endpoints.yml
@@ -134,8 +134,6 @@ try_with_headers:
api_method: JSONP
custom_headers: '{"Authorization": "ApiKey TEF5QkFJUUJVUnRsNG1fekNCR3E6WmRYUFJRRVdTMHlBVWotWFdqQWxuUQ=="}'
-
-
bootstrap_try_1:
name: Solr https://test.com
endpoint_url: https://test.com
diff --git a/test/lib/solr_arg_parser_test.rb b/test/lib/solr_arg_parser_test.rb
index 5a9ecb687..6bca66261 100644
--- a/test/lib/solr_arg_parser_test.rb
+++ b/test/lib/solr_arg_parser_test.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+o # frozen_string_literal: true
+
require 'test_helper'
class SolrArgParserTest < ActiveSupport::TestCase
diff --git a/test/services/fetch_service_test.rb b/test/services/fetch_service_test.rb
new file mode 100644
index 000000000..6458e36c0
--- /dev/null
+++ b/test/services/fetch_service_test.rb
@@ -0,0 +1,126 @@
+# frozen_string_literal: true
+
+require 'test_helper'
+
+class FetchServiceTest < ActiveSupport::TestCase
+ # Need to make the url.
+ # Need to understand the hTTP verb, GET, POST, JSONP
+ # Need to send the url with each query, swapping in the template.
+ # Need to also send the body with the qOptions as appropirate.
+ # Need to understand if it worked or not.
+ # Need to store the results as a snapshot.
+ # - Need ot use the snapshot structure to manage the lifecycle of the runs.
+ #
+
+ let(:try_with_headers) { tries(:try_with_headers) }
+ let(:case_with_ratings) { cases(:random_case) }
+ let(:search_endpoint) { search_endpoints(:try_with_headers) }
+ let(:options) do
+ {
+ debug_mode: true,
+ fake_call: false,
+ snapshot_limit: 3,
+ }
+ end
+
+ describe 'Creating an appropriate search query from Quepid data' do
+ let(:acase) { cases(:queries_case) }
+ let(:atry) { tries(:for_case_queries_case) }
+ let(:es_try_with_curator_vars) { tries(:es_try_with_curator_vars) }
+ let(:try_with_headers) { tries(:try_with_headers) }
+ let(:first_query) { queries(:first_query) }
+
+ it 'creates a GET request' do
+ fetch_service = FetchService.new options
+ response = fetch_service.create_request(atry, first_query)
+ assert_not_nil response
+ assert 200 == response.status
+ end
+
+ it 'works with custom headers and JSONP' do
+ fetch_service = FetchService.new options
+ response = fetch_service.create_request(try_with_headers, first_query)
+ assert_not_nil response
+ assert 200 == response.status
+ end
+
+ it 'creates a POST request' do
+ fetch_service = FetchService.new options
+ response = fetch_service.create_request(es_try_with_curator_vars, first_query)
+ assert_not_nil response
+ assert 200 == response.status
+ end
+ end
+
+ describe 'Running a fetch cycle produces a snapshot' do
+ let(:acase) { cases(:queries_case) }
+ let(:atry) { tries(:for_case_queries_case) }
+ let(:first_query) { queries(:first_query) }
+ let(:second_query) { queries(:second_query) }
+
+ it 'creates a snapshot when you begin' do
+ fetch_service = FetchService.new options
+ assert_difference 'acase.snapshots.count' do
+ snapshot = fetch_service.begin(acase, atry)
+ assert snapshot.name.starts_with?('Fetch [BEGUN]')
+ end
+ end
+
+ it 'saves a snapshot query for each run' do
+ fetch_service = FetchService.new options
+ fetch_service.begin(acase, atry)
+
+ docs = [
+ { id: 'doc1', explain: '1' },
+ { id: 'doc2', explain: '2' }
+ ]
+
+ assert_difference 'first_query.snapshot_queries.count' do
+ snapshot_query = fetch_service.store_query_results first_query, docs
+ assert_equal docs.size, snapshot_query.snapshot_docs.size
+ end
+ end
+
+ it 'limits how many snapshots you can have when completed' do
+ fetch_service = FetchService.new options
+ assert_difference 'acase.snapshots.count', 6 do
+ 6.times do
+ fetch_service.begin(acase, atry)
+ end
+ end
+
+ assert acase.snapshots.count > options[:snapshot_limit]
+
+ acase.snapshots.first
+
+ snapshot = fetch_service.begin(acase, atry)
+
+ fetch_service.complete
+
+ assert snapshot.name.starts_with?('Fetch [COMPLETED]')
+ assert_equal options[:snapshot_limit], acase.snapshots.count
+ end
+ end
+
+ test 'think should what we have' do
+ FetchService.new options
+ end
+
+ test 'should be able to handle a get' do
+ fetch_service = FetchService.new options
+
+ # should url: be in params? Or seperate?
+ params = {
+ url: 'http://solr.quepid.com:8983/solr/statedecoded/select', fl: 'id,text', q: 'legal', rows: 10, start: 0
+ }
+
+ fetch_service.fetch params
+ assert_response :success
+ end
+
+ test 'should track failure' do
+ end
+
+ test 'should track successes' do
+ end
+end
diff --git a/test/support/webmock.rb b/test/support/webmock.rb
index fe537364a..39a4f780a 100644
--- a/test/support/webmock.rb
+++ b/test/support/webmock.rb
@@ -208,6 +208,35 @@ def setup
}
)
.to_return(status: 200, body: '', headers: {})
+
+ # Testing out fetch service using
+ # search_endpoint for_case_queries_case
+ # try for_case_queries_case
+ stub_request(:get, 'http://test.com/solr/tmdb/select?q=First%20Query')
+ .with(
+ headers: {
+ 'Accept' => '*/*',
+ 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
+ 'Content-Type' => 'application/json',
+ 'User-Agent' => 'Faraday v2.9.0',
+ }
+ )
+ .to_return(status: 200, body: mock_statedecoded_body, headers: {})
+
+ # Testing out fetch service using
+ # search_endpoint for_case_queries_case
+ # try es_try_with_curator_vars
+ stub_request(:post, 'http://test.com:9200/tmdb/_search')
+ .with(
+ body: { 'query'=>{ 'multi_match'=>{ 'fields' => 'title, overview', 'query' => 'First Query', 'tie_breaker' => '1' } } },
+ headers: {
+ 'Accept' => '*/*',
+ 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
+ 'Content-Type' => 'application/json',
+ 'User-Agent' => 'Faraday v2.9.0',
+ }
+ )
+ .to_return(status: 200, body: mock_statedecoded_body, headers: {})
end
# rubocop:enable Metrics/MethodLength
From 7d4e7d436721c6d17f9c5b1172ef5bdaf53d7a85 Mon Sep 17 00:00:00 2001
From: Eric Pugh
Date: Fri, 5 Jul 2024 14:35:59 +0200
Subject: [PATCH 007/120] rubocop
---
app/services/fetch_service.rb | 2 ++
test/services/fetch_service_test.rb | 2 ++
2 files changed, 4 insertions(+)
diff --git a/app/services/fetch_service.rb b/app/services/fetch_service.rb
index 4a9e7f7c8..42463300e 100644
--- a/app/services/fetch_service.rb
+++ b/app/services/fetch_service.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+# rubocop:disable Metrics/ClassLength
class FetchService
# include ProgressIndicator
@@ -166,3 +167,4 @@ def replace_values data, query_text
end
# rubocop:enable Metrics/PerceivedComplexity
end
+# rubocop:enable Metrics/ClassLength
diff --git a/test/services/fetch_service_test.rb b/test/services/fetch_service_test.rb
index 6458e36c0..20a87903b 100644
--- a/test/services/fetch_service_test.rb
+++ b/test/services/fetch_service_test.rb
@@ -119,8 +119,10 @@ class FetchServiceTest < ActiveSupport::TestCase
end
test 'should track failure' do
+ assert true
end
test 'should track successes' do
+ assert true
end
end
From 29a9a1468cfe86413e7df807ad356c7d294259f6 Mon Sep 17 00:00:00 2001
From: Eric Pugh
Date: Tue, 9 Jul 2024 18:57:44 +0200
Subject: [PATCH 008/120] Now storing the response status and body text.
Practiced using the query runner job!
---
app/jobs/query_runner_job.rb | 132 ++++-----------
app/models/snapshot.rb | 8 +-
app/models/snapshot_query.rb | 2 +
app/services/fetch_service.rb | 159 ++++++++++++------
.../_notification_case.turbo_stream.erb | 17 ++
app/views/home/case_prophet.html.erb | 2 +
...add_response_fields_to_snapshot_queries.rb | 6 +
db/schema.rb | 4 +-
test/fixtures/queries.yml | 5 +
test/fixtures/snapshot_queries.yml | 2 +
test/lib/solr_arg_parser_test.rb | 2 -
test/services/fetch_service_test.rb | 48 +++---
test/support/webmock.rb | 15 ++
13 files changed, 212 insertions(+), 190 deletions(-)
create mode 100644 app/views/admin/query_runner/_notification_case.turbo_stream.erb
create mode 100644 db/migrate/20240709141551_add_response_fields_to_snapshot_queries.rb
diff --git a/app/jobs/query_runner_job.rb b/app/jobs/query_runner_job.rb
index 5a84fc8ea..fece081a1 100644
--- a/app/jobs/query_runner_job.rb
+++ b/app/jobs/query_runner_job.rb
@@ -8,10 +8,27 @@ class QueryRunnerJob < ApplicationJob
queue_as :bulk_processing
# rubocop:disable Metrics/MethodLength
- def perform acase, _try
+ def perform acase, atry
query_count = acase.queries.count
+ options = {
+ fake_mode: true,
+ debug_mode: true,
+ snapshot_limit: 3,
+ }
+
+ fetch_service = FetchService.new options
+ fetch_service.begin(acase, atry)
+
acase.queries.each_with_index do |query, counter|
+ response = fetch_service.make_request(atry, query)
+
+ response_code = response.status
+ response_body = response.body
+
+ docs = []
+ fetch_service.store_query_results query, docs, response_code, response_body
+
Turbo::StreamsChannel.broadcast_render_to(
:notifications,
target: 'notifications',
@@ -19,6 +36,13 @@ def perform acase, _try
locals: { query: query, query_count: query_count, counter: counter }
)
+ Turbo::StreamsChannel.broadcast_render_to(
+ :notifications,
+ target: "notifications-case-#{acase.id}",
+ partial: 'admin/query_runner/notification_case',
+ locals: { acase: acase, query: query, query_count: query_count, counter: counter }
+ )
+
sleep(2)
end
@@ -28,107 +52,15 @@ def perform acase, _try
partial: 'admin/query_runner/notification',
locals: { query: nil, query_count: query_count, counter: 0 }
)
- end
- # rubocop:enable Metrics/MethodLength
-
- # rubocop:disable Metrics/AbcSize
- # rubocop:disable Metrics/CyclomaticComplexity
- # rubocop:disable Metrics/MethodLength
- # rubocop:disable Metrics/PerceivedComplexity
- def fetch
- excluded_keys = [ :url, :action, :controller, :proxy_debug ]
-
- url_param = proxy_url_params
-
- proxy_debug = 'true' == params[:proxy_debug]
- uri = URI.parse(url_param)
- url_without_path = "#{uri.scheme}://#{uri.host}"
- url_without_path += ":#{uri.port}" unless uri.port.nil?
-
- connection = Faraday.new(url: url_without_path) do |faraday|
- # Configure the connection options, such as headers or middleware
- faraday.response :follow_redirects
- faraday.response :logger, nil, { headers: proxy_debug, bodies: proxy_debug, errors: !Rails.env.test? }
- faraday.ssl.verify = false
- faraday.request :url_encoded
-
- matching_headers = request.headers.select { |name, _| name.start_with?('HTTP') && !rack_header?(name) }
-
- matching_headers.each do |name, value|
- converted_name = name.sub('HTTP_', '')
- converted_name = converted_name.tr('_', '-')
- faraday.headers[converted_name] = value
- end
-
- faraday.headers['Content-Type'] = 'application/json'
- has_credentials = !uri.userinfo.nil?
- if has_credentials
- username, password = uri.userinfo.split(':')
- faraday.headers['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
- end
- faraday.adapter Faraday.default_adapter
- end
-
- begin
- if request.get?
- response = connection.get do |req|
- req.path = uri.path
- query_params = request.query_parameters.except(*excluded_keys)
- body_params = request.request_parameters.except(*query_params.keys)
-
- query_params.each do |param|
- req.params[param.first] = param.second
- end
-
- # the url parameter often has a format like
- # http://myserver.com/search?query=text, and when this is passed in
- # we get http://localhost:3000/proxy/fetch?url=http://myserver.com/search?query=text&rows=10
- # which means the parameter "query=text" is lost because the URL is parsed and this part is dropped,
- # so here we add this one parameter back in if we have it.
- if url_param.include?('?')
- # sometimes our url looks like http://myserver.com/search?q=tiger
- # But it could also be http://myserver.com/search?q=tiger? and that needs handling via the special .split
- extra_query_param = url_param.split('?', 2).last.split('=')
-
- req.params[extra_query_param.first] = extra_query_param.second
- end
- unless body_params.empty?
-
- json_query = body_params.first.first
- req.body = json_query
- end
- end
- elsif request.post?
- response = connection.post do |req|
- req.path = uri.path
- query_params = request.query_parameters.except(*excluded_keys)
- # not sure about this and the request.raw_post
- body_params = request.request_parameters.except(*query_params.keys)
- query_params.each do |param|
- req.params[param.first] = param.second
- end
- unless body_params.empty?
- json_query = request.raw_post
-
- req.body = json_query
- end
- end
- end
+ Turbo::StreamsChannel.broadcast_render_to(
+ :notifications,
+ target: "notifications-case-#{acase.id}",
+ partial: 'admin/query_runner/notification_case',
+ locals: { acase: acase, query: nil, query_count: query_count, counter: 0 }
+ )
- begin
- data = JSON.parse(response.body)
- { json: data, status: response.status }
- rescue JSON::ParserError
- # sometimes the API is returning plain old text, like a "Unauthorized" type message.
- { json: { response: response.body }, status: response.status }
- end
- rescue Faraday::ConnectionFailed => e
- { json: { proxy_error: e.message }, status: :internal_server_error }
- end
+ fetch_service.complete
end
- # rubocop:enable Metrics/AbcSize
- # rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/MethodLength
- # rubocop:enable Metrics/PerceivedComplexity
end
diff --git a/app/models/snapshot.rb b/app/models/snapshot.rb
index f3fe4eb3f..db5a4b958 100644
--- a/app/models/snapshot.rb
+++ b/app/models/snapshot.rb
@@ -30,11 +30,9 @@ class Snapshot < ApplicationRecord
belongs_to :scorer, optional: true # shouldn't be optional!
# see the call back delete_associated_objects for special delete logic.
- # rubocop:disable Rails/HasManyOrHasOneDependent
- has_many :snapshot_queries
- # rubocop:enable Rails/HasManyOrHasOneDependent
- has_many :snapshot_docs,
- through: :snapshot_queries
+ has_many :snapshot_queries, dependent: :delete_all
+ has_many :snapshot_docs,
+ through: :snapshot_queries
has_one_attached :snapshot_file
diff --git a/app/models/snapshot_query.rb b/app/models/snapshot_query.rb
index d28d2e0bd..ff2d947bb 100644
--- a/app/models/snapshot_query.rb
+++ b/app/models/snapshot_query.rb
@@ -7,6 +7,8 @@
# id :integer not null, primary key
# all_rated :boolean
# number_of_results :integer
+# response_body :text(65535)
+# response_status :integer
# score :float(24)
# query_id :integer
# snapshot_id :integer
diff --git a/app/services/fetch_service.rb b/app/services/fetch_service.rb
index 42463300e..b0d27f090 100644
--- a/app/services/fetch_service.rb
+++ b/app/services/fetch_service.rb
@@ -1,5 +1,8 @@
# frozen_string_literal: true
+require 'faraday'
+require 'faraday/follow_redirects'
+
# rubocop:disable Metrics/ClassLength
class FetchService
# include ProgressIndicator
@@ -29,8 +32,13 @@ def begin acase, atry
@snapshot
end
- def store_query_results query, docs
- snapshot_query = @snapshot.snapshot_queries.create(query: query, number_of_results: results.count)
+ def store_query_results query, docs, response_status, response_body
+ snapshot_query = @snapshot.snapshot_queries.create(
+ query: query,
+ number_of_results: docs.count,
+ response_status: response_status,
+ response_body: response_body
+ )
snapshot_manager = SnapshotManager.new(@snapshot)
snapshot_manager.setup_docs_for_query(snapshot_query, docs)
@@ -49,38 +57,27 @@ def complete
# rubocop:disable Metrics/MethodLength
def get_connection url, debug_mode, credentials, custom_headers
- if @connection.nil?
- @connection = Faraday.new(url: url) do |faraday|
- # Configure the connection options, such as headers or middleware
- faraday.response :follow_redirects
- faraday.response :logger, nil, { headers: debug_mode, bodies: debug_mode, errors: !Rails.env.test? }
- faraday.ssl.verify = false
- # faraday.request :url_encoded
-
- # matching_headers = request.headers.select { |name, _| name.start_with?('HTTP') && !rack_header?(name) }
-
- # matching_headers.each do |name, value|
- # converted_name = name.sub('HTTP_', '')
- # converted_name = converted_name.tr('_', '-')
- # faraday.headers[converted_name] = value
- # end
-
- faraday.headers['Content-Type'] = 'application/json'
- unless credentials.nil?
- username, password = credentials.split(':')
- faraday.headers['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
- end
+ connection = Faraday.new(url: url) do |faraday|
+ # Configure the connection options, such as headers or middleware
+ faraday.response :follow_redirects
+ faraday.response :logger, nil, { headers: debug_mode, bodies: debug_mode, errors: !Rails.env.test? }
+ faraday.ssl.verify = false
+
+ faraday.headers['Content-Type'] = 'application/json'
+ unless credentials.nil?
+ username, password = credentials.split(':')
+ faraday.headers['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
+ end
- unless custom_headers.nil?
- puts JSON.parse(custom_headers).to_h
- JSON.parse(custom_headers).to_h.each do |key, value|
- faraday.headers[key] = value
- end
+ unless custom_headers.nil?
+ puts JSON.parse(custom_headers).to_h
+ JSON.parse(custom_headers).to_h.each do |key, value|
+ faraday.headers[key] = value
end
- faraday.adapter Faraday.default_adapter
end
+ faraday.adapter Faraday.default_adapter
end
- @connection
+ connection
end
# rubocop:enable Metrics/MethodLength
@@ -88,15 +85,24 @@ def get_connection url, debug_mode, credentials, custom_headers
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/PerceivedComplexity
- def create_request atry, query
+ def make_request atry, query
+ if @options[:fake_mode]
+ # useful in development mode when we don't have access to a search engine'
+ body = mock_response
+ response = Faraday::Response.new(status: 200, body: body)
+ return response
+ end
search_endpoint = atry.search_endpoint
- debug_mode = true
- connection = get_connection search_endpoint.endpoint_url, debug_mode, search_endpoint.basic_auth_credential,
- search_endpoint.custom_headers
+ if @connection.nil?
+ @connection = get_connection search_endpoint.endpoint_url,
+ @options[:debug_mode],
+ search_endpoint.basic_auth_credential,
+ search_endpoint.custom_headers
+ end
- http_verb = search_endpoint.api_method # .to_sym
+ http_verb = search_endpoint.api_method
- # TODO: Make api_method a enum
+ # TODO: Make api_method an enum
http_verb = :get if 'JSONP' == http_verb
http_verb = :get if 'GET' == http_verb
http_verb = :post if 'POST' == http_verb
@@ -109,35 +115,26 @@ def create_request atry, query
# response = connection.run_request(http_verb, search_endpoint.endpoint_url) do |req|
case http_verb
when :get
- response = connection.get do |req|
+ response = @connection.get do |req|
req.url search_endpoint.endpoint_url
- args.each_value do |value|
- value.map! { |val| val.gsub("\#$query##", query.query_text) }
-
- puts "Here is the key: #{key}"
- puts "Here is the value: #{value}"
- # if value.is_a?(Array)
- # very unsure how we are handling the value, it may always be a array?
- value.each do |item|
- req.params[key] = item
+ args.each do |key, value|
+ value.each do |val|
+ val.gsub!('#$query##', query.query_text)
+ req.params[key] = val
end
end
-
- # Add any additional headers, params, or other options as needed
end
when :post
- response = connection.post do |req|
+ response = @connection.post do |req|
req.url search_endpoint.endpoint_url
args = replace_values(args, query.query_text)
- req.body = args
- # Add any additional headers, params, or other options as needed
+ req.body = args.to_json
end
when :put
- response = connection.put do |req|
+ response = @connection.put do |req|
req.url search_endpoint.endpoint_url
args = replace_values(args, query.query_text)
- req.body = args
- # Add any additional headers, params, or other options as needed
+ req.body = args.to_json
end
else
raise "Invalid HTTP verb: #{http_verb}"
@@ -154,7 +151,7 @@ def create_request atry, query
def replace_values data, query_text
if data.is_a?(Hash)
data.each do |key, value|
- if "\#$query##" == value
+ if '#$query##' == value
data[key] = query_text
elsif value.is_a?(Hash) || value.is_a?(Array)
replace_values(value, query_text)
@@ -166,5 +163,57 @@ def replace_values data, query_text
data
end
# rubocop:enable Metrics/PerceivedComplexity
+
+ # rubocop:disable Metrics/MethodLength
+ # rubocop:disable Layout/LineLength
+ def mock_response
+ mock_statedecoded_body = '{
+ "responseHeader":{
+ "zkConnected":true,
+ "status":0,
+ "QTime":0,
+ "params":{
+ "q":"*:*",
+ "fl":"id,text",
+ "start":"0",
+ "rows":"10"}},
+ "response":{"numFound":20148,"start":0,"docs":[
+ {
+ "id":"l_11730",
+ "text":"For the purpose of this chapter:\n\n"Claimant" means the person filing a claim pursuant to this chapter.\n\n"Commission" means the Virginia Workers' Compensation Commission.\n\n"Crime" means an act committed by any person in the Commonwealth of Virginia which would constitute a crime as defined by the Code of Virginia or at common law. However, no act involving the operation of a motor vehicle which results in injury shall constitute a crime for the purpose of this chapter unless the injuries (i) were intentionally inflicted through the use of such vehicle or (ii) resulted from a violation of § 18.2-51.4 or 18.2-266 or from a felony violation of § 46.2-894.\n\n"Family," when used with reference to a person, means (i) any person related to such person within the third degree of consanguinity or affinity, (ii) any person residing in the same household with such person, or (iii) a spouse.\n\n"Sexual abuse" means sexual abuse as defined in subdivision 6 of § 18.2-67.10 and acts constituting rape, sodomy, object sexual penetration or sexual battery as defined in Article 7 (§ 18.2-61 et seq.) of Chapter 4 of Title 18.2.\n\n"Victim" means a person who suffers personal physical injury or death as a direct result of a crime including a person who is injured or killed as a result of foreign terrorism or who suffers personal emotional injury as a direct result of being the subject of a violent felony offense as defined in subsection C of § 17.1-805, or stalking as described in § 18.2-60.3, or attempted robbery or abduction."},
+ {
+ "id":"l_5780",
+ "text":"Agencies authorized under any other law to issue grading, building, or other permits for activities involving land-disturbing activities regulated under this article may not issue any such permit unless the applicant submits with his application an approved erosion and sediment control plan and certification that the plan will be followed and, upon the development of an online reporting system by the Department but no later than July 1, 2014, evidence of Virginia stormwater management state permit coverage where it is required. Prior to issuance of any permit, the agency may also require an applicant to submit a reasonable performance bond with surety, cash escrow, letter of credit, any combination thereof, or such other legal arrangement acceptable to the agency, to ensure that measures could be taken by the agency at the applicant's expense should he fail, after proper notice, within the time specified to initiate or maintain appropriate conservation action which may be required of him by the approved plan as a result of his land-disturbing activity. The amount of the bond or other security for performance shall not exceed the total of the estimated cost to initiate and maintain appropriate conservation action based on unit price for new public or private sector construction in the locality and a reasonable allowance for estimated administrative costs and inflation which shall not exceed 25 percent of the estimated cost of the conservation action. If the agency takes such conservation action upon such failure by the permittee, the agency may collect from the permittee for the difference should the amount of the reasonable cost of such action exceed the amount of the security held. Within 60 days of the achievement of adequate stabilization of the land-disturbing activity in any project or section thereof, the bond, cash escrow, letter of credit or other legal arrangement, or the unexpended or unobligated portion thereof, shall be refunded to the applicant or terminated based upon the percentage of stabilization accomplished in the project or section thereof. These requirements are in addition to all other provisions of law relating to the issuance of such permits and are not intended to otherwise affect the requirements for such permits."},
+ {
+ "id":"l_16271",
+ "text":"If in the administration of this article a question concerning compliance with standards of practice governing any health care profession arises pursuant to Subtitle III (§ 54.1-2400 et seq.) of Title 54.1, the Commissioner or his designee shall consult with the appropriate health regulatory board within the Department of Health Professions."},
+ {
+ "id":"l_4010",
+ "text":"The production of documentary material in response to a civil investigative demand served under this article shall be made under a sworn certificate, in such form as the demand designates, by (i) in the case of a natural person, the person to whom the demand is directed, or (ii) in the case of a person other than a natural person, a person having knowledge of the facts and circumstances relating to such production and authorized to act on behalf of such person. The certificate shall state that all of the documentary material required by the demand and in the possession, custody, or control of the person to whom the demand is directed has been produced and made available to the investigator identified in the demand.\n\nAny person upon whom any civil investigative demand for the production of documentary material has been served shall make such material available for inspection and copying to the investigator identified in such demand at the principal place of business of such person, or at such other place as the investigator and the person thereafter may agree and prescribe in writing, or as the court may direct. Such material shall be made available on the return date specified in such demand, or on such later date as the investigator may prescribe in writing. Such person may, upon written agreement between the person and the investigator, substitute copies for originals of all or any part of such material."},
+ {
+ "id":"l_5552",
+ "text":"For purposes of computing fire protection or law-enforcement employees' entitlement to overtime compensation, all hours that an employee works or is in a paid status during his regularly scheduled work hours shall be counted as hours of work. The provisions of this section pertaining to law-enforcement employees shall only apply to such employees of an employer of 100 or more law-enforcement employees."},
+ {
+ "id":"l_725",
+ "text":"There is hereby created a political subdivision and public body corporate and politic of the Commonwealth of Virginia to be known as the Fort Monroe Authority, to be governed by a Board of Trustees (Board) consisting of 12 voting members appointed as follows: the Secretary of Natural Resources, the Secretary of Commerce and Trade, and the Secretary of Veterans Affairs and Homeland Security, or their successor positions if those positions no longer exist, from the Governor's cabinet; the member of the Senate of Virginia and the member of the House of Delegates representing the district in which Fort Monroe lies; two members appointed by the Hampton City Council; and five nonlegislative citizen members appointed by the Governor, four of whom shall have expertise relevant to the implementation of the Fort Monroe Reuse Plan, including but not limited to the fields of historic preservation, tourism, environment, real estate, finance, and education, and one of whom shall be a citizen representative from the Hampton Roads region. Cabinet members and elected representatives shall serve terms commensurate with their terms of office. Citizen appointees shall initially be appointed for staggered terms of either one, two, or three years, and thereafter shall serve for four-year terms. Cabinet members shall be entitled to send their deputies or another cabinet member, and legislative members another legislator, to meetings as full voting members in the event that official duties require their presence elsewhere.\n\nThe Board so appointed shall enter upon the performance of its duties and shall initially and annually thereafter elect one of its members as chairman and another as vice-chairman. The Board shall also elect annually a secretary, who shall be a member of the Board, and a treasurer, who need not be a member of the Board, or a secretary-treasurer, who need not be a member of the Board. The chairman, or in his absence the vice-chairman, shall preside at all meetings of the Board, and in the absence of both the chairman and vice-chairman, the Board shall elect a chairman pro tempore who shall preside at such meetings. Seven Trustees shall constitute a quorum, and all action by the Board shall require the affirmative vote of a majority of the Trustees present and voting, except that any action to amend or terminate the existing Reuse Plan, or to adopt a new Reuse Plan, shall require the affirmative vote of 75 percent or more of the Trustees present and voting. The members of the Board shall be entitled to reimbursement for expenses incurred in attendance upon meetings of the Board or while otherwise engaged in the discharge of their duties. Such expenses shall be paid out of the treasury of the Authority in such manner as shall be prescribed by the Authority."},
+ {
+ "id":"l_2980",
+ "text":"No provision of this chapter imposing any liability shall apply to any act done or omitted in good faith in conformity with any rule, regulation, or interpretation thereof by the Commission or by the Federal Reserve Board or officer or employee duly authorized by the Board to issue such interpretation or approvals under the comparable provisions of the federal Equal Credit Opportunity Act, (15 U.S.C. § 1691 et seq.), and regulations thereunder, notwithstanding that after such act or omission has occurred, such rule, regulation, or interpretation is amended, rescinded, or determined by judicial or other authority to be invalid for any reason."},
+ {
+ "id":"l_25249",
+ "text":"In this section, "document of rescission" means a document stating that an identified satisfaction, certificate of satisfaction, or affidavit of satisfaction of a security instrument was recorded erroneously or fraudulently, the secured obligation remains unsatisfied, and the security instrument remains in force.If a person records a satisfaction, certificate of satisfaction, or affidavit of satisfaction of a security instrument in error or by fraud, the person may execute and record a document of rescission. Upon recording, the document rescinds an erroneously recorded satisfaction, certificate, or affidavit.A recorded document of rescission has no effect on the rights of a person who:Acquired an interest in the real property described in a security instrument after the recording of the satisfaction, certificate of satisfaction, or affidavit of satisfaction of the security instrument and before the recording of the document of rescission; andWould otherwise have priority over or take free of the lien created by the security instrument under the laws of the Commonwealth of Virginia.A person, other than the clerk of the circuit court or any of his employees or other governmental official in the course of the performance of his recordation duties, who erroneously, fraudulently, or wrongfully records a document of rescission is subject to liability under § 55-66.3."},
+ {
+ "id":"l_1063",
+ "text":"All state public bodies created in the executive branch of state government and subject to the provisions of this chapter shall make available the following information to the public upon request and shall post such information on the Internet:A plain English explanation of the rights of a requester under this chapter, the procedures to obtain public records from the public body, and the responsibilities of the public body in complying with this chapter. For purposes of this subdivision "plain English" means written in nontechnical, readily understandable language using words of common everyday usage and avoiding legal terms and phrases or other terms and words of art whose usage or special meaning primarily is limited to a particular field or profession;Contact information for the person designated by the public body to (i) assist a requester in making a request for records or (ii) respond to requests for public records;A general description, summary, list, or index of the types of public records maintained by such state public body;A general description, summary, list, or index of any exemptions in law that permit or require such public records to be withheld from release; andAny policy the public body has concerning the type of public records it routinely withholds from release as permitted by this chapter or other law.The Freedom of Information Advisory Council, created pursuant to § 30-178, shall assist in the development and implementation of the provisions of subsection A, upon request."},
+ {
+ "id":"l_20837",
+ "text":"The Inspector shall administer the laws and regulations and shall have access to all records and properties necessary for this purpose. He shall perform all duties delegated by the Director pursuant to § 45.1-161.5 and maintain permanent records of the following:Each application for a gas, oil, or geophysical operation and each permitted gas, oil, or geophysical operation;Meetings, actions and orders of the Board;Petitions for mining coal within 200 feet of or through a well;Requests for special plugging by a coal owner or coal operator; andAll other records prepared pursuant to this chapter.The Inspector shall serve as the principal executive of the staff of the Board.The Inspector may take charge of well or corehole, or pipeline emergency operations whenever a well or corehole blowout, release of hydrogen sulfide or other gases, or other serious accident occurs."}]
+ }}
+ '
+
+ mock_statedecoded_body
+ end
+ # rubocop:enable Metrics/MethodLength
+ # rubocop:enable Layout/LineLength
end
# rubocop:enable Metrics/ClassLength
diff --git a/app/views/admin/query_runner/_notification_case.turbo_stream.erb b/app/views/admin/query_runner/_notification_case.turbo_stream.erb
new file mode 100644
index 000000000..184ddc6a9
--- /dev/null
+++ b/app/views/admin/query_runner/_notification_case.turbo_stream.erb
@@ -0,0 +1,17 @@
+<%= turbo_stream.replace "notification-case-#{acase.id}" do %>
+
+ <% unless query.nil? %>
+ Query: <%=query.query_text%>
+ <% end %>
+ <% if counter > 0 %>
+
<%= link_to_core_case 'View', kase, kase.last_try_number, class: 'btn btn-sm btn-primary', role: 'button' %>
diff --git a/db/scorers/scoring_logic.js b/db/scorers/scoring_logic.js
new file mode 100644
index 000000000..bff3b2685
--- /dev/null
+++ b/db/scorers/scoring_logic.js
@@ -0,0 +1,73 @@
+function scoreItems(items, options) {
+ // Log something using Ruby's logger
+ rubyLog('Starting score calculation');
+
+ // rubyLog('items:');
+ // rubyLog(items);
+
+ // Access Ruby data if needed
+ //const additionalData = JSON.parse(fetchData(options.dataId));
+ const additionalData = {};
+ console.log("Here")
+ console.log(additionalData)
+ return items.reduce((score, item) => {
+ let itemScore = item.value;
+
+ // Apply multipliers from options
+ if (options.multiplier) {
+ itemScore *= options.multiplier;
+ }
+
+ // Use additional data from Ruby
+ if (additionalData[item.id]) {
+ itemScore += additionalData[item.id].bonus;
+ }
+
+ return score + itemScore;
+ }, 0);
+}
+
+
+function docAt(posn) {
+ if (posn >= docs.length) {
+ return {};
+ } else {
+ return docs[posn].doc;
+ }
+}
+
+var docExistsAt = function(posn) {
+ if (posn >= docs.length) {
+ return false;
+ }
+ return true;
+};
+
+var eachDoc = function(f, count) {
+ // if ( angular.isUndefined(count) ) {
+ // count = DEFAULT_NUM_DOCS;
+ // }
+ if (typeof variable === "undefined") {
+ count = 10;
+ }
+ var i = 0;
+ for (i = 0; i < count; i++) {
+ if (docExistsAt(i)) {
+ f(docAt(i), i);
+ }
+ }
+};
+
+var hasDocRating = function(posn) {
+ return docExistsAt(posn) && hasRating(docs[posn]);
+};
+
+var hasRating = function(doc) {
+ return doc.hasOwnProperty('rating');
+};
+var docRating = function(posn) {
+ if (docExistsAt(posn)) {
+ return docs[posn]["rating"];
+ }
+ return undefined;
+};
diff --git a/lib/java_script_scorer.rb b/lib/java_script_scorer.rb
new file mode 100644
index 000000000..b4c00bc9a
--- /dev/null
+++ b/lib/java_script_scorer.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+class JavaScriptScorer
+ class ScoreError < StandardError; end
+
+ def initialize js_file_path
+ @context = MiniRacer::Context.new
+
+ # Add Ruby methods to JavaScript context
+ attach_ruby_methods
+
+ # Add console.log support
+ @context.eval <<-JS
+ var console = {
+ log: function(msg) { puts(msg); }
+ };
+ JS
+
+ # Load your scoring JavaScript
+ @context.eval(File.read(js_file_path))
+ end
+
+ def score docs, _scorer_file_path
+ # docs = docs.to_json
+ @context.eval("docs = #{docs.to_json};")
+
+ result = @context.eval(<<-JS)
+ try {
+
+ #{' '}
+ console.log("Starting ");
+ //console.log(docs)
+ //scoreItems(items, options); // Your JavaScript scoring function
+ const k = 10; // @Rank
+ // k may be > length list, so count up the total number of documents processed.
+ let count = 0, total = 0;
+ eachDoc(function(doc, i) {
+ if (hasDocRating(i) && (docRating(i)) > 0) { // map 0 -> irrel, 1+ ->rel
+ count = count + 1;
+ }
+ total = total + 1.0;
+ }, k);
+ const score = total ? count / total : 0.0;
+ // setScore(score);
+ console.log("The score is " + score);
+ score;
+
+ } catch (error) {
+ ({ error: error.message });
+ }
+ JS
+ puts "the result is #{result}"
+ raise ScoreError, result['error'] if result.is_a?(Hash) && result['error']
+
+ result
+ rescue MiniRacer::Error => e
+ raise ScoreError, "JavaScript execution error: #{e.message}"
+ end
+
+ def score_items items, options = {}
+ # Convert Ruby objects and options to JavaScript
+ js_items = items.to_json
+ js_options = options.to_json
+
+ puts "js_itmes is made: #{js_items}"
+ puts "js_options is made: #{js_options}"
+
+ result = @context.eval(<<-JS)
+ try {
+ const items = #{js_items};
+ const options = #{js_options};
+ console.log("Hello World");
+ //{bob:true};
+ console.log(items)
+ scoreItems(items, options); // Your JavaScript scoring function
+ } catch (error) {
+ ({ error: error.message });
+ }
+ JS
+ puts "the result is #{result}"
+ raise ScoreError, result['error'] if result.is_a?(Hash) && result['error']
+
+ result
+ rescue MiniRacer::Error => e
+ raise ScoreError, "JavaScript execution error: #{e.message}"
+ end
+
+ private
+
+ def attach_ruby_methods
+ @context.attach('puts', ->(message) { puts message })
+
+ # Expose Ruby methods to JavaScript
+ @context.attach('rubyLog', ->(message) { puts(message) })
+
+ # Add more Ruby methods as needed
+ @context.attach('fetchData', ->(id) {
+ Data.find(id).to_json
+ })
+ end
+end
diff --git a/test/fixtures/ratings.yml b/test/fixtures/ratings.yml
index 1d1e24e9e..a89abe9f4 100644
--- a/test/fixtures/ratings.yml
+++ b/test/fixtures/ratings.yml
@@ -54,3 +54,13 @@ second_query_rating2:
user: :random
doc_id: "docb"
rating: 1
+
+a_query_rating:
+ query: :a_query
+ doc_id: "doc_a"
+ rating: 1
+
+b_query_rating:
+ query: :b_query
+ doc_id: "doc_b"
+ rating: 0
diff --git a/test/services/fetch_service_test.rb b/test/services/fetch_service_test.rb
index 61bc3040d..504c29d21 100644
--- a/test/services/fetch_service_test.rb
+++ b/test/services/fetch_service_test.rb
@@ -248,4 +248,22 @@ class FetchServiceTest < ActiveSupport::TestCase
assert_nothing_raised { JSON.parse(snapshot_doc.explain) }
end
end
+
+ describe 'scoring logic' do
+ let(:acase) { cases(:snapshot_case) }
+ let(:atry) { tries(:for_case_snapshot_case) }
+
+ let(:asnapshot) { snapshots(:a_snapshot) }
+ let(:snapshot_query) { snapshot_queries(:first_snapshot_query) }
+
+ it 'runs a score' do
+ assert_equal 1.0, snapshot_query.score # before running P@10
+ fetch_service = FetchService.new options
+ score_data = fetch_service.score_snapshot(asnapshot, atry)
+ assert_equal 2, score_data[:queries].size
+
+ snapshot_query.reload
+ assert_equal 0.5, snapshot_query.score
+ end
+ end
end
diff --git a/test/services/scoring_test.rb b/test/services/scoring_test.rb
new file mode 100644
index 000000000..2cc4cceda
--- /dev/null
+++ b/test/services/scoring_test.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'test_helper'
+require 'java_script_scorer'
+
+class ScoringTest < ActiveSupport::TestCase
+ describe 'Samples' do
+ let(:asnapshot) { snapshots(:a_snapshot) }
+
+ it 'calculates some numbers' do
+ # MiniRacer::Platform.set_flags!(:single_threaded)
+
+ context = MiniRacer::Context.new
+ context.eval('var adder = (a,b)=>a+b;')
+ assert_equal 42, context.eval('adder(20,22)')
+
+ context = MiniRacer::Context.new
+ context.attach('math.adder', proc { |a, b| a + b })
+ assert_equal 42, context.eval('math.adder(20,22)')
+ end
+
+ it 'reads in a ascorer' do
+ scorer = JavaScriptScorer.new(Rails.root.join('db/scorers/scoring_logic.js'))
+
+ # Prepare some items to score
+ items = [
+ { id: 1, value: 10 },
+ { id: 2, value: 20 }
+ ]
+
+ # Calculate score with options
+ begin
+ score = scorer.score_items(items, {
+ multiplier: 1.5,
+ dataId: 123,
+ })
+ puts "Final score: #{score}"
+ assert_equal 45, score
+ rescue JavaScriptScorer::ScoreError => e
+ puts "Scoring failed: #{e.message}"
+ end
+ end
+
+ it 'handles P@10' do
+ java_script_scorer = JavaScriptScorer.new(Rails.root.join('db/scorers/scoring_logic.js'))
+
+ # Prepare some items to score
+ items = [
+ { id: 1, value: 10, rating: 3 },
+ { id: 2, value: 20, rating: 0 }
+ ]
+
+ # Calculate score with options
+ begin
+ score = java_script_scorer.score(items, Rails.root.join('db/scorers/p@10.js'))
+ puts "Final score: #{score}"
+ assert_equal 0.5, score
+ rescue JavaScriptScorer::ScoreError => e
+ puts "Scoring failed: #{e.message}"
+ end
+ end
+ end
+end
From b89250fa4b2482a405153f639528f9e7d847e88d Mon Sep 17 00:00:00 2001
From: Eric Pugh
Date: Tue, 17 Dec 2024 21:10:42 -0500
Subject: [PATCH 054/120] try and shrink slug
---
Gemfile.lock | 1 -
app/services/fetch_service.rb | 5 ++++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index 5c8389646..37e34f936 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -250,7 +250,6 @@ GEM
childprocess (~> 5.0)
letter_opener (1.10.0)
launchy (>= 2.2, < 4)
- libv8-node (18.19.0.0-aarch64-linux)
libv8-node (18.19.0.0-x86_64-darwin)
libv8-node (18.19.0.0-x86_64-linux)
listen (3.9.0)
diff --git a/app/services/fetch_service.rb b/app/services/fetch_service.rb
index 2e64acc56..532a03f7c 100644
--- a/app/services/fetch_service.rb
+++ b/app/services/fetch_service.rb
@@ -113,6 +113,8 @@ def score_run
case_score_manager.update score_data
end
+ # rubocop disable Metrics/AbcSize
+ # rubocop disable Metrics/MethodLength
def score_snapshot snapshot, try
java_script_scorer = JavaScriptScorer.new(Rails.root.join('db/scorers/scoring_logic.js'))
@@ -128,7 +130,6 @@ def score_snapshot snapshot, try
items = snapshot_query.snapshot_docs.map do |snapshot_doc|
{ id: snapshot_doc.doc_id, rating: doc_ratings[snapshot_doc.doc_id] }
end
- puts items
# items = [
# { id: 1, value: 10, rating: 3 },
@@ -158,6 +159,8 @@ def score_snapshot snapshot, try
score_data
end
+ # rubocop enable Metrics/AbcSize
+ # rubocop enable Metrics/MethodLength
def complete
@snapshot.name = 'Fetch [COMPLETED]'
From 2977bb426a42dfa13d3b1eb63ceb720599f1c2cb Mon Sep 17 00:00:00 2001
From: Eric Pugh
Date: Tue, 17 Dec 2024 21:12:06 -0500
Subject: [PATCH 055/120] comments
---
lib/java_script_scorer.rb | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/lib/java_script_scorer.rb b/lib/java_script_scorer.rb
index b4c00bc9a..c39849151 100644
--- a/lib/java_script_scorer.rb
+++ b/lib/java_script_scorer.rb
@@ -20,8 +20,9 @@ def initialize js_file_path
@context.eval(File.read(js_file_path))
end
+ # rubocop disable Metrics/MethodLength
+ # rubocop:disable Style/DocumentDynamicEvalDefinition
def score docs, _scorer_file_path
- # docs = docs.to_json
@context.eval("docs = #{docs.to_json};")
result = @context.eval(<<-JS)
@@ -57,27 +58,24 @@ def score docs, _scorer_file_path
raise ScoreError, "JavaScript execution error: #{e.message}"
end
+ # rubocop enable Metrics/MethodLength
+ # rubocop:enable Style/DocumentDynamicEvalDefinition
+ #
def score_items items, options = {}
# Convert Ruby objects and options to JavaScript
js_items = items.to_json
js_options = options.to_json
- puts "js_itmes is made: #{js_items}"
- puts "js_options is made: #{js_options}"
-
result = @context.eval(<<-JS)
try {
const items = #{js_items};
const options = #{js_options};
- console.log("Hello World");
- //{bob:true};
- console.log(items)
scoreItems(items, options); // Your JavaScript scoring function
} catch (error) {
({ error: error.message });
}
JS
- puts "the result is #{result}"
+
raise ScoreError, result['error'] if result.is_a?(Hash) && result['error']
result
From 51e4910d1da082a81eadded6b5b80dfaa4ba6b57 Mon Sep 17 00:00:00 2001
From: Eric Pugh
Date: Tue, 17 Dec 2024 21:15:10 -0500
Subject: [PATCH 056/120] can we get under 500 mb?
---
lib/tasks/assets.rake | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/lib/tasks/assets.rake b/lib/tasks/assets.rake
index d6860407a..b5fd7e1bd 100644
--- a/lib/tasks/assets.rake
+++ b/lib/tasks/assets.rake
@@ -17,8 +17,9 @@ namespace :assets do
system "wget --no-verbose -O #{notebooks_gz} https://github.com/o19s/quepid-jupyterlite/releases/latest/download/jupyter-lite-build.tgz"
end
- puts "Unpacking Jupyterlite into #{destination}"
- system "tar -xzf #{notebooks_gz} --directory #{destination}"
+ puts "Not unpacking Jupyterlite"
+ #puts "Unpacking Jupyterlite into #{destination}"
+ #ystem "tar -xzf #{notebooks_gz} --directory #{destination}"
File.delete(notebooks_gz)
end
From a3d9f02f75b31bb23c6438ad9a304248c740c6a9 Mon Sep 17 00:00:00 2001
From: Eric Pugh
Date: Tue, 17 Dec 2024 21:20:49 -0500
Subject: [PATCH 057/120] lets try this
---
.slugignore | 4 ++++
lib/tasks/assets.rake | 6 +++---
2 files changed, 7 insertions(+), 3 deletions(-)
create mode 100644 .slugignore
diff --git a/.slugignore b/.slugignore
new file mode 100644
index 000000000..b0537d9d6
--- /dev/null
+++ b/.slugignore
@@ -0,0 +1,4 @@
+/test
+/spec
+/deployment
+
diff --git a/lib/tasks/assets.rake b/lib/tasks/assets.rake
index b5fd7e1bd..2ecb4c373 100644
--- a/lib/tasks/assets.rake
+++ b/lib/tasks/assets.rake
@@ -17,9 +17,9 @@ namespace :assets do
system "wget --no-verbose -O #{notebooks_gz} https://github.com/o19s/quepid-jupyterlite/releases/latest/download/jupyter-lite-build.tgz"
end
- puts "Not unpacking Jupyterlite"
- #puts "Unpacking Jupyterlite into #{destination}"
- #ystem "tar -xzf #{notebooks_gz} --directory #{destination}"
+ #puts "Not unpacking Jupyterlite"
+ puts "Unpacking Jupyterlite into #{destination}"
+ system "tar -xzf #{notebooks_gz} --directory #{destination}"
File.delete(notebooks_gz)
end
From d63ef34a63479e580a2168bfbae901a0e73d94dd Mon Sep 17 00:00:00 2001
From: Eric Pugh
Date: Tue, 17 Dec 2024 21:23:30 -0500
Subject: [PATCH 058/120] okay, that didn't owrk...
---
.slugignore | 4 ----
lib/tasks/assets.rake | 8 ++++----
2 files changed, 4 insertions(+), 8 deletions(-)
delete mode 100644 .slugignore
diff --git a/.slugignore b/.slugignore
deleted file mode 100644
index b0537d9d6..000000000
--- a/.slugignore
+++ /dev/null
@@ -1,4 +0,0 @@
-/test
-/spec
-/deployment
-
diff --git a/lib/tasks/assets.rake b/lib/tasks/assets.rake
index 2ecb4c373..b4e5cff83 100644
--- a/lib/tasks/assets.rake
+++ b/lib/tasks/assets.rake
@@ -17,11 +17,11 @@ namespace :assets do
system "wget --no-verbose -O #{notebooks_gz} https://github.com/o19s/quepid-jupyterlite/releases/latest/download/jupyter-lite-build.tgz"
end
- #puts "Not unpacking Jupyterlite"
- puts "Unpacking Jupyterlite into #{destination}"
- system "tar -xzf #{notebooks_gz} --directory #{destination}"
+ puts "Not unpacking Jupyterlite"
+ #puts "Unpacking Jupyterlite into #{destination}"
+ #system "tar -xzf #{notebooks_gz} --directory #{destination}"
- File.delete(notebooks_gz)
+ #File.delete(notebooks_gz)
end
end
From 49a9064790abdb1dfc51ed785ff471e635c74439 Mon Sep 17 00:00:00 2001
From: Eric Pugh
Date: Tue, 17 Dec 2024 21:25:49 -0500
Subject: [PATCH 059/120] argh
---
lib/tasks/assets.rake | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/tasks/assets.rake b/lib/tasks/assets.rake
index b4e5cff83..7b3ee3fc9 100644
--- a/lib/tasks/assets.rake
+++ b/lib/tasks/assets.rake
@@ -21,7 +21,7 @@ namespace :assets do
#puts "Unpacking Jupyterlite into #{destination}"
#system "tar -xzf #{notebooks_gz} --directory #{destination}"
- #File.delete(notebooks_gz)
+ File.delete(notebooks_gz)
end
end
From 81dbf13e436303ef2619ffed397c7cc5952bcb98 Mon Sep 17 00:00:00 2001
From: Eric Pugh
Date: Wed, 18 Dec 2024 06:25:31 -0500
Subject: [PATCH 060/120] Fix up handling some errors..
I know there will be more!
---
app/services/fetch_service.rb | 23 +++++++++++++++++++----
test/services/fetch_service_test.rb | 8 ++++++++
2 files changed, 27 insertions(+), 4 deletions(-)
diff --git a/app/services/fetch_service.rb b/app/services/fetch_service.rb
index 532a03f7c..45bee2375 100644
--- a/app/services/fetch_service.rb
+++ b/app/services/fetch_service.rb
@@ -88,6 +88,7 @@ def setup_docs_for_query query, docs
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
+ # This maybe should be split out into a snapshot_query and a snapshot_docs?
def store_query_results query, docs, response_status, response_body
snapshot_query = @snapshot.snapshot_queries.create(
query: query,
@@ -113,8 +114,8 @@ def score_run
case_score_manager.update score_data
end
- # rubocop disable Metrics/AbcSize
- # rubocop disable Metrics/MethodLength
+ # rubocop:disable Metrics/AbcSize
+ # rubocop:disable Metrics/MethodLength
def score_snapshot snapshot, try
java_script_scorer = JavaScriptScorer.new(Rails.root.join('db/scorers/scoring_logic.js'))
@@ -159,8 +160,8 @@ def score_snapshot snapshot, try
score_data
end
- # rubocop enable Metrics/AbcSize
- # rubocop enable Metrics/MethodLength
+ # rubocop:enable Metrics/AbcSize
+ # rubocop:enable Metrics/MethodLength
def complete
@snapshot.name = 'Fetch [COMPLETED]'
@@ -210,6 +211,12 @@ def make_request atry, query
response = Faraday::Response.new(status: 200, body: body)
return response
end
+
+ if atry.search_endpoint.nil?
+ response = create_error_response("No search endpoint defined for try number #{atry.try_number}" )
+ return response
+ end
+
search_endpoint = atry.search_endpoint
if @connection.nil?
@connection = get_connection search_endpoint.endpoint_url,
@@ -350,5 +357,13 @@ def normalize_docs_array docs
result
end
+
+ def create_error_response message
+ Faraday::Response.new(
+ status: 400,
+ body: { error: message }.to_json,
+ response_headers: { 'Content-Type' => 'application/json' }
+ )
+ end
end
# rubocop:enable Metrics/ClassLength
diff --git a/test/services/fetch_service_test.rb b/test/services/fetch_service_test.rb
index 504c29d21..0ffb08e47 100644
--- a/test/services/fetch_service_test.rb
+++ b/test/services/fetch_service_test.rb
@@ -57,6 +57,14 @@ class FetchServiceTest < ActiveSupport::TestCase
assert_not_nil response
assert 404 == response.status
end
+
+ it 'handles a missing search_endpoint' do
+ fetch_service = FetchService.new options
+ atry.search_endpoint = nil
+ response = fetch_service.make_request(atry, blowup_query)
+ assert_not_nil response
+ assert 400 == response.status
+ end
end
describe 'Running a fetch cycle produces a snapshot' do
From a0c41d3b595742c16fb1c58a219c029516e4202d Mon Sep 17 00:00:00 2001
From: Eric Pugh
Date: Wed, 18 Dec 2024 06:25:37 -0500
Subject: [PATCH 061/120] rubocop
---
app/jobs/query_runner_job.rb | 3 +++
lib/java_script_scorer.rb | 12 ++++++------
lib/tasks/assets.rake | 8 ++++----
3 files changed, 13 insertions(+), 10 deletions(-)
diff --git a/app/jobs/query_runner_job.rb b/app/jobs/query_runner_job.rb
index 8f9893c24..c06b03c21 100644
--- a/app/jobs/query_runner_job.rb
+++ b/app/jobs/query_runner_job.rb
@@ -26,11 +26,14 @@ def perform acase, atry
response_code = response.status
response_body = response.body
puts "Does response_code == 200? #{response_code} is #{200 == response_code}"
+ # need to deal with errors better.
if 200 == response_code
puts 'WE GOT A 200'
# this is all rough... just to get some snapshot_docs...
docs = fetch_service.extract_docs_from_response_body_for_solr response_body
fetch_service.store_query_results query, docs, response_code, response_body
+ else
+ fetch_service.store_query_results query, [], response_code, response_body
end
Turbo::StreamsChannel.broadcast_render_to(
diff --git a/lib/java_script_scorer.rb b/lib/java_script_scorer.rb
index c39849151..2215aba46 100644
--- a/lib/java_script_scorer.rb
+++ b/lib/java_script_scorer.rb
@@ -22,16 +22,14 @@ def initialize js_file_path
# rubocop disable Metrics/MethodLength
# rubocop:disable Style/DocumentDynamicEvalDefinition
+ # rubocop:disable Metrics/MethodLength
def score docs, _scorer_file_path
@context.eval("docs = #{docs.to_json};")
result = @context.eval(<<-JS)
try {
-
- #{' '}
- console.log("Starting ");
+ console.log("Starting JS scoring");
//console.log(docs)
- //scoreItems(items, options); // Your JavaScript scoring function
const k = 10; // @Rank
// k may be > length list, so count up the total number of documents processed.
let count = 0, total = 0;
@@ -57,10 +55,11 @@ def score docs, _scorer_file_path
rescue MiniRacer::Error => e
raise ScoreError, "JavaScript execution error: #{e.message}"
end
-
# rubocop enable Metrics/MethodLength
# rubocop:enable Style/DocumentDynamicEvalDefinition
- #
+ # rubocop:enable Metrics/MethodLength
+
+ # rubocop:disable Style/DocumentDynamicEvalDefinition
def score_items items, options = {}
# Convert Ruby objects and options to JavaScript
js_items = items.to_json
@@ -82,6 +81,7 @@ def score_items items, options = {}
rescue MiniRacer::Error => e
raise ScoreError, "JavaScript execution error: #{e.message}"
end
+ # rubocop:enable Style/DocumentDynamicEvalDefinition
private
diff --git a/lib/tasks/assets.rake b/lib/tasks/assets.rake
index 7b3ee3fc9..df691d5c3 100644
--- a/lib/tasks/assets.rake
+++ b/lib/tasks/assets.rake
@@ -7,7 +7,7 @@ namespace :assets do
desc 'Unpack Jupyterlite assets'
task jupyterlite: :environment do
notebooks_gz = Rails.root.join('notebooks.gz')
- destination = Rails.public_path
+ Rails.public_path
notebooks_dir = Rails.public_path.join('notebooks')
# Only deal with the compressed notebooks if we don't have the directory already.
@@ -17,9 +17,9 @@ namespace :assets do
system "wget --no-verbose -O #{notebooks_gz} https://github.com/o19s/quepid-jupyterlite/releases/latest/download/jupyter-lite-build.tgz"
end
- puts "Not unpacking Jupyterlite"
- #puts "Unpacking Jupyterlite into #{destination}"
- #system "tar -xzf #{notebooks_gz} --directory #{destination}"
+ puts 'Not unpacking Jupyterlite'
+ # puts "Unpacking Jupyterlite into #{destination}"
+ # system "tar -xzf #{notebooks_gz} --directory #{destination}"
File.delete(notebooks_gz)
end
From a450ef01be288aa57ecff72e5a9d4fca174222c3 Mon Sep 17 00:00:00 2001
From: Eric Pugh
Date: Wed, 18 Dec 2024 06:32:46 -0500
Subject: [PATCH 062/120] refacotring
---
app/services/fetch_service.rb | 144 ++++++++++++++++++----------------
1 file changed, 75 insertions(+), 69 deletions(-)
diff --git a/app/services/fetch_service.rb b/app/services/fetch_service.rb
index 45bee2375..0be612841 100644
--- a/app/services/fetch_service.rb
+++ b/app/services/fetch_service.rb
@@ -200,78 +200,14 @@ def get_connection url, debug_mode, credentials, custom_headers
end
# rubocop:enable Metrics/MethodLength
- # rubocop:disable Metrics/AbcSize
- # rubocop:disable Metrics/CyclomaticComplexity
- # rubocop:disable Metrics/MethodLength
- # rubocop:disable Metrics/PerceivedComplexity
def make_request atry, query
- if @options[:fake_mode]
- # useful in development mode when we don't have access to a search engine'
- body = mock_response
- response = Faraday::Response.new(status: 200, body: body)
- return response
- end
-
- if atry.search_endpoint.nil?
- response = create_error_response("No search endpoint defined for try number #{atry.try_number}" )
- return response
- end
-
- search_endpoint = atry.search_endpoint
- if @connection.nil?
- @connection = get_connection search_endpoint.endpoint_url,
- @options[:debug_mode],
- search_endpoint.basic_auth_credential,
- search_endpoint.custom_headers
- end
-
- http_verb = search_endpoint.api_method
-
- # TODO: Make api_method an enum
- http_verb = :get if 'JSONP' == http_verb
- http_verb = :get if 'GET' == http_verb
- http_verb = :post if 'POST' == http_verb
- http_verb = :put if 'PUT' == http_verb
- puts "Running query with #{http_verb}"
-
- args = atry.args
-
- response = nil
- # response = connection.run_request(http_verb, search_endpoint.endpoint_url) do |req|
- case http_verb
- when :get
- response = @connection.get do |req|
- # TODO: This is now Solr specific and it shouldn't be.
- req.url "#{search_endpoint.endpoint_url}?debug=true&debug.explain.structured=true&wt=json"
- args.each do |key, value|
- value.each do |val|
- val.gsub!('#$query##', query.query_text)
- req.params[key] = val
- end
- end
- end
- when :post
- response = @connection.post do |req|
- req.url search_endpoint.endpoint_url
- args = replace_values(args, query.query_text)
- req.body = args.to_json
- end
- when :put
- response = @connection.put do |req|
- req.url search_endpoint.endpoint_url
- args = replace_values(args, query.query_text)
- req.body = args.to_json
- end
- else
- raise "Invalid HTTP verb: #{http_verb}"
- end
+ return mock_response if @options[:fake_mode]
+ return handle_missing_endpoint(atry) if atry.search_endpoint.nil?
+ setup_connection(atry.search_endpoint)
+ response = execute_request(atry, query)
response
end
- # rubocop:enable Metrics/AbcSize
- # rubocop:enable Metrics/CyclomaticComplexity
- # rubocop:enable Metrics/MethodLength
- # rubocop:enable Metrics/PerceivedComplexity
# rubocop:disable Metrics/PerceivedComplexity
def replace_values data, query_text
@@ -292,7 +228,7 @@ def replace_values data, query_text
# rubocop:disable Metrics/MethodLength
# rubocop:disable Layout/LineLength
- def mock_response
+ def mock_response_body
mock_statedecoded_body = '{
"responseHeader":{
"zkConnected":true,
@@ -365,5 +301,75 @@ def create_error_response message
response_headers: { 'Content-Type' => 'application/json' }
)
end
+
+ def mock_response
+ body = mock_response_body
+ Faraday::Response.new(status: 200, body: body)
+ end
+
+ def handle_missing_endpoint atry
+ create_error_response("No search endpoint defined for try number #{atry.try_number}")
+ end
+
+ def setup_connection search_endpoint
+ return if @connection
+
+ @connection = get_connection(
+ search_endpoint.endpoint_url,
+ @options[:debug_mode],
+ search_endpoint.basic_auth_credential,
+ search_endpoint.custom_headers
+ )
+ end
+
+ def execute_request atry, query
+ endpoint = atry.search_endpoint
+ http_verb = normalize_http_verb(endpoint.api_method)
+
+ case http_verb
+ when :get then execute_get_request(endpoint, atry.args, query)
+ when :post then execute_body_request(:post, endpoint, atry.args, query)
+ when :put then execute_body_request(:put, endpoint, atry.args, query)
+ else
+ raise ArgumentError, "Invalid HTTP verb: #{http_verb}"
+ end
+ end
+
+ def normalize_http_verb verb
+ case verb.upcase
+ when 'JSONP', 'GET' then :get
+ when 'POST' then :post
+ when 'PUT' then :put
+ else verb.downcase.to_sym
+ end
+ end
+
+ def execute_get_request endpoint, args, query
+ @connection.get do |req|
+ req.url "#{endpoint.endpoint_url}?debug=true&debug.explain.structured=true&wt=json"
+ process_get_params(req, args, query)
+ end
+ end
+
+ def process_get_params req, args, query
+ args.each do |key, values|
+ values.each do |val|
+ val.gsub!('#$query##', query.query_text)
+ req.params[key] = val
+ end
+ end
+ end
+
+ def execute_body_request method, endpoint, args, query
+ @connection.public_send(method) do |req|
+ req.url endpoint.endpoint_url
+ req.body = prepare_request_body(args, query)
+ end
+ end
+
+ def prepare_request_body args, query
+ processed_args = replace_values(args, query.query_text)
+ processed_args.to_json
+ end
end
# rubocop:enable Metrics/ClassLength
From 11dfb5d393ac95b27c3eee4ee61d1df28d6f87f0 Mon Sep 17 00:00:00 2001
From: Eric Pugh
Date: Wed, 18 Dec 2024 07:45:17 -0500
Subject: [PATCH 063/120] MORE OFTEN
---
config/recurring.yml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/config/recurring.yml b/config/recurring.yml
index 81dc577ef..caadfdd30 100644
--- a/config/recurring.yml
+++ b/config/recurring.yml
@@ -13,7 +13,8 @@ development:
production:
nightly_jobs_command:
class: EnqueueNightlyQueryRunnerJob
- schedule: every day at 1 am
+ schedule: every hour
+ #schedule: every day at 1 am
# periodic_cleanup:
From a887ee6d580001b454d13640e6d9cc74bb4762d2 Mon Sep 17 00:00:00 2001
From: Eric Pugh
Date: Wed, 18 Dec 2024 11:52:08 -0500
Subject: [PATCH 064/120] be robust on checking for a user now that it isnt
required
---
app/views/api/v1/case_scores/_score.json.jbuilder | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/views/api/v1/case_scores/_score.json.jbuilder b/app/views/api/v1/case_scores/_score.json.jbuilder
index fe0dd3907..052bc29ec 100644
--- a/app/views/api/v1/case_scores/_score.json.jbuilder
+++ b/app/views/api/v1/case_scores/_score.json.jbuilder
@@ -11,7 +11,7 @@ json.shallow shallow
json.try_id score.try_id
json.updated_at score.updated_at
json.user_id score.user_id
-json.email score.user.email
+json.email score&.user&.email
json.queries score.queries unless shallow
From 4ccfd7404360e2a97912e413d9ca31203c619892 Mon Sep 17 00:00:00 2001
From: Eric Pugh
Date: Fri, 20 Dec 2024 18:21:12 -0500
Subject: [PATCH 065/120] RunCaseJob is better name
---
.../admin/query_runner_controller.rb | 20 ------------------
app/controllers/admin/run_case_controller.rb | 20 ++++++++++++++++++
...ob.rb => enqueue_run_nightly_cases_job.rb} | 4 ++--
.../{query_runner_job.rb => run_case_job.rb} | 12 +++++------
.../_notification.turbo_stream.erb | 2 +-
.../_notification_case.turbo_stream.erb | 2 +-
.../{query_runner => run_case}/index.html.erb | 6 +++---
config/recurring.yml | 11 +++++-----
config/routes.rb | 21 ++++++++++++++-----
9 files changed, 54 insertions(+), 44 deletions(-)
delete mode 100644 app/controllers/admin/query_runner_controller.rb
create mode 100644 app/controllers/admin/run_case_controller.rb
rename app/jobs/{enqueue_nightly_query_runner_job.rb => enqueue_run_nightly_cases_job.rb} (64%)
rename app/jobs/{query_runner_job.rb => run_case_job.rb} (84%)
rename app/views/admin/{query_runner => run_case}/_notification.turbo_stream.erb (93%)
rename app/views/admin/{query_runner => run_case}/_notification_case.turbo_stream.erb (92%)
rename app/views/admin/{query_runner => run_case}/index.html.erb (81%)
diff --git a/app/controllers/admin/query_runner_controller.rb b/app/controllers/admin/query_runner_controller.rb
deleted file mode 100644
index abc6710dd..000000000
--- a/app/controllers/admin/query_runner_controller.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-module Admin
- class QueryRunnerController < ApplicationController
- before_action :set_case, only: [ :run_queries ]
-
- def index
- end
-
- # rubocop:disable Layout/LineLength
- def run_queries
- @case = Case.find(params['case_id']) # any case is accessible!
- @try = @case.tries.where(try_number: params['try_number']).first
- QueryRunnerJob.perform_later @case, @try
- redirect_to admin_query_runner_index_path,
- notice: "Query Runner Job was queued up for case id #{@case.id} / #{@case.case_name} and try #{@try.name}."
- end
- # rubocop:enable Layout/LineLength
- end
-end
diff --git a/app/controllers/admin/run_case_controller.rb b/app/controllers/admin/run_case_controller.rb
new file mode 100644
index 000000000..059bd85a5
--- /dev/null
+++ b/app/controllers/admin/run_case_controller.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Admin
+ class RunCaseController < ApplicationController
+ before_action :set_case, only: [ :run_case ]
+
+ def index
+ end
+
+ # rubocop:disable Layout/LineLength
+ def run_case
+ @case = Case.find(params['case_id']) # any case is accessible!
+ @try = @case.tries.where(try_number: params['try_number']).first
+ RunCaseJob.perform_later @case, @try
+ redirect_to admin_run_case_index_path,
+ notice: "Run Case Job was queued up for case id #{@case.id} / #{@case.case_name} and try #{@try.name}."
+ end
+ # rubocop:enable Layout/LineLength
+ end
+end
diff --git a/app/jobs/enqueue_nightly_query_runner_job.rb b/app/jobs/enqueue_run_nightly_cases_job.rb
similarity index 64%
rename from app/jobs/enqueue_nightly_query_runner_job.rb
rename to app/jobs/enqueue_run_nightly_cases_job.rb
index 1e9a5b512..d05b990f6 100644
--- a/app/jobs/enqueue_nightly_query_runner_job.rb
+++ b/app/jobs/enqueue_run_nightly_cases_job.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
-class EnqueueNightlyQueryRunnerJob < ApplicationJob
+class EnqueueRunNightlyCasesJob < ApplicationJob
queue_as :default
def perform(*_args)
Case.all.nightly_run.each do |kase|
try = kase.tries.first # new to old ;-)
- QueryRunnerJob.perform_later kase, try
+ RunCaseJob.perform_later kase, try
end
end
end
diff --git a/app/jobs/query_runner_job.rb b/app/jobs/run_case_job.rb
similarity index 84%
rename from app/jobs/query_runner_job.rb
rename to app/jobs/run_case_job.rb
index c06b03c21..5de75bb2e 100644
--- a/app/jobs/query_runner_job.rb
+++ b/app/jobs/run_case_job.rb
@@ -4,7 +4,7 @@
require 'faraday'
require 'faraday/follow_redirects'
-class QueryRunnerJob < ApplicationJob
+class RunCaseJob < ApplicationJob
queue_as :bulk_processing
# rubocop:disable Metrics/MethodLength
@@ -25,10 +25,8 @@ def perform acase, atry
response_code = response.status
response_body = response.body
- puts "Does response_code == 200? #{response_code} is #{200 == response_code}"
# need to deal with errors better.
if 200 == response_code
- puts 'WE GOT A 200'
# this is all rough... just to get some snapshot_docs...
docs = fetch_service.extract_docs_from_response_body_for_solr response_body
fetch_service.store_query_results query, docs, response_code, response_body
@@ -39,14 +37,14 @@ def perform acase, atry
Turbo::StreamsChannel.broadcast_render_to(
:notifications,
target: 'notifications',
- partial: 'admin/query_runner/notification',
+ partial: 'admin/run_case/notification',
locals: { query: query, query_count: query_count, counter: counter }
)
Turbo::StreamsChannel.broadcast_render_to(
:notifications,
target: "notifications-case-#{acase.id}",
- partial: 'admin/query_runner/notification_case',
+ partial: 'admin/run_case/notification_case',
locals: { acase: acase, query: query, query_count: query_count, counter: counter }
)
end
@@ -56,14 +54,14 @@ def perform acase, atry
Turbo::StreamsChannel.broadcast_render_to(
:notifications,
target: 'notifications',
- partial: 'admin/query_runner/notification',
+ partial: 'admin/run_case/notification',
locals: { query: nil, query_count: query_count, counter: -1 }
)
Turbo::StreamsChannel.broadcast_render_to(
:notifications,
target: "notifications-case-#{acase.id}",
- partial: 'admin/query_runner/notification_case',
+ partial: 'admin/run_case/notification_case',
locals: { acase: acase, query: nil, query_count: query_count, counter: -1 }
)
diff --git a/app/views/admin/query_runner/_notification.turbo_stream.erb b/app/views/admin/run_case/_notification.turbo_stream.erb
similarity index 93%
rename from app/views/admin/query_runner/_notification.turbo_stream.erb
rename to app/views/admin/run_case/_notification.turbo_stream.erb
index 5ef103aad..19adfae72 100644
--- a/app/views/admin/query_runner/_notification.turbo_stream.erb
+++ b/app/views/admin/run_case/_notification.turbo_stream.erb
@@ -13,7 +13,7 @@
<% else %>
- Query Runner Job has completed.
+ Run Case Job has completed.
<% end %>
diff --git a/app/views/admin/query_runner/_notification_case.turbo_stream.erb b/app/views/admin/run_case/_notification_case.turbo_stream.erb
similarity index 92%
rename from app/views/admin/query_runner/_notification_case.turbo_stream.erb
rename to app/views/admin/run_case/_notification_case.turbo_stream.erb
index 48e549615..9d510a725 100644
--- a/app/views/admin/query_runner/_notification_case.turbo_stream.erb
+++ b/app/views/admin/run_case/_notification_case.turbo_stream.erb
@@ -10,7 +10,7 @@
<% else %>
- Query Runner Job has completed.
+ Run Case Job has completed.
<% end %>
diff --git a/app/views/admin/query_runner/index.html.erb b/app/views/admin/run_case/index.html.erb
similarity index 81%
rename from app/views/admin/query_runner/index.html.erb
rename to app/views/admin/run_case/index.html.erb
index 6aeef9faa..315392a8f 100644
--- a/app/views/admin/query_runner/index.html.erb
+++ b/app/views/admin/run_case/index.html.erb
@@ -1,8 +1,8 @@
<%= turbo_stream_from(:notifications) %>
-
Query Runner
+
Run Case
- This tool is for testing out background query running in Quepid.
+ This tool is for testing out background query running in Quepid for a case.
After clicking the button you should see a set of updates in a alert box.
@@ -13,7 +13,7 @@
-<%= form_with(url: run_queries_admin_query_runner_index_path, method: :post) do |form| %>
+<%= form_with(url: run_case_admin_run_case_index_path, method: :post) do |form| %>
<%= form.label :case_id %>
<%= form.text_field :case_id %>
diff --git a/config/recurring.yml b/config/recurring.yml
index caadfdd30..2770e5670 100644
--- a/config/recurring.yml
+++ b/config/recurring.yml
@@ -1,7 +1,8 @@
development:
- nightly_jobs_command:
- class: EnqueueNightlyQueryRunnerJob
- schedule: every hour
+ nightly_run_cases_command:
+ class: EnqueueRunNightlyCasesJob
+ #schedule: every hour
+ schedule: every day at 1 am
# periodic_cleanup:
# class: QueryRunnerJob
@@ -11,8 +12,8 @@ development:
production:
- nightly_jobs_command:
- class: EnqueueNightlyQueryRunnerJob
+ nightly_run_cases_command:
+ class: EnqueueRunNightlyCasesJob
schedule: every hour
#schedule: every day at 1 am
diff --git a/config/routes.rb b/config/routes.rb
index f4aa06fa7..f17bfe303 100755
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,11 +1,10 @@
# frozen_string_literal: true
-# rubocop:disable Layout/LineLength
-
# == Route Map
#
# Prefix Verb URI Pattern Controller#Action
-# /cable #, @worker_pool_size=4, @disable_request_forgery_protection=true, @allow_same_origin_as_host=true, @filter_parameters=[:passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc, :document_fields, "query_doc_pair.document_fields", "snapshot.docs", "snapshot_doc.explain", "snapshot_doc.fields"], @health_check_application=#, @logger=#, @formatter=#, @logdev=#, @binmode=false, @reraise_write_errors=[], @mon_data=#, @mon_data_owner_object_id=7340>, @level_override={}>], @progname="Broadcast", @formatter=#>, @cable={"adapter"=>"solid_cable", "polling_interval"=>"0.1.seconds", "message_retention"=>"1.day", "silence_polling"=>true}, @mount_path="/cable", @precompile_assets=true, @allowed_request_origins="*", @url="/cable">, @mutex=#, @pubsub=nil, @worker_pool=nil, @event_loop=nil, @remote_connections=nil>
+# debugbar /_debugbar Debugbar::Engine
+# /cable #, @worker_pool_size=4, @disable_request_forgery_protection=true, @allow_same_origin_as_host=true, @filter_parameters=[:passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc, :document_fields, "query_doc_pair.document_fields", "snapshot.docs", "snapshot_doc.explain", "snapshot_doc.fields"], @health_check_application=#, @logger=#, @formatter=#, @logdev=#, @binmode=false, @reraise_write_errors=[], @mon_data=#, @mon_data_owner_object_id=7340>, @level_override={}>], @progname="Broadcast", @formatter=#>, @cable={"adapter"=>"solid_cable", "polling_interval"=>"0.1.seconds", "message_retention"=>"1.day", "silence_polling"=>true}, @mount_path="/cable", @precompile_assets=true, @allowed_request_origins="*", @url="/cable">, @mutex=#, @pubsub=nil, @worker_pool=nil, @event_loop=nil, @remote_connections=nil>
# apipie_apipie_checksum GET /apipie/apipie_checksum(.:format) apipie/apipies#apipie_checksum {:format=>/json/}
# apipie_apipie GET /apipie(/:version)(/:resource)(/:method)(.:format) apipie/apipies#index {:version=>/[^\/]+/, :resource=>/[^\/]+/, :method=>/[^\/]+/}
# active_storage_db /active_storage_db ActiveStorageDB::Engine
@@ -165,6 +164,8 @@
# DELETE /admin/announcements/:id(.:format) admin/announcements#destroy
# test_background_job_admin_websocket_tester_index POST /admin/websocket_tester/test_background_job(.:format) admin/websocket_tester#test_background_job
# admin_websocket_tester_index GET /admin/websocket_tester(.:format) admin/websocket_tester#index
+# run_case_admin_run_case_index POST /admin/run_case/run_case(.:format) admin/run_case#run_case
+# admin_run_case_index GET /admin/run_case(.:format) admin/run_case#index
# GET /rails/mailers(.:format) rails/mailers#index
# GET /rails/mailers/*path(.:format) rails/mailers#preview
# api_test GET /api/test(.:format) api/api#test {:format=>:json}
@@ -323,6 +324,7 @@
# cases_import GET /cases/import(.:format) core#index
# teams_core GET /teams(/:id)(.:format) core#teams
# scorers GET /scorers(.:format) core#index
+# GET /*page(.:format) pages#show
# turbo_recede_historical_location GET /recede_historical_location(.:format) turbo/native/navigation#recede
# turbo_resume_historical_location GET /resume_historical_location(.:format) turbo/native/navigation#resume
# turbo_refresh_historical_location GET /refresh_historical_location(.:format) turbo/native/navigation#refresh
@@ -351,6 +353,13 @@
# update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update
# rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create
#
+# Routes for Debugbar::Engine:
+# /cable #, @worker_pool_size=4, @disable_request_forgery_protection=true, @allow_same_origin_as_host=true, @filter_parameters=[:passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc, :document_fields, "query_doc_pair.document_fields", "snapshot.docs", "snapshot_doc.explain", "snapshot_doc.fields"], @health_check_application=#, @logger=#, @formatter=#, @logdev=#, @binmode=false, @reraise_write_errors=[], @mon_data=#, @mon_data_owner_object_id=7340>, @level_override={}>], @progname="Broadcast", @formatter=#>, @cable={"adapter"=>"solid_cable", "polling_interval"=>"0.1.seconds", "message_retention"=>"1.day", "silence_polling"=>true}, @mount_path="/cable", @precompile_assets=true, @allowed_request_origins="*", @url="/cable">, @mutex=#, @pubsub=nil, @worker_pool=nil, @event_loop=nil, @remote_connections=nil>
+# poll GET /poll(.:format) debugbar/polling#poll
+# poll_confirm OPTIONS /poll/confirm(.:format) debugbar/polling#confirm
+# POST /poll/confirm(.:format) debugbar/polling#confirm
+# assets_script GET /assets/script(.:format) debugbar/assets#js
+#
# Routes for ActiveStorageDB::Engine:
# service GET /files/:encoded_key/*filename(.:format) active_storage_db/files#show
# update_service PUT /files/:encoded_token(.:format) active_storage_db/files#update
@@ -416,6 +425,8 @@
# visits POST /visits(.:format) ahoy/visits#create
# events POST /events(.:format) ahoy/events#create
+# rubocop:disable Layout/LineLength
+
# rubocop:disable Metrics/BlockLength
Rails.application.routes.draw do
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
@@ -543,8 +554,8 @@
resources :websocket_tester, only: [ :index ] do
post 'test_background_job', on: :collection
end
- resources :query_runner, only: [ :index ] do
- post 'run_queries', on: :collection
+ resources :run_case, only: [ :index ] do
+ post 'run_case', on: :collection
end
end
From 9a16981b8916d27f15eb407c5d346f1bab130556 Mon Sep 17 00:00:00 2001
From: Eric Pugh
Date: Fri, 20 Dec 2024 18:21:26 -0500
Subject: [PATCH 066/120] actually persist hte change
---
app/controllers/api/v1/cases_controller.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/controllers/api/v1/cases_controller.rb b/app/controllers/api/v1/cases_controller.rb
index d02509ac0..8e22f77ee 100644
--- a/app/controllers/api/v1/cases_controller.rb
+++ b/app/controllers/api/v1/cases_controller.rb
@@ -113,7 +113,7 @@ def destroy
private
def case_params
- params.require(:case).permit(:case_name, :scorer_id, :archived, :book_id, :last_try_number)
+ params.require(:case).permit(:case_name, :scorer_id, :archived, :book_id, :last_try_number, :nightly)
end
def default_scorer_removed? params = {}
From a8172a9d2d77a1f4a5de6bd8c55e4063779b79cd Mon Sep 17 00:00:00 2001
From: Eric Pugh
Date: Fri, 20 Dec 2024 18:21:32 -0500
Subject: [PATCH 067/120] Dead code
---
app/models/case.rb | 7 -------
1 file changed, 7 deletions(-)
diff --git a/app/models/case.rb b/app/models/case.rb
index ea2cdca87..5c344d1e4 100644
--- a/app/models/case.rb
+++ b/app/models/case.rb
@@ -185,13 +185,6 @@ def public_id
Rails.application.message_verifier('magic').generate(id)
end
- def self.enqueue_nightly_query_runner_jobs
- Case.all.nightly_run.each do |kase|
- try = kase.tries.last
- QueryRunnerJob.perform_later kase, try
- end
- end
-
private
def set_scorer
From a191c8976bbe9453f1433badf25e58a0d9a2d9b8 Mon Sep 17 00:00:00 2001
From: Eric Pugh
Date: Fri, 20 Dec 2024 18:21:58 -0500
Subject: [PATCH 068/120] Move the heavy web response to it's own table
---
app/models/snapshot_query.rb | 2 +-
app/models/web_request.rb | 19 +++++++++++++++++++
app/services/fetch_service.rb | 7 +++++--
...73642_move_response_body_to_web_request.rb | 16 ++++++++++++++++
db/schema.rb | 13 +++++++++++--
test/fixtures/snapshot_queries.yml | 1 -
6 files changed, 52 insertions(+), 6 deletions(-)
create mode 100644 app/models/web_request.rb
create mode 100644 db/migrate/20241220173642_move_response_body_to_web_request.rb
diff --git a/app/models/snapshot_query.rb b/app/models/snapshot_query.rb
index e100747cd..8e11bb108 100644
--- a/app/models/snapshot_query.rb
+++ b/app/models/snapshot_query.rb
@@ -7,7 +7,6 @@
# id :integer not null, primary key
# all_rated :boolean
# number_of_results :integer
-# response_body :binary(429496729
# response_status :integer
# score :float(24)
# query_id :integer
@@ -27,6 +26,7 @@
class SnapshotQuery < ApplicationRecord
belongs_to :snapshot, optional: true # shouldn't be
belongs_to :query, optional: true # shouldn't be
+ has_one :web_request, dependent: :destroy
has_many :snapshot_docs, -> { order(position: :asc) },
dependent: :destroy,
inverse_of: :snapshot_query
diff --git a/app/models/web_request.rb b/app/models/web_request.rb
new file mode 100644
index 000000000..815dd89ab
--- /dev/null
+++ b/app/models/web_request.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+# app/models/web_request.rb
+# == Schema Information
+#
+# Table name: web_requests
+#
+# id :bigint not null, primary key
+# integer :integer
+# request :binary(65535)
+# response :binary(429496729
+# response_status :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+# snapshot_query_id :integer
+#
+class WebRequest < ApplicationRecord
+ belongs_to :snapshot_query, optional: true
+end
diff --git a/app/services/fetch_service.rb b/app/services/fetch_service.rb
index 0be612841..809b0a1a8 100644
--- a/app/services/fetch_service.rb
+++ b/app/services/fetch_service.rb
@@ -93,8 +93,11 @@ def store_query_results query, docs, response_status, response_body
snapshot_query = @snapshot.snapshot_queries.create(
query: query,
number_of_results: docs.count,
- response_status: response_status,
- response_body: response_body
+ response_status: response_status
+ )
+ snapshot_query.create_web_request(
+ response_status: response_status,
+ response: response_body
)
snapshot_manager = SnapshotManager.new(@snapshot)
query_docs = snapshot_manager.setup_docs_for_query(snapshot_query, docs)
diff --git a/db/migrate/20241220173642_move_response_body_to_web_request.rb b/db/migrate/20241220173642_move_response_body_to_web_request.rb
new file mode 100644
index 000000000..63bd10452
--- /dev/null
+++ b/db/migrate/20241220173642_move_response_body_to_web_request.rb
@@ -0,0 +1,16 @@
+class MoveResponseBodyToWebRequest < ActiveRecord::Migration[8.0]
+ def change
+ create_table :web_requests do |t|
+ t.integer :snapshot_query_id
+ t.binary :request
+ # okay with duplicating this in snapshot_queries for now.
+ t.integer :response_status, :integer
+ t.binary :response, limit: 100.megabytes
+
+ t.timestamps
+ end
+
+ remove_column :snapshot_queries, :response_body
+ end
+
+end
diff --git a/db/schema.rb b/db/schema.rb
index 6e585e0b7..22bc3ec85 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.0].define(version: 2024_12_18_173819) do
+ActiveRecord::Schema[8.0].define(version: 2024_12_20_173642) do
create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_bin", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
@@ -360,7 +360,6 @@
t.boolean "all_rated"
t.integer "number_of_results"
t.integer "response_status"
- t.binary "response_body", size: :long
t.index ["query_id"], name: "query_id"
t.index ["snapshot_id"], name: "snapshot_id"
end
@@ -600,6 +599,16 @@
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, length: 191
end
+ create_table "web_requests", charset: "utf8mb4", collation: "utf8mb4_bin", force: :cascade do |t|
+ t.integer "snapshot_query_id"
+ t.binary "request"
+ t.integer "response_status"
+ t.integer "integer"
+ t.binary "response", size: :long
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "annotations", "users"
diff --git a/test/fixtures/snapshot_queries.yml b/test/fixtures/snapshot_queries.yml
index 7150cbcec..b5aa07817 100644
--- a/test/fixtures/snapshot_queries.yml
+++ b/test/fixtures/snapshot_queries.yml
@@ -5,7 +5,6 @@
# id :integer not null, primary key
# all_rated :boolean
# number_of_results :integer
-# response_body :binary(429496729
# response_status :integer
# score :float(24)
# query_id :integer
From bfe4ff0088611e31af2d091062e48a4194d94219 Mon Sep 17 00:00:00 2001
From: Eric Pugh
Date: Fri, 20 Dec 2024 18:22:06 -0500
Subject: [PATCH 069/120] part of job renaming
---
app/views/admin/home/index.html.erb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/views/admin/home/index.html.erb b/app/views/admin/home/index.html.erb
index 7a94e2c8b..13c695df9 100644
--- a/app/views/admin/home/index.html.erb
+++ b/app/views/admin/home/index.html.erb
@@ -36,7 +36,7 @@