diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index db8b9855e5c..280bd99b532 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -33,75 +33,19 @@ def new_results @results_validator.validate(@competition.id) end - def check_results - with_results_validator - end - def check_competition_results with_results_validator do @competition = competition_from_params - @result_validation.competition_ids = @competition.id end end - def compute_validation_competitions - validation_form = ResultValidationForm.new( - competition_start_date: params[:start_date], - competition_end_date: params[:end_date], - competition_selection: ResultValidationForm::COMP_VALIDATION_ALL, - ) - - render json: { - competitions: validation_form.competitions, - } - end - def with_results_validator - @result_validation = ResultValidationForm.new( - competition_ids: params[:competition_ids] || "", - competition_start_date: params[:competition_start_date] || "", - competition_end_date: params[:competition_end_date] || "", - validator_classes: params[:validator_classes] || ResultValidationForm::ALL_VALIDATOR_NAMES.join(","), - competition_selection: params[:competition_selection] || ResultValidationForm::COMP_VALIDATION_MANUAL, - apply_fixes: params[:apply_fixes] || false, - ) - # For this view, we just build an empty validator: the WRT will decide what # to actually run (by default all validators will be selected). @results_validator = ResultsValidators::CompetitionsResultsValidator.new(check_real_results: true) yield if block_given? end - def do_check_results - running_validators do - render :check_results - end - end - - def do_check_competition_results - running_validators do - uniq_id = @result_validation.competitions.first - @competition = Competition.find(uniq_id) - - render :check_competition_results - end - end - - def running_validators - action_params = params.require(:result_validation_form) - .permit(:competition_ids, :validator_classes, :apply_fixes, :competition_selection, :competition_start_date, :competition_end_date) - - @result_validation = ResultValidationForm.new(action_params) - - if @result_validation.valid? - @results_validator = @result_validation.build_and_run - else - @results_validator = ResultsValidators::CompetitionsResultsValidator.new(check_real_results: true) - end - - yield if block_given? - end - def clear_results_submission # Just clear the "results_submitted_at" field to let the Delegate submit # the results again. We don't actually want to clear InboxResult and InboxPerson. diff --git a/app/controllers/api/v0/competitions_controller.rb b/app/controllers/api/v0/competitions_controller.rb index 9bbba881f6e..828843bd617 100644 --- a/app/controllers/api/v0/competitions_controller.rb +++ b/app/controllers/api/v0/competitions_controller.rb @@ -37,6 +37,14 @@ def competition_index include: serial_includes end + def competition_count + start_date = params.require(:startDate) + end_date = params.require(:endDate) + + count = Competition.where("start_date >= ? AND end_date <= ?", start_date, end_date).count + render json: { count: count } + end + def show competition = competition_from_params diff --git a/app/controllers/panel_controller.rb b/app/controllers/panel_controller.rb index 92ed2271cbf..b54761083a6 100644 --- a/app/controllers/panel_controller.rb +++ b/app/controllers/panel_controller.rb @@ -50,6 +50,48 @@ def generate_db_token } end + private def validators_for_competition_ids(competition_ids) + validators = params.require(:selectedValidators).split(',').map(&:constantize) + apply_fix_when_possible = params.require(:applyFixWhenPossible) + + results_validator = ResultsValidators::CompetitionsResultsValidator.new( + validators, + check_real_results: true, + apply_fixes: apply_fix_when_possible, + ) + results_validator.validate(competition_ids) + render json: { + has_results: results_validator.has_results?, + validators: results_validator.validators, + infos: results_validator.infos, + errors: results_validator.errors, + warnings: results_validator.warnings, + } + end + + private def competition_ids_in_range(range) + start_date = range[:startDate] + end_date = range[:endDate] + Competition.over + .not_cancelled + .where(start_date: start_date..) + .where(start_date: ..end_date) + .order(:start_date) + .ids + end + + def validators_for_competition_list + competition_ids = params.require(:competitionIds).split(',') + validators_for_competition_ids(competition_ids) + end + + def validators_for_competitions_in_range + range = JSON.parse(params.require(:competitionRange)).transform_keys(&:to_sym) + competition_ids = competition_ids_in_range(range) + + validators_for_competition_ids(competition_ids) + end + def panel_page panel_page_id = params.require(:id) panel_with_panel_page = current_user.panels_with_access&.find { |panel| User.panel_list[panel][:pages].include?(panel_page_id) } diff --git a/app/models/result_validation_form.rb b/app/models/result_validation_form.rb deleted file mode 100644 index 60ae37542d8..00000000000 --- a/app/models/result_validation_form.rb +++ /dev/null @@ -1,128 +0,0 @@ -# frozen_string_literal: true - -class ResultValidationForm - ALL_VALIDATOR_NAMES = ResultsValidators::Utils::ALL_VALIDATORS.map(&:class_name) - VALIDATOR_WITH_FIX_NAMES = ResultsValidators::Utils::VALIDATORS_WITH_FIX.map(&:class_name) - - COMP_VALIDATION_ALL = :all - COMP_VALIDATION_MANUAL = :manual - - COMP_VALIDATION_MODES = [["Pick competition(s) manually", COMP_VALIDATION_MANUAL], ["Execute for ALL competitions", COMP_VALIDATION_ALL]].freeze - - ALL_COMPETITIONS_MAX = 500 - - include ActiveModel::Model - - attr_accessor :validator_classes, :competition_ids - attr_writer :apply_fixes, :competition_selection, :competition_start_date, :competition_end_date - - validates :competition_ids, presence: true, if: -> { self.competition_selection == COMP_VALIDATION_MANUAL } - - validates :competition_start_date, presence: true, if: -> { self.competition_selection == COMP_VALIDATION_ALL } - validates :competition_end_date, presence: true, if: -> { self.competition_selection == COMP_VALIDATION_ALL } - - validate :competition_count_within_bounds, :competition_range_overlapping - - def self.all_competitions_scope - Competition.over.not_cancelled - end - - def competition_count_within_bounds - if competition_selection == COMP_VALIDATION_ALL && competition_range_count > ALL_COMPETITIONS_MAX - range_end_date = ResultValidationForm.compute_range_end(self.competition_start_date) - errors.add(:competition_range_count, "You are only allowed to select up to #{ALL_COMPETITIONS_MAX} at once! Suggested end date is #{range_end_date}") - end - end - - def competition_range_overlapping - if competition_selection == COMP_VALIDATION_ALL - if competition_start_date > competition_end_date - errors.add(:competition_start_date, "The start date must be before the end date!") - end - - if competition_start_date > Date.today - errors.add(:competition_start_date, "The start date must not be in the future!") - end - end - end - - def competitions - if self.competition_selection == COMP_VALIDATION_ALL - ResultValidationForm.competitions_between(self.competition_start_date, self.competition_end_date) - .order(:start_date) - .ids - else - @competition_ids.split(",").uniq.compact - end - end - - def competition_selection - @competition_selection&.to_sym || COMP_VALIDATION_MANUAL - end - - def competition_start_date - Date.parse(@competition_start_date) if @competition_start_date.present? - end - - def competition_end_date - Date.parse(@competition_end_date) if @competition_end_date.present? - end - - def competition_range_count - ResultValidationForm.competitions_between(competition_start_date, competition_end_date).count - end - - def validators - @validator_classes.split(",").map { |v| ResultsValidators::Utils.validator_class_from_name(v) }.compact - end - - def apply_fixes - ActiveModel::Type::Boolean.new.cast(@apply_fixes) - end - - def build_validator - ResultsValidators::CompetitionsResultsValidator.new( - validators, - check_real_results: true, - apply_fixes: apply_fixes, - ) - end - - def build_and_run - build_validator.validate competitions - end - - def self.compute_range_end(start_date, count = ALL_COMPETITIONS_MAX) - range_end = self.all_competitions_scope - .where(start_date: start_date..) - .order(:start_date) - # Not using `offset` because of the risk to skip into nothingness for newer competitions - .limit(count) - .pluck(:start_date) - .last - - return start_date if range_end.nil? - - self.cap_range(start_date, range_end, count) - end - - def self.cap_range(start_date, range_end, max_count) - if range_end < start_date - return start_date - end - - actual_count = self.competitions_between(start_date, range_end).count - - if actual_count > max_count - return self.cap_range(start_date, range_end - 1.day, max_count) - end - - range_end - end - - def self.competitions_between(range_start, range_end) - self.all_competitions_scope - .where(start_date: range_start..) - .where(start_date: ..range_end) - end -end diff --git a/app/views/admin/_validator_form.html.erb b/app/views/admin/_validator_form.html.erb deleted file mode 100644 index 97e247f925b..00000000000 --- a/app/views/admin/_validator_form.html.erb +++ /dev/null @@ -1,152 +0,0 @@ -<% backend_url ||= nil %> -<% lock_selection = defined? @competition %> -
-
Configure validations to run
-
- <%= simple_form_for @result_validation, url: backend_url do |f| %> - <%= f.input :validator_classes, as: :string, label: false, hint: false, input_html: { id: "validators" } %> - <%= f.input :apply_fixes, as: :boolean, label: "Apply fix when possible", hint: "List of validators with automated fix: #{ResultValidationForm::VALIDATOR_WITH_FIX_NAMES.join(",")}." %> - <% unless lock_selection %> - <%= f.input :competition_selection, collection: ResultValidationForm::COMP_VALIDATION_MODES, as: :radio_buttons, label: "Competition selection", hint: "WARNING: Running multiple validations on all competitions can take a long time." %> - <% end %> -
- <%= f.input :competition_ids, as: :competition_id, hint: false, label: "Competition ID(s)", input_html: { class: lock_selection ? "wca-autocomplete-input_lock" : "" } %> -
- <% unless lock_selection %> -
- <%= f.input :competition_start_date, as: :date_picker, hint: false, label: "Start date for checking" %> - <%= f.input :competition_end_date, as: :date_picker, hint: "Please pick dates above to update this label with information on the selected competitions.", label: "End date for checking" %> - - -
- <% end %> -
- <%= f.button :submit, value: "Run validators", class: "btn-primary" %> - <%= ui_icon('info circle', 'data-toggle': "collapse", 'data-target': "#collapse-validator-desc") %> -
-
- <%= wca_table do %> - - - Validator - Description - - - - - <% ResultsValidators::Utils::ALL_VALIDATORS.each do |validator| %> - - <%= validator.class_name %> - <%= validator.description %> - - - <% end %> - - <% end %> -
- <% end %> -
-
- - - diff --git a/app/views/admin/check_competition_results.html.erb b/app/views/admin/check_competition_results.html.erb index 3bfc67f2d06..0b0761d79f1 100644 --- a/app/views/admin/check_competition_results.html.erb +++ b/app/views/admin/check_competition_results.html.erb @@ -5,8 +5,9 @@ Check existing results for the competition.

- <%= render "validator_form", backend_url: competition_admin_run_validators_path %> - <%= render "results_submission/check_results_panel", results_validator: @results_validator %> + <%= react_component("Panel/pages/RunValidatorsPage/RunValidatorsForm", { + competitionIds: [@competition.id], + }) %> <%= render "results_submission/results_preview_panel", results_validator: @results_validator %> <% end %> diff --git a/app/views/admin/check_results.html.erb b/app/views/admin/check_results.html.erb deleted file mode 100644 index 577f14ce555..00000000000 --- a/app/views/admin/check_results.html.erb +++ /dev/null @@ -1,10 +0,0 @@ -<% provide(:title, "Check results") %> -
-

Run validators

-

- Check existing results. -

- - <%= render "validator_form", backend_url: admin_do_check_results_path %> - <%= render "results_submission/check_results_panel", results_validator: @results_validator, display_competition: @result_validation.competitions.length > 1 %> -
diff --git a/app/webpacker/components/Panel/PanelPages.jsx b/app/webpacker/components/Panel/PanelPages.jsx index 369264676ff..ee29d9992b8 100644 --- a/app/webpacker/components/Panel/PanelPages.jsx +++ b/app/webpacker/components/Panel/PanelPages.jsx @@ -5,7 +5,6 @@ import { subordinateUpcomingCompetitionsUrl, generateDbTokenUrl, serverStatusPageUrl, - runValidatorsUrl, createNewComersUrl, checkRecordsUrl, computeAuxiliaryDataUrl, @@ -39,6 +38,7 @@ import DownloadVoters from './pages/DownloadVoters'; import ApprovePictures from './pages/ApprovePictures'; import EditPersonRequestsPage from './pages/EditPersonRequestsPage'; import AnonymizationScriptPage from './pages/AnonymizationScriptPage'; +import RunValidatorsForm from './pages/RunValidatorsPage/RunValidatorsForm'; const DELEGATE_HANDBOOK_LINK = 'https://documents.worldcubeassociation.org/edudoc/delegate-handbook/delegate-handbook.pdf'; @@ -165,7 +165,7 @@ export default { }, [PANEL_PAGES.runValidators]: { name: 'Run Validators', - link: runValidatorsUrl, + component: RunValidatorsForm, }, [PANEL_PAGES.createNewComers]: { name: 'Create New Comers', diff --git a/app/webpacker/components/Panel/pages/RunValidatorsPage/CompetitionRangeSelector.jsx b/app/webpacker/components/Panel/pages/RunValidatorsPage/CompetitionRangeSelector.jsx new file mode 100644 index 00000000000..796d1a0468e --- /dev/null +++ b/app/webpacker/components/Panel/pages/RunValidatorsPage/CompetitionRangeSelector.jsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { Form } from 'semantic-ui-react'; +import { QueryClient, useQuery } from '@tanstack/react-query'; +import UtcDatePicker from '../../../wca/UtcDatePicker'; +import getCompetitionCount from './api/getCompetitionCount'; +import Loading from '../../../Requests/Loading'; +import Errored from '../../../Requests/Errored'; + +const RUN_VALIDATORS_QUERY_CLIENT = new QueryClient(); + +export default function CompetitionRangeSelector({ range, setRange }) { + const bothDatesAreSelected = Boolean(range?.startDate && range?.endDate); + + const { + data: competitionCount, isLoading, isError, + } = useQuery({ + queryKey: ['competitionCountInRange', range?.startDate, range?.endDate], + queryFn: () => getCompetitionCount(range?.startDate, range?.endDate), + enabled: bothDatesAreSelected, + }, RUN_VALIDATORS_QUERY_CLIENT); + + return ( + <> + { + setRange({ + ...range, + startDate: date, + }); + }} + /> + { + setRange({ + ...range, + endDate: date, + }); + }} + /> + {bothDatesAreSelected && ( + + )} + + ); +} + +function CompetitionCountViewer({ isLoading, isError, competitionCount }) { + if (isLoading) return ; + if (isError) return ; + return `The checks will run for ${competitionCount} competitions`; +} diff --git a/app/webpacker/components/Panel/pages/RunValidatorsPage/RunValidatorsForm.jsx b/app/webpacker/components/Panel/pages/RunValidatorsPage/RunValidatorsForm.jsx new file mode 100644 index 00000000000..02173c6faa3 --- /dev/null +++ b/app/webpacker/components/Panel/pages/RunValidatorsPage/RunValidatorsForm.jsx @@ -0,0 +1,165 @@ +import React, { useState } from 'react'; +import { + Form, FormField, FormGroup, Header, HeaderSubheader, Radio, +} from 'semantic-ui-react'; +import { useMutation } from '@tanstack/react-query'; +import useInputState from '../../../../lib/hooks/useInputState'; +import { ALL_VALIDATORS, VALIDATORS_WITH_FIX } from '../../../../lib/wca-data.js.erb'; +import { IdWcaSearch } from '../../../SearchWidget/WcaSearch'; +import SEARCH_MODELS from '../../../SearchWidget/SearchModel'; +import CompetitionRangeSelector from './CompetitionRangeSelector'; +import useCheckboxState from '../../../../lib/hooks/useCheckboxState'; +import runValidatorsForCompetitionList from './api/runValidatorsForCompetitionList'; +import runValidatorsForCompetitionsInRange from './api/runValidatorsForCompetitionsInRange'; +import ValidationOutput from './ValidationOutput'; +import WCAQueryClientProvider from '../../../../lib/providers/WCAQueryClientProvider'; + +const validatorNameReadable = (validatorName) => validatorName.split('::')[1]; + +const VALIDATOR_OPTIONS = ALL_VALIDATORS.map((validator) => ({ + key: validator, + text: validatorNameReadable(validator), + value: validator, +})); + +const COMPETITION_SELECTION_OPTIONS_TEXT = { + manual: 'Pick competition(s) manually', + range: 'Competition between dates', +}; + +const COMPETITION_SELECTION_OPTIONS = Object.keys(COMPETITION_SELECTION_OPTIONS_TEXT); + +export default function Wrapper() { + return ( + + + + ); +} + +function RunValidatorsForm({ competitionIds }) { + const [ + selectedCompetitionSelectionOption, + setSelectedCompetitionSelectionOption, + ] = useInputState('manual'); + + const [selectedCompetitionIds, setSelectedCompetitionIds] = useInputState(competitionIds || []); + const [selectedCompetitionRange, setSelectedCompetitionRange] = useState(); + const [validationOutput, setValidationOutput] = useState(); + + const [selectedValidators, setSelectedValidators] = useInputState(ALL_VALIDATORS); + const [applyFixWhenPossible, setApplyFixWhenPossible] = useCheckboxState(false); + + const runValidatorsForCompetitions = () => { + if (selectedCompetitionSelectionOption === 'manual') { + return runValidatorsForCompetitionList( + selectedCompetitionIds, + selectedValidators, + applyFixWhenPossible, + ); + } + return runValidatorsForCompetitionsInRange( + selectedCompetitionRange, + selectedValidators, + applyFixWhenPossible, + ); + }; + + const { + mutate: runValidators, + isPending, + isError, + } = useMutation({ + mutationFn: runValidatorsForCompetitions, + onSuccess: (data) => { + setValidationOutput(data); + }, + }); + + // enableCompetitionEditor says whether competition list editor should be enabled or not. If the + // list of competitions is passed as parameter, then the editor need not be shown. + const enableCompetitionEditor = !competitionIds; + + // Competition name needs to be shown on output only when the script is not ran just for a single + // competition. + const showCompetitionNameOnOutput = !( + selectedCompetitionSelectionOption === 'manual' + && selectedCompetitionIds.length === 1 + ); + + return ( + <> +
+ {enableCompetitionEditor && ( + <> +
Competition Selector
+ + {COMPETITION_SELECTION_OPTIONS.map((key) => ( + + + + ))} + + {( + selectedCompetitionSelectionOption === 'manual' + ) && ( + + )} + {( + selectedCompetitionSelectionOption === 'range' + ) && ( + + )} + + )} +
Run Validators
+ + +
+ + {`List of validators with automated fix: ${ + VALIDATORS_WITH_FIX.map(validatorNameReadable).join(', ') + }`} + +
+ Run Validators + + + + ); +} diff --git a/app/webpacker/components/Panel/pages/RunValidatorsPage/ValidationListView.jsx b/app/webpacker/components/Panel/pages/RunValidatorsPage/ValidationListView.jsx new file mode 100644 index 00000000000..d71cbd66b4e --- /dev/null +++ b/app/webpacker/components/Panel/pages/RunValidatorsPage/ValidationListView.jsx @@ -0,0 +1,56 @@ +import React, { Fragment, useMemo } from 'react'; +import _ from 'lodash'; +import { Header, List, ListItem } from 'semantic-ui-react'; +import I18n from '../../../../lib/i18n'; +import { competitionUrl } from '../../../../lib/requests/routes.js.erb'; + +const headingPrefixForType = (type) => { + switch (type) { + case 'info': return 'Information for'; + case 'warning': return 'Warnings detected in'; + case 'error': return 'Errors detected in'; + default: return 'Unknown detected in'; + } +}; + +export default function ValidationListView({ validations, showCompetitionNameOnOutput, type }) { + const listByGroup = useMemo(() => _.groupBy(validations, 'kind'), [validations]); + + return ( + <> + {Object.entries(listByGroup).map(([group, list]) => ( + +
{`${headingPrefixForType(type)} ${group}`}
+ + {list.map((validationData) => ( + + + + ))} + +
+ ))} + + ); +} + +function ValidationText({ validationData, group, showCompetitionNameOnOutput }) { + return ( + <> + {showCompetitionNameOnOutput && ( + <> + [ + + {validationData.competition_id} + + {'] '} + + )} + <>{I18n.t(`validators.${group}.${validationData.id}`, validationData.args)} + + ); +} diff --git a/app/webpacker/components/Panel/pages/RunValidatorsPage/ValidationOutput.jsx b/app/webpacker/components/Panel/pages/RunValidatorsPage/ValidationOutput.jsx new file mode 100644 index 00000000000..f6e30c49c49 --- /dev/null +++ b/app/webpacker/components/Panel/pages/RunValidatorsPage/ValidationOutput.jsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { Header, Message } from 'semantic-ui-react'; +import Loading from '../../../Requests/Loading'; +import Errored from '../../../Requests/Errored'; +import ValidationListView from './ValidationListView'; + +export default function ValidationOutput({ + validationOutput, isPending, isError, showCompetitionNameOnOutput, +}) { + if (isPending) return ; + if (isError) return ; + + if (!validationOutput) { + return ( + Please run the validators to see the output. + ); + } + + return ( + <> +
Validation Output
+ {validationOutput.infos.length > 0 && ( + <> +
Infos
+ + + )} +
Errors
+ +
Warnings
+ + + ); +} + +function ValidationErrorOutput({ validationOutput, showCompetitionNameOnOutput }) { + const hasResults = validationOutput.has_results; + const hasErrors = validationOutput.errors.length > 0; + + if (!hasErrors) { + if (hasResults) { + return

No error detected in the results.

; + } + return

No results for the competition yet.

; + } + + return ( + <> +

Please fix the errors below:

+ + + ); +} + +function ValidationWarningOutput({ validationOutput, showCompetitionNameOnOutput }) { + const hasResults = validationOutput.has_results; + const hasWarning = validationOutput.warnings.length > 0; + + if (!hasWarning) { + if (hasResults) { + return

No warning detected in the results.

; + } + return

No results for the competition yet.

; + } + + return ( + <> +

Please pay attention to the warnings below:

+ + + ); +} diff --git a/app/webpacker/components/Panel/pages/RunValidatorsPage/api/getCompetitionCount.js b/app/webpacker/components/Panel/pages/RunValidatorsPage/api/getCompetitionCount.js new file mode 100644 index 00000000000..356cd2f56a4 --- /dev/null +++ b/app/webpacker/components/Panel/pages/RunValidatorsPage/api/getCompetitionCount.js @@ -0,0 +1,7 @@ +import { fetchJsonOrError } from '../../../../../lib/requests/fetchWithAuthenticityToken'; +import { viewUrls } from '../../../../../lib/requests/routes.js.erb'; + +export default async function getCompetitionCount(startDate, endDate) { + const { data } = await fetchJsonOrError(viewUrls.competitions.countInRange(startDate, endDate)); + return data?.count; +} diff --git a/app/webpacker/components/Panel/pages/RunValidatorsPage/api/runValidatorsForCompetitionList.js b/app/webpacker/components/Panel/pages/RunValidatorsPage/api/runValidatorsForCompetitionList.js new file mode 100644 index 00000000000..d840fc16075 --- /dev/null +++ b/app/webpacker/components/Panel/pages/RunValidatorsPage/api/runValidatorsForCompetitionList.js @@ -0,0 +1,15 @@ +import { fetchJsonOrError } from '../../../../../lib/requests/fetchWithAuthenticityToken'; +import { actionUrls } from '../../../../../lib/requests/routes.js.erb'; + +export default async function runValidatorsForCompetitionList( + competitionIds, + selectedValidators, + applyFixWhenPossible, +) { + const { data } = await fetchJsonOrError(actionUrls.validators.forCompetitionList( + competitionIds, + selectedValidators, + applyFixWhenPossible, + )); + return data; +} diff --git a/app/webpacker/components/Panel/pages/RunValidatorsPage/api/runValidatorsForCompetitionsInRange.js b/app/webpacker/components/Panel/pages/RunValidatorsPage/api/runValidatorsForCompetitionsInRange.js new file mode 100644 index 00000000000..f1801a44307 --- /dev/null +++ b/app/webpacker/components/Panel/pages/RunValidatorsPage/api/runValidatorsForCompetitionsInRange.js @@ -0,0 +1,15 @@ +import { fetchJsonOrError } from '../../../../../lib/requests/fetchWithAuthenticityToken'; +import { actionUrls } from '../../../../../lib/requests/routes.js.erb'; + +export default async function runValidatorsForCompetitionsInRange( + competitionRange, + selectedValidators, + applyFixWhenPossible, +) { + const { data } = await fetchJsonOrError(actionUrls.validators.forCompetitionsInRange( + JSON.stringify(competitionRange), + selectedValidators, + applyFixWhenPossible, + )); + return data; +} diff --git a/app/webpacker/lib/requests/routes.js.erb b/app/webpacker/lib/requests/routes.js.erb index 170118c7a3e..9ef0a6c9a80 100644 --- a/app/webpacker/lib/requests/routes.js.erb +++ b/app/webpacker/lib/requests/routes.js.erb @@ -266,7 +266,10 @@ export const viewUrls = { tickets: { list: (type, status, sort) => `<%= CGI.unescape(Rails.application.routes.url_helpers.tickets_path) %>?${jsonToQueryString({ type, status, sort })}`, show: (ticketId) => `<%= CGI.unescape(Rails.application.routes.url_helpers.ticket_path("${ticketId}")) %>`, - } + }, + competitions: { + countInRange: (startDate, endDate) => `<%= CGI.unescape(Rails.application.routes.url_helpers.api_v0_competition_count_path) %>?${jsonToQueryString({ startDate, endDate })}`, + }, } export const actionUrls = { @@ -278,6 +281,10 @@ export const actionUrls = { users: { anonymize: (userId) => `<%= CGI.unescape(Rails.application.routes.url_helpers.anonymize_user_path("${userId}")) %>`, }, + validators: { + forCompetitionList: (competitionIds, selectedValidators, applyFixWhenPossible) => `<%= CGI.unescape(Rails.application.routes.url_helpers.panel_validators_for_competition_list_path) %>?${jsonToQueryString({ competitionIds, selectedValidators, applyFixWhenPossible })}`, + forCompetitionsInRange: (competitionRange, selectedValidators, applyFixWhenPossible) => `<%= CGI.unescape(Rails.application.routes.url_helpers.panel_validators_for_competitions_in_range_path) %>?${jsonToQueryString({ competitionRange, selectedValidators, applyFixWhenPossible })}`, + }, } export const userPreferencesRoute = `<%= CGI.unescape(Rails.application.routes.url_helpers.profile_edit_path)%>?section=preferences`; @@ -310,8 +317,6 @@ export const paymentTicketUrl = (competitionId, donationIso) => `<%= CGI.unescap export const serverStatusPageUrl = `<%= CGI.unescape(Rails.application.routes.url_helpers.server_status_path) %>`; -export const runValidatorsUrl = `<%= CGI.unescape(Rails.application.routes.url_helpers.admin_check_results_path) %>`; - export const createNewComersUrl = `<%= CGI.unescape(Rails.application.routes.url_helpers.admin_finish_unfinished_persons_path) %>`; export const checkRecordsUrl = `<%= CGI.unescape(Rails.application.routes.url_helpers.admin_check_regional_records_path) %>`; diff --git a/app/webpacker/lib/wca-data.js.erb b/app/webpacker/lib/wca-data.js.erb index 2615f4784b0..afc88072964 100644 --- a/app/webpacker/lib/wca-data.js.erb +++ b/app/webpacker/lib/wca-data.js.erb @@ -225,3 +225,7 @@ export const avatarImageTypes = <%= Rails.application.config.active_storage.web_ export const ticketTypes = <%= Ticket::TICKET_TYPES.to_json %>; export const ticketStatuses = <%= Ticket::TICKET_TYPES.transform_values { |value| value.safe_constantize&.statuses }.to_json %>; export const ticketLogActionTypes = <%= TicketLog.action_types.to_json %>; + +// ----- VALIDATORS ----- +export const ALL_VALIDATORS = <%= ResultsValidators::Utils::ALL_VALIDATORS.map(&:name) %>; +export const VALIDATORS_WITH_FIX = <%= ResultsValidators::Utils::VALIDATORS_WITH_FIX.map(&:name) %>; diff --git a/config/routes.rb b/config/routes.rb index 75d54e32923..3bffebb61e1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -112,7 +112,6 @@ # WRT views and action get '/admin/upload-results' => "admin#new_results", as: :admin_upload_results_edit get '/admin/check-existing-results' => "admin#check_competition_results", as: :admin_check_existing_results - post '/admin/check-existing-results' => "admin#do_check_competition_results", as: :admin_run_validators post '/admin/upload-json' => "admin#create_results", as: :admin_upload_results post '/admin/clear-submission' => "admin#clear_results_submission", as: :clear_results_submission get '/admin/import-results' => 'admin#import_results', as: :admin_import_results @@ -190,10 +189,11 @@ scope 'panel' do get 'staff' => 'panel#staff', as: :panel_staff get 'generate_db_token' => 'panel#generate_db_token', as: :panel_generate_db_token + get 'validators_for_competition_list' => 'panel#validators_for_competition_list', as: :panel_validators_for_competition_list + get 'validators_for_competitions_in_range' => 'panel#validators_for_competitions_in_range', as: :panel_validators_for_competitions_in_range end get 'panel/:panel_id' => 'panel#index', as: :panel_index scope 'panel-page' do - get 'run-validators' => 'admin#check_results', as: :admin_check_results get 'create-new-comers' => 'admin#finish_unfinished_persons', as: :admin_finish_unfinished_persons get 'check-records' => 'admin#check_regional_records', as: :admin_check_regional_records get 'compute-auxiliary-data' => 'admin#compute_auxiliary_data', as: :admin_compute_auxiliary_data @@ -279,8 +279,6 @@ get '/admin/all-voters' => 'admin#all_voters', as: :eligible_voters get '/admin/leader-senior-voters' => 'admin#leader_senior_voters', as: :leader_senior_voters - get '/admin/validation_competitions' => "admin#compute_validation_competitions" - post '/admin/check_results' => 'admin#do_check_results', as: :admin_do_check_results post '/admin/merge_people' => 'admin#do_merge_people', as: :admin_do_merge_people get '/admin/fix_results_selector' => 'admin#fix_results_selector', as: :admin_fix_results_ajax get '/admin/person_data' => 'admin#person_data' @@ -396,6 +394,7 @@ get '/results/:user_id/qualification_data' => 'api#user_qualification_data', as: :user_qualification_data get '/competition_series/:id' => 'api#competition_series' get '/competition_index' => 'competitions#competition_index', as: :competition_index + get '/competition_count' => 'competitions#competition_count', as: :competition_count resources :competitions, only: [:index, :show] do get '/wcif' => 'competitions#show_wcif'