qa_server 0.1.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +71 -0
- data/Gemfile +55 -0
- data/Gemfile.lock +411 -0
- data/LICENSE +201 -0
- data/README.md +187 -0
- data/Rakefile +18 -0
- data/app/assets/images/qa_server/LD4L-bg.png +0 -0
- data/app/assets/images/qa_server/cornell-reduced-white.svg +160 -0
- data/app/assets/images/qa_server/iowa-logo.png +0 -0
- data/app/assets/images/qa_server/samvera-fall-font1-1000w-light.png +0 -0
- data/app/assets/javascripts/qa_server.js +0 -0
- data/app/assets/stylesheets/qa_server/_authorities.scss +0 -0
- data/app/assets/stylesheets/qa_server/_check-status.scss +72 -0
- data/app/assets/stylesheets/qa_server/_footer.scss +20 -0
- data/app/assets/stylesheets/qa_server/_header.scss +51 -0
- data/app/assets/stylesheets/qa_server/_home-page.scss +15 -0
- data/app/assets/stylesheets/qa_server/_monitor-status.scss +13 -0
- data/app/assets/stylesheets/qa_server/_qa_server.scss +36 -0
- data/app/assets/stylesheets/qa_server/_styles.scss +27 -0
- data/app/assets/stylesheets/qa_server/_usage.scss +0 -0
- data/app/controllers/qa_server/authority_list_controller.rb +14 -0
- data/app/controllers/qa_server/authority_validation_controller.rb +77 -0
- data/app/controllers/qa_server/check_status_controller.rb +38 -0
- data/app/controllers/qa_server/homepage_controller.rb +8 -0
- data/app/controllers/qa_server/monitor_status_controller.rb +79 -0
- data/app/controllers/qa_server/usage_controller.rb +8 -0
- data/app/loggers/qa_server/scenario_logger.rb +84 -0
- data/app/models/qa_server/authority_scenario.rb +35 -0
- data/app/models/qa_server/authority_status.rb +18 -0
- data/app/models/qa_server/authority_status_failure.rb +7 -0
- data/app/models/qa_server/scenarios.rb +71 -0
- data/app/models/qa_server/search_scenario.rb +58 -0
- data/app/models/qa_server/term_scenario.rb +50 -0
- data/app/presenters/qa_server/authority_list_presenter.rb +22 -0
- data/app/presenters/qa_server/check_status_presenter.rb +96 -0
- data/app/presenters/qa_server/monitor_status_presenter.rb +108 -0
- data/app/services/qa_server/authority_lister_service.rb +31 -0
- data/app/services/qa_server/authority_loader_service.rb +38 -0
- data/app/services/qa_server/authority_validator_service.rb +41 -0
- data/app/services/qa_server/database_migrator.rb +70 -0
- data/app/services/qa_server/scenarios_loader_service.rb +57 -0
- data/app/validators/qa_server/scenario_validator.rb +134 -0
- data/app/validators/qa_server/search_scenario_validator.rb +84 -0
- data/app/validators/qa_server/term_scenario_validator.rb +42 -0
- data/app/views/layouts/qa_server.html.erb +65 -0
- data/app/views/qa_server/authority_list/index.html.erb +27 -0
- data/app/views/qa_server/check_status/index.html.erb +104 -0
- data/app/views/qa_server/homepage/index.html.erb +10 -0
- data/app/views/qa_server/monitor_status/index.html.erb +78 -0
- data/app/views/qa_server/usage/index.html.erb +106 -0
- data/app/views/shared/_footer.html.erb +24 -0
- data/config/locales/qa_server.en.yml +68 -0
- data/config/routes.rb +12 -0
- data/lib/generators/qa_server/assets_generator.rb +35 -0
- data/lib/generators/qa_server/config_generator.rb +31 -0
- data/lib/generators/qa_server/install_generator.rb +60 -0
- data/lib/generators/qa_server/models_generator.rb +18 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/agrovoc_direct.json +69 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/agrovoc_ld4l_cache.json +85 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/dbpedia_direct.json +36 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/dbpedia_ld4l_cache.json +83 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/geonames_direct.json +68 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/geonames_ld4l_cache.json +102 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/getty_aat_ld4l_cache.json +109 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/getty_tgn_ld4l_cache.json +72 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/getty_ulan_ld4l_cache.json +81 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/loc_direct.json +47 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/locgenres_ld4l_cache.json +99 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/locnames_ld4l_cache.json +89 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/locsubjects_ld4l_cache.json +82 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/mesh_ld4l_cache.json +70 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/nalt_direct.json +32 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/nalt_ld4l_cache.json +84 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/oclcfast_direct.json +81 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/oclcfast_ld4l_cache.json +86 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/agrovoc_direct_validation.yml +9 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/agrovoc_ld4l_cache_validation.yml +9 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/agrovoc_validation.yml +9 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/dbpedia_direct_validation.yml +6 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/dbpedia_ld4l_cache_validation.yml +9 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/geonames_direct_validation.yml +9 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/geonames_ld4l_cache_validation.yml +41 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/getty_aat_ld4l_cache_validation.yml +115 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/getty_tgn_ld4l_cache_validation.yml +9 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/getty_ulan_ld4l_cache_validation.yml +15 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/loc_direct_validation.yml +25 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/loc_validation.yml +22 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/locgenres_ld4l_cache_validation.yml +99 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/locnames_ld4l_cache_validation.yml +27 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/locsubjects_ld4l_cache_validation.yml +27 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/mesh_ld4l_cache_validation.yml +9 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/nalt_direct_validation.yml +6 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/nalt_ld4l_cache_validation.yml +9 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/oclc_fast_validation.yml +58 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/oclcfast_direct_validation.yml +58 -0
- data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/oclcfast_ld4l_cache_validation.yml +30 -0
- data/lib/generators/qa_server/templates/config/locales/qa_server.en.yml +9 -0
- data/lib/generators/qa_server/templates/db/migrate/20180313045551_create_cron_authority_status.rb.erb +9 -0
- data/lib/generators/qa_server/templates/db/migrate/20180508045549_rename_cron_authority_status_to_authority_status.rb.erb +5 -0
- data/lib/generators/qa_server/templates/db/migrate/20180508045551_create_authority_status_failure.rb.erb +15 -0
- data/lib/generators/qa_server/templates/qa_server.scss +5 -0
- data/lib/qa_server/engine.rb +19 -0
- data/lib/qa_server/version.rb +3 -0
- data/lib/qa_server.rb +6 -0
- data/lib/tasks/install.rake +8 -0
- data/lib/tasks/qa_server_tasks.rake +4 -0
- data/qa_server.gemspec +39 -0
- data/spec/.gitignore +1 -0
- data/spec/rails_helper.rb +2 -0
- data/spec/spec_helper.rb +283 -0
- data/spec/test_app_templates/Gemfile.extra +5 -0
- data/spec/test_app_templates/lib/generators/test_app_generator.rb +15 -0
- data/tasks/qa_server_dev.rake +16 -0
- metadata +275 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Holds all scenarios for an authority.
|
|
2
|
+
module QaServer
|
|
3
|
+
class Scenarios
|
|
4
|
+
|
|
5
|
+
AUTHORITY_SCENARIO = 'authority'.freeze
|
|
6
|
+
TERM_SCENARIOS = 'term'.freeze
|
|
7
|
+
SEARCH_SCENARIOS = 'search'.freeze
|
|
8
|
+
|
|
9
|
+
# @return [Qa::Authorities::LinkedData::GenericAuthority] authority instance the scenarios run against
|
|
10
|
+
attr_reader :authority
|
|
11
|
+
|
|
12
|
+
# @return [String] name of the authority the scenarios run against (e.g. 'agrovoc_direct')
|
|
13
|
+
attr_reader :authority_name
|
|
14
|
+
|
|
15
|
+
# @return [Array<TermScenario>] the term scenarios to run against the authority
|
|
16
|
+
attr_reader :term_scenarios
|
|
17
|
+
|
|
18
|
+
# @return [Array<SearchScenario>] the search scenarios to run against the authority
|
|
19
|
+
attr_reader :search_scenarios
|
|
20
|
+
|
|
21
|
+
# @param authority [Qa::Authorities::LinkedData::GenericAuthority] the instance of the QA authority
|
|
22
|
+
# @param authoity_name [String] the name of the authority the scenario tests (e.g. "agrovoc_direct")
|
|
23
|
+
# @param scenarios_config [Hash] configurations from the yml file for all scenarios for an authority
|
|
24
|
+
def initialize(authority:, authority_name:, scenarios_config:)
|
|
25
|
+
@authority = authority
|
|
26
|
+
@authority_name = authority_name
|
|
27
|
+
@scenarios_config = scenarios_config
|
|
28
|
+
parse_term_scenarios
|
|
29
|
+
parse_search_scenarios
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def parse_term_scenarios
|
|
35
|
+
@term_scenarios = []
|
|
36
|
+
term_scenarios_config.each do |term_scenario_config|
|
|
37
|
+
@term_scenarios << TermScenario.new(authority: authority,
|
|
38
|
+
authority_name: authority_name,
|
|
39
|
+
authority_scenario_config: authority_scenario_config,
|
|
40
|
+
scenario_config: term_scenario_config)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def parse_search_scenarios
|
|
45
|
+
@search_scenarios = []
|
|
46
|
+
search_scenarios_config.each do |search_scenario_config|
|
|
47
|
+
@search_scenarios << SearchScenario.new(authority: authority,
|
|
48
|
+
authority_name: authority_name,
|
|
49
|
+
authority_scenario_config: authority_scenario_config,
|
|
50
|
+
scenario_config: search_scenario_config)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def scenarios_config
|
|
55
|
+
@scenarios_config
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def authority_scenario_config
|
|
59
|
+
scenarios_config[AUTHORITY_SCENARIO]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def term_scenarios_config
|
|
63
|
+
scenarios_config[TERM_SCENARIOS] || []
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def search_scenarios_config
|
|
67
|
+
scenarios_config[SEARCH_SCENARIOS] || []
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# This class parses the search configuration from the yml file into the parts needed by the search scenario validator.
|
|
2
|
+
module QaServer
|
|
3
|
+
class SearchScenario < AuthorityScenario
|
|
4
|
+
|
|
5
|
+
# @return [String] query being executed by this scenario
|
|
6
|
+
attr_reader :query
|
|
7
|
+
|
|
8
|
+
# @return [Hash] replacements parameters used to construct the URL executed by this scenario
|
|
9
|
+
attr_reader :replacements
|
|
10
|
+
|
|
11
|
+
# @return [Integer] expected_by_position designates the maximum position in search results of subject_uri, if specified, for this scenario to be considered passing
|
|
12
|
+
attr_reader :expected_by_position
|
|
13
|
+
|
|
14
|
+
# @return [String] subject_uri, if specified, should be in the search results between position 1 and expected_by_position
|
|
15
|
+
attr_reader :subject_uri
|
|
16
|
+
|
|
17
|
+
MAX_RECORDS = '4'.freeze
|
|
18
|
+
DEFAULT_REPLACEMENTS = { maxRecords: MAX_RECORDS }
|
|
19
|
+
DEFAULT_POSITION = nil
|
|
20
|
+
DEFAULT_SUBJECT_URI = nil
|
|
21
|
+
|
|
22
|
+
# @param authority [Qa::Authorities::LinkedData::GenericAuthority] the instance of the QA authority
|
|
23
|
+
# @param authoity_name [Symbol] the name of the authority the scenario tests (e.g. :AGROVOC_DIRECT)
|
|
24
|
+
# @param authority_scenario_config [Hash] configurations from the yml file that pertain to all scenarios regardless of type
|
|
25
|
+
# @param scenario_config [Hash] configuration from the yml file that are specific to a search scenario
|
|
26
|
+
def initialize(authority:, authority_name:, authority_scenario_config:, scenario_config:)
|
|
27
|
+
super
|
|
28
|
+
@query = scenario_config['query']
|
|
29
|
+
@subauthority_name = scenario_config['subauth'] || DEFAULT_SUBAUTH
|
|
30
|
+
@min_result_size = scenario_config['result_size'] || MIN_EXPECTED_SIZE
|
|
31
|
+
@replacements = scenario_config['replacements'] || DEFAULT_REPLACEMENTS
|
|
32
|
+
@expected_by_position = scenario_config['position'] || DEFAULT_POSITION
|
|
33
|
+
@subject_uri = scenario_config['subject_uri'] || DEFAULT_SUBJECT_URI
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Generate an example URL that can be called in a browser or through curl
|
|
37
|
+
# @return [String] the example URL
|
|
38
|
+
def url
|
|
39
|
+
subauth = "/#{subauthority_name}" if subauthority?
|
|
40
|
+
prefix = "/#{QA_ENGINE_MOUNT}/search/linked_data/#{authority_name.downcase}#{subauth}"
|
|
41
|
+
"#{prefix}?q=#{query}#{url_replacements}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
# Convert replacements hash into URL parameters
|
|
47
|
+
def url_replacements
|
|
48
|
+
return "&maxRecords=#{MAX_RECORDS}" unless replacements
|
|
49
|
+
param_replacements = ""
|
|
50
|
+
replacements.each { |k, v| param_replacements += "&#{k}=#{v}" }
|
|
51
|
+
param_replacements
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def subauthority?
|
|
55
|
+
subauthority_name.present?
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'erb'
|
|
2
|
+
|
|
3
|
+
# This class parses the term configuration from the yml file into the parts needed by the term scenario validator.
|
|
4
|
+
module QaServer
|
|
5
|
+
class TermScenario < AuthorityScenario
|
|
6
|
+
include ERB::Util
|
|
7
|
+
|
|
8
|
+
# @return [String] id or uri of the term being fetched by this scenario
|
|
9
|
+
attr_reader :identifier
|
|
10
|
+
|
|
11
|
+
# @param authority [Qa::Authorities::LinkedData::GenericAuthority] the instance of the QA authority
|
|
12
|
+
# @param authoity_name [Symbol] the name of the authority the scenario tests (e.g. :AGROVOC_DIRECT)
|
|
13
|
+
# @param authority_scenario_config [Hash] configurations from the yml file that pertain to all scenarios regardless of type
|
|
14
|
+
# @param scenario_config [Hash] configuration from the yml file that are specific to a term scenario
|
|
15
|
+
def initialize(authority:, authority_name:, authority_scenario_config:, scenario_config:)
|
|
16
|
+
super
|
|
17
|
+
@identifier = scenario_config['identifier']
|
|
18
|
+
@subauthority_name = scenario_config['subauth'] || DEFAULT_SUBAUTH
|
|
19
|
+
@min_result_size = scenario_config['min_result_size'] || MIN_EXPECTED_SIZE
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Generate an example URL that can be called in a browser or through curl
|
|
23
|
+
# @return [String] the example URL
|
|
24
|
+
def url
|
|
25
|
+
subauth = "/#{subauthority_name}" if subauthority_name.present?
|
|
26
|
+
prefix = "/#{QA_ENGINE_MOUNT}/show/linked_data/#{authority_name.downcase}#{subauth}"
|
|
27
|
+
"#{prefix}/#{url_identifier}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
# Convert identifier into URL safe version with encoding if needed.
|
|
33
|
+
def url_identifier
|
|
34
|
+
return uri_encode(identifier) if encode?
|
|
35
|
+
identifier
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def subauthority?
|
|
39
|
+
subauthority_name.present?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def encode?
|
|
43
|
+
authority.auth_config.term.term_id_expects_uri?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def uri_encode(uri)
|
|
47
|
+
url_encode(uri).gsub(".", "%2E")
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# This presenter class provides all data needed by the view that show the list of authorities.
|
|
2
|
+
module QaServer
|
|
3
|
+
class AuthorityListPresenter
|
|
4
|
+
def initialize(urls_data:)
|
|
5
|
+
@urls_data = urls_data
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# @return [Array<Hash>] A list of status data for each scenario tested.
|
|
9
|
+
# @example
|
|
10
|
+
# { status: :PASS,
|
|
11
|
+
# status_label: '√',
|
|
12
|
+
# authority_name: 'LOCNAMES_LD4L_CACHE',
|
|
13
|
+
# subauthority_name: 'person',
|
|
14
|
+
# service: 'ld4l_cache',
|
|
15
|
+
# action: 'search',
|
|
16
|
+
# url: '/qa/search/linked_data/locnames_ld4l_cache/person?q=mark twain&maxRecords=4',
|
|
17
|
+
# err_message: '' }
|
|
18
|
+
def urls_data
|
|
19
|
+
@urls_data
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# This presenter class provides all data needed by the view that checks the status of authorities.
|
|
2
|
+
module QaServer
|
|
3
|
+
class CheckStatusPresenter
|
|
4
|
+
|
|
5
|
+
# @param authorities_list [Array<String>] a list of all loaded authorities' names
|
|
6
|
+
# @param status_data [Array<Hash>] a list of status data for each scenario tested
|
|
7
|
+
def initialize(authorities_list:, connection_status_data:, accuracy_status_data:)
|
|
8
|
+
@authorities_list = authorities_list
|
|
9
|
+
@connection_status_data = connection_status_data
|
|
10
|
+
@accuracy_status_data = accuracy_status_data
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @return [Array<String>] A list of all loaded authorities' names
|
|
14
|
+
# @example ['AGROVOC_DIRECT', 'AGROVOC_LD4L_CACHE', 'LOCNAMES_LD4L_CACHE']
|
|
15
|
+
def authorities_list
|
|
16
|
+
@authorities_list
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @return [Array<Hash>] A list of status data for each connection scenario tested.
|
|
20
|
+
# @example
|
|
21
|
+
# [ { status: :PASS,
|
|
22
|
+
# status_label: '√',
|
|
23
|
+
# authority_name: 'LOCNAMES_LD4L_CACHE',
|
|
24
|
+
# subauthority_name: 'person',
|
|
25
|
+
# service: 'ld4l_cache',
|
|
26
|
+
# action: 'search',
|
|
27
|
+
# url: '/qa/search/linked_data/locnames_ld4l_cache/person?q=mark twain&maxRecords=4',
|
|
28
|
+
# err_message: '' }, ... ]
|
|
29
|
+
def connection_status_data
|
|
30
|
+
@connection_status_data
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [Array<Hash>] A list of status data for each accuracy scenario tested.
|
|
34
|
+
# @example
|
|
35
|
+
# [ { status: :PASS,
|
|
36
|
+
# status_label: '√',
|
|
37
|
+
# authority_name: 'LOCNAMES_LD4L_CACHE',
|
|
38
|
+
# subauthority_name: 'person',
|
|
39
|
+
# service: 'ld4l_cache',
|
|
40
|
+
# action: 'search',
|
|
41
|
+
# expected: 10,
|
|
42
|
+
# actual: 8,
|
|
43
|
+
# url: '/qa/search/linked_data/locnames_ld4l_cache/person?q=mark twain&maxRecords=20',
|
|
44
|
+
# err_message: '' }, ... ]
|
|
45
|
+
def accuracy_status_data
|
|
46
|
+
@accuracy_status_data
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @return [Boolean] true if status data exists; otherwise false
|
|
50
|
+
def connection_status_data?
|
|
51
|
+
@connection_status_data.present?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @return [Boolean] true if status data exists; otherwise false
|
|
55
|
+
def accuracy_status_data?
|
|
56
|
+
@accuracy_status_data.present?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @return [String] the name of the css style class to use for the status cell based on the status of the scenario test.
|
|
60
|
+
def status_style_class(status)
|
|
61
|
+
"status-#{status[:status].to_s}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def value_all_collections
|
|
65
|
+
CheckStatusController::ALL_AUTHORITIES
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def value_check_param
|
|
69
|
+
AuthorityValidationController::VALIDATION_TYPE_PARAM
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def value_check_connections
|
|
73
|
+
AuthorityValidationController::VALIDATE_CONNECTIONS
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def label_check_connections
|
|
77
|
+
"#{value_check_param}_#{value_check_connections}".downcase.to_sym
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def value_check_accuracy
|
|
81
|
+
AuthorityValidationController::VALIDATE_ACCURACY
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def label_check_accuracy
|
|
85
|
+
"#{value_check_param}_#{value_check_accuracy}".downcase.to_sym
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def value_all_checks
|
|
89
|
+
AuthorityValidationController::ALL_VALIDATIONS
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def label_all_checks
|
|
93
|
+
"#{value_check_param}_#{value_all_checks}".downcase.to_sym
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# This presenter class provides all data needed by the view that monitors status of authorities.
|
|
2
|
+
module QaServer
|
|
3
|
+
class MonitorStatusPresenter
|
|
4
|
+
|
|
5
|
+
# @param authority_count [Integer] number of loaded authorities
|
|
6
|
+
# @param authority_status [AuthorityStatus] summary status of the latest run of test scenarios
|
|
7
|
+
# @param current_data [Array<Hash>] current set of failures for the latest test run, if any
|
|
8
|
+
# @param historical_data [Array<Hash>] data for past failures
|
|
9
|
+
def initialize(authority_count:, authority_status:, current_data:, historical_data:)
|
|
10
|
+
@authority_count = authority_count
|
|
11
|
+
@authority_status = authority_status
|
|
12
|
+
@current_failures = current_data
|
|
13
|
+
@history = historical_data
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @return [String] date of last test run
|
|
17
|
+
def last_updated
|
|
18
|
+
@authority_status.dt_stamp.in_time_zone("Eastern Time (US & Canada)").strftime("%m/%d/%y - %I:%M %p")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [String] date of first recorded test run
|
|
22
|
+
def first_updated
|
|
23
|
+
AuthorityStatus.first.dt_stamp.in_time_zone("Eastern Time (US & Canada)").strftime("%m/%d/%y - %I:%M %p")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @return [Integer] number of loaded authorities
|
|
27
|
+
def authorities_count
|
|
28
|
+
@authority_count
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [Integer] number of authorities with failing tests in the latest test run
|
|
32
|
+
def failing_authorities_count
|
|
33
|
+
@current_failures.map { |f| f[:authority_name] }.uniq.count
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [String] css style class representing whether all tests passed or any failed
|
|
37
|
+
def authorities_count_style
|
|
38
|
+
failures? ? 'status-bad' : 'status-good'
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Integer] number of tests in the latest test run
|
|
42
|
+
def tests_count
|
|
43
|
+
@authority_status.test_count
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @return [Integer] number of passing tests in the latest test run
|
|
47
|
+
def passing_tests_count
|
|
48
|
+
tests_count - failing_tests_count
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @return [Integer] number of failing tests in the latest test run
|
|
52
|
+
def failing_tests_count
|
|
53
|
+
@current_failures.count
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @return [String] css style class representing whether all tests passed or any failed
|
|
57
|
+
def failing_tests_style
|
|
58
|
+
failures? ? 'status-bad' : 'status-good'
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @return [Array<Hash>] A list of failures data in the latest test run, if any
|
|
62
|
+
# @example
|
|
63
|
+
# [ { status: :FAIL,
|
|
64
|
+
# status_label: 'X',
|
|
65
|
+
# authority_name: 'LOCNAMES_LD4L_CACHE',
|
|
66
|
+
# subauthority_name: 'person',
|
|
67
|
+
# service: 'ld4l_cache',
|
|
68
|
+
# action: 'search',
|
|
69
|
+
# url: '/qa/search/linked_data/locnames_ld4l_cache/person?q=mark twain&maxRecords=4',
|
|
70
|
+
# err_message: 'Exception: Something went wrong.' }, ... ]
|
|
71
|
+
def failures
|
|
72
|
+
@current_failures
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @return [Boolean] true if failure data exists for the latest test run; otherwise false
|
|
76
|
+
def failures?
|
|
77
|
+
failing_tests_count.positive?
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @return [Array<Hash>] historical test data to be displayed
|
|
81
|
+
# @example
|
|
82
|
+
# [ { days_failing: 1, authority_name: 'AGROVOC_DIRECT' },
|
|
83
|
+
# { days_failing: 3, authority_name: 'AGROVOC_LD4L_CACHE' },
|
|
84
|
+
# { days_failing: 0, authority_name: 'LOCNAMES_LD4L_CACHE' } ]
|
|
85
|
+
def history
|
|
86
|
+
# TODO: STUBED -- need to include history of past failures -- Question: How much data to save?
|
|
87
|
+
# Want to answer questions like...
|
|
88
|
+
# * # of failing days out of # of days run
|
|
89
|
+
# * # type of failures... can't load authority vs. exception vs. no data returned
|
|
90
|
+
# * for a given authority, did all tests fail or just a few?
|
|
91
|
+
# * are tests failing for a particular subauthority?
|
|
92
|
+
# * are tests failing for a particular type (search vs. term)
|
|
93
|
+
history = []
|
|
94
|
+
entry = { days_failing: 3,
|
|
95
|
+
authority_name: "FOO",
|
|
96
|
+
subauthority_name: "bar",
|
|
97
|
+
service: 'test',
|
|
98
|
+
action: 'search' }
|
|
99
|
+
history << entry
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @return [Boolean] true if historical test data exists; otherwise false
|
|
103
|
+
def history?
|
|
104
|
+
# TODO: STUBED -- need check for history data
|
|
105
|
+
false
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Provide service methods for getting a list of all authorities and scenarios for an authority.
|
|
2
|
+
module QaServer
|
|
3
|
+
class AuthorityListerService
|
|
4
|
+
|
|
5
|
+
# Return a list of supported authorities
|
|
6
|
+
# @return [Array<String>] list of authorities
|
|
7
|
+
def self.authorities_list
|
|
8
|
+
LINKED_DATA_AUTHORITIES_CONFIG.keys.sort
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Fill in status_log with data about each scenario for an authority
|
|
12
|
+
# @param authority_name [String] the name of the authority
|
|
13
|
+
# @param status_log [ScenarioLogger] the log that will hold the data about the scenarios
|
|
14
|
+
def self.scenarios_list(authority_name:, status_log:)
|
|
15
|
+
scenarios = QaServer::ScenariosLoaderService.load(authority_name: authority_name, status_log: status_log)
|
|
16
|
+
return if scenarios.blank?
|
|
17
|
+
list_terms(scenarios, status_log)
|
|
18
|
+
list_searches(scenarios, status_log)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def self.list_terms(scenarios, status_log)
|
|
24
|
+
scenarios.term_scenarios.each { |scenario| QaServer::TermScenarioValidator.new(scenario: scenario, status_log: status_log).log_without_running }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.list_searches(scenarios, status_log)
|
|
28
|
+
scenarios.search_scenarios.each { |scenario| QaServer::SearchScenarioValidator.new(scenario: scenario, status_log: status_log).log_without_running }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# This class loads an authority.
|
|
2
|
+
module QaServer
|
|
3
|
+
class AuthorityLoaderService
|
|
4
|
+
|
|
5
|
+
# Load a QA authority
|
|
6
|
+
# @param authority_name [String] name of the authority to load (e.g. "agrovoc_direct")
|
|
7
|
+
# @param status_log [ScenarioLogger] logger to hold failure information if the authority cannot be loaded
|
|
8
|
+
# @return [Qa::Authorities::LinkedData::GenericAuthority] the instance of the authority that can receive QA requests OR nil if fails to load
|
|
9
|
+
def self.load(authority_name:, status_log:)
|
|
10
|
+
begin
|
|
11
|
+
authority = load_authority(authority_name, status_log)
|
|
12
|
+
return nil if authority.blank?
|
|
13
|
+
rescue Exception => e
|
|
14
|
+
status_log.add(authority_name: authority_name,
|
|
15
|
+
status: ScenarioValidator::FAIL,
|
|
16
|
+
error_message: "Unable to load authority '#{authority_name}'; cause: #{e.message}")
|
|
17
|
+
return nil
|
|
18
|
+
end
|
|
19
|
+
authority
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
def self.authority_key(authority_name)
|
|
24
|
+
authority_name.upcase.to_sym
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.load_authority(authority_name, status_log)
|
|
28
|
+
authority = Qa::Authorities::LinkedData::GenericAuthority.new(authority_key(authority_name))
|
|
29
|
+
if authority.blank?
|
|
30
|
+
status_log.add(authority_name: authority_name,
|
|
31
|
+
status: ScenarioValidator::FAIL,
|
|
32
|
+
error_message: "Unable to load authority '#{authority_name}'; cause: UNKNOWN") unless authority.present?
|
|
33
|
+
return nil
|
|
34
|
+
end
|
|
35
|
+
authority
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Provide service methods for running a a set of validation scenarios for an authority.
|
|
2
|
+
module QaServer
|
|
3
|
+
class AuthorityValidatorService
|
|
4
|
+
class_attribute :validator_class,
|
|
5
|
+
:term_validator_class,
|
|
6
|
+
:search_validator_class,
|
|
7
|
+
:scenarios_loader_class
|
|
8
|
+
|
|
9
|
+
self.validator_class = ScenarioValidator
|
|
10
|
+
self.term_validator_class = TermScenarioValidator
|
|
11
|
+
self.search_validator_class = SearchScenarioValidator
|
|
12
|
+
self.scenarios_loader_class = ScenariosLoaderService
|
|
13
|
+
|
|
14
|
+
VALIDATE_CONNECTIONS = validator_class::VALIDATE_CONNECTION
|
|
15
|
+
VALIDATE_ACCURACY = validator_class::VALIDATE_ACCURACY
|
|
16
|
+
ALL_VALIDATIONS = validator_class::ALL_VALIDATIONS
|
|
17
|
+
DEFAULT_VALIDATION_TYPE = validator_class::DEFAULT_VALIDATION_TYPE
|
|
18
|
+
|
|
19
|
+
# Run the set of validation scenarios for an authority logging the results
|
|
20
|
+
# @param authority_name [String] the name of the authority
|
|
21
|
+
# @param status_log [ScenarioLogger] the log that will hold the data about the scenarios and test results
|
|
22
|
+
# @param validation_type [Symbol] the type of scenarios to run (e.g. VALIDATE_CONNECTIONS, VALIDATE_ACCURACY, ALL_VALIDATIONS)
|
|
23
|
+
def self.run(authority_name:, status_log:, validation_type: DEFAULT_VALIDATION_TYPE)
|
|
24
|
+
scenarios = scenarios_loader_class.load(authority_name: authority_name, status_log: status_log)
|
|
25
|
+
return if scenarios.blank?
|
|
26
|
+
run_terms(scenarios, status_log, validation_type)
|
|
27
|
+
run_searches(scenarios, status_log, validation_type)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def self.run_terms(scenarios, status_log, validation_type)
|
|
34
|
+
scenarios.term_scenarios.each { |scenario| term_validator_class.new(scenario: scenario, status_log: status_log, validation_type: validation_type).run }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.run_searches(scenarios, status_log, validation_type)
|
|
38
|
+
scenarios.search_scenarios.each { |scenario| search_validator_class.new(scenario: scenario, status_log: status_log, validation_type: validation_type).run }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require 'rails/generators/active_record'
|
|
2
|
+
|
|
3
|
+
module QaServer
|
|
4
|
+
# QaServer::DatabaseMigrator is responsible for copying QaServer's required database
|
|
5
|
+
# migrations into applications. Rails engines typically use the built-in
|
|
6
|
+
# `{ENGINE_NAME}:install:migrations` task to handle this; instead QaServer
|
|
7
|
+
# follows the practice used by Devise to dynamically subclass migrations with
|
|
8
|
+
# the version of `ActiveRecord::Migration` corresponding to the version of
|
|
9
|
+
# Rails used by the application.
|
|
10
|
+
#
|
|
11
|
+
# @note QaServer::DatabaseMigrator uses Rails' generator internals to avoid
|
|
12
|
+
# having to re-implement code that knows how to copy migrations only if
|
|
13
|
+
# needed
|
|
14
|
+
class DatabaseMigrator < Rails::Generators::Base
|
|
15
|
+
# @note included to pick up AR's migration numbering algorithm
|
|
16
|
+
include ActiveRecord::Generators::Migration
|
|
17
|
+
|
|
18
|
+
# @note This method is required by Rails::Generators::Base
|
|
19
|
+
def self.source_root
|
|
20
|
+
migrations_dir
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.migrations_dir
|
|
24
|
+
QaServer::Engine.root.join('lib', 'generators', 'qa_server', 'templates')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.copy
|
|
28
|
+
new.copy
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# def copy
|
|
32
|
+
# # QA Server's migrations changed between 2.0.0 and subsequent versions, so the
|
|
33
|
+
# # migration comparison algorithm decides that those older migrations are a
|
|
34
|
+
# # source of conflict. Default Rails behavior here is to abort and
|
|
35
|
+
# # explicitly instruct the user to try again with either the `--skip` or
|
|
36
|
+
# # `--force` option. QA Server skips these conflicts.
|
|
37
|
+
# migrations.each do |filename|
|
|
38
|
+
# migration_template filename,
|
|
39
|
+
# "db/migrate/#{parse_basename_from(filename)}",
|
|
40
|
+
# migration_version: migration_version,
|
|
41
|
+
# skip: true
|
|
42
|
+
# end
|
|
43
|
+
# end
|
|
44
|
+
def copy
|
|
45
|
+
migrations.each do |filename|
|
|
46
|
+
migration_template filename,
|
|
47
|
+
"db/migrate/#{parse_basename_from(filename)}",
|
|
48
|
+
migration_version: migration_version
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def migrations
|
|
55
|
+
Dir.chdir(self.class.migrations_dir) { Dir.glob('db/migrate/[0-9]*_*.rb.erb') }.sort
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def parse_basename_from(filename)
|
|
59
|
+
# Add engine name to filename to mimic ``ActiveRecord::Migration.copy` behavior
|
|
60
|
+
filename.slice(/(?<dateprefix>\d)+_(?<basename>.+)\.erb/, 'basename').sub('.', '.qa_server.')
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def migration_version
|
|
64
|
+
# Don't use AR migration versioning in Rails 4
|
|
65
|
+
return if Rails.version < '5.0.0'
|
|
66
|
+
# Specify the current major.minor version of Rails for AR migrations
|
|
67
|
+
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# This class loads scenario configuration file for an authority.
|
|
2
|
+
module QaServer
|
|
3
|
+
class ScenariosLoaderService
|
|
4
|
+
|
|
5
|
+
# Load scenarios for testing an authority
|
|
6
|
+
# @param authority_name [String] name of the authority to load (e.g. "agrovoc_direct")
|
|
7
|
+
# @param status_log [ScenarioLogger] logger to hold failure information if the scenarios cannot be loaded
|
|
8
|
+
# @return [Scenarios] the instance of the set of scenarios to test for the authority OR nil if fails to load
|
|
9
|
+
def self.load(authority_name:, status_log:)
|
|
10
|
+
begin
|
|
11
|
+
authority = load_authority(authority_name, status_log)
|
|
12
|
+
return nil if authority.blank?
|
|
13
|
+
return nil unless scenarios_exist?(authority_name, status_log)
|
|
14
|
+
|
|
15
|
+
scenarios_config = load_config(authority_name, status_log)
|
|
16
|
+
return nil if scenarios_config.blank?
|
|
17
|
+
|
|
18
|
+
scenarios = Scenarios.new(authority: authority, authority_name: authority_name, scenarios_config: scenarios_config)
|
|
19
|
+
rescue Exception => e
|
|
20
|
+
status_log.add(authority_name: authority_name,
|
|
21
|
+
status: ScenarioValidator::FAIL,
|
|
22
|
+
error_message: "Unable to load scenarios for authority '#{authority_name}'; cause: #{e.message}")
|
|
23
|
+
return nil
|
|
24
|
+
end
|
|
25
|
+
scenarios
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def self.load_authority(authority_name, status_log)
|
|
31
|
+
AuthorityLoaderService.load(authority_name: authority_name, status_log: status_log)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.load_config(authority_name, status_log)
|
|
35
|
+
scenarios_config = YAML.load_file(scenario_path(authority_name))
|
|
36
|
+
unless scenarios_config.present?
|
|
37
|
+
status_log.add(authority_name: authority_name,
|
|
38
|
+
status: ScenarioValidator::FAIL,
|
|
39
|
+
error_message: "Unable to load scenarios for authority '#{authority_name}'; cause: UNKNOWN")
|
|
40
|
+
return nil
|
|
41
|
+
end
|
|
42
|
+
scenarios_config
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.scenarios_exist?(authority_name, status_log)
|
|
46
|
+
return true if File.exists?(scenario_path(authority_name))
|
|
47
|
+
status_log.add(authority_name: authority_name,
|
|
48
|
+
status: ScenarioValidator::FAIL,
|
|
49
|
+
error_message: "Unable to load scenarios for authority '#{authority_name}'; cause: #{scenario_path} does not exist.")
|
|
50
|
+
false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.scenario_path(authority_name)
|
|
54
|
+
File.join(::Rails.root, 'config', 'authorities', 'linked_data', 'scenarios', "#{authority_name.downcase}_validation.yml")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|