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 (
+ <>
+
+ )}
+ {(
+ selectedCompetitionSelectionOption === 'range'
+ ) && (
+
+ )}
+ >
+ )}
+
+
+
+
+
+ {`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 (
+ <>
+
+ {validationOutput.infos.length > 0 && (
+ <>
+
+
+ >
+ )}
+
+
+
+
+ >
+ );
+}
+
+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'