Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate Rankings Table to React #10569

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion app/controllers/results_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,27 @@ def rankings

else
flash[:danger] = t(".unknown_show")
redirect_to rankings_path
return redirect_to rankings_path
end

@ranking_timestamp = ComputeAuxiliaryData.successful_start_date || Date.current

respond_to do |format|
format.html
format.json do
cached_data = Rails.cache.fetch [*@cache_params, @ranking_timestamp] do
rows = DbHelper.execute_cached_query(@cache_params, @ranking_timestamp, @query)
comp_ids = rows.map { |r| r["competitionId"] }.uniq
if @is_by_region
rows = compute_rankings_by_region(rows, @continent, @country)
end
competitions_by_id = Competition.where(id: comp_ids).to_h { |c| [c.id, c] }
{
rows: rows.as_json, competitionsById: competitions_by_id.as_json({ methods: %w[country cellName id], includes: [] })
}
end
render json: cached_data
end
end
end

Expand Down Expand Up @@ -356,4 +376,43 @@ def records
params[:show] = nil
end
end

private def compute_rankings_by_region(rows, continent, country)
if rows.empty?
return [[], 0, 0]
end
best_value_of_world = rows.first["value"]
best_values_of_continents = {}
best_values_of_countries = {}
world_rows = []
continents_rows = []
countries_rows = []
rows.each do |row|
result = LightResult.new(row)
value = row["value"]

world_rows << row if value == best_value_of_world

if best_values_of_continents[result.country.continent.id].nil? || value == best_values_of_continents[result.country.continent.id]
best_values_of_continents[result.country.continent.id] = value

if (country.present? && country.continent.id == result.country.continent.id) || (continent.present? && continent.id == result.country.continent.id) || params[:region] == "world"
continents_rows << row
end
end

if best_values_of_countries[result.country.id].nil? || value == best_values_of_countries[result.country.id]
best_values_of_countries[result.country.id] = value

if (country.present? && country.id == result.country.id) || params[:region] == "world"
countries_rows << row
end
end
end

first_continent_index = world_rows.length
first_country_index = first_continent_index + continents_rows.length
rows_to_display = world_rows + continents_rows + countries_rows
[rows_to_display, first_continent_index, first_country_index]
end
end
39 changes: 0 additions & 39 deletions app/helpers/results_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,45 +29,6 @@ def historical_pb_markers(results)
end
end

def compute_rankings_by_region(rows, continent, country)
if rows.empty?
return [[], 0, 0]
end
best_value_of_world = rows.first["value"]
best_values_of_continents = {}
best_values_of_countries = {}
world_rows = []
continents_rows = []
countries_rows = []
rows.each do |row|
result = LightResult.new(row)
value = row["value"]

world_rows << row if value == best_value_of_world

if best_values_of_continents[result.country.continent.id].nil? || value == best_values_of_continents[result.country.continent.id]
best_values_of_continents[result.country.continent.id] = value

if (country.present? && country.continent.id == result.country.continent.id) || (continent.present? && continent.id == result.country.continent.id) || params[:region] == "world"
continents_rows << row
end
end

if best_values_of_countries[result.country.id].nil? || value == best_values_of_countries[result.country.id]
best_values_of_countries[result.country.id] = value

if (country.present? && country.id == result.country.id) || params[:region] == "world"
countries_rows << row
end
end
end

first_continent_index = world_rows.length
first_country_index = first_continent_index + continents_rows.length
rows_to_display = world_rows + continents_rows + countries_rows
[rows_to_display, first_continent_index, first_country_index]
end

def compute_slim_or_separate_records(rows)
single_rows = []
average_rows = []
Expand Down
31 changes: 9 additions & 22 deletions app/views/results/rankings.html.erb
Original file line number Diff line number Diff line change
@@ -1,29 +1,16 @@
<% provide(:title, t(".title")) %>

<% ranking_timestamp = ComputeAuxiliaryData.successful_start_date || Date.current %>
<% @rows = DbHelper.execute_cached_query(@cache_params, ranking_timestamp, @query) %>

<div class="container">
<h1><%= yield(:title) %></h1>
<p><%= t("results.last_updated_html", timestamp: wca_local_time(ranking_timestamp)) %></p>
<p><%= t("results.last_updated_html", timestamp: wca_local_time(@ranking_timestamp)) %></p>
<p><i><%= t('results.filters_fixes_underway') %></i></p>

<div id="results-selector" class="results-select form-inline">
<%= render 'results_selector', show_rankings_options: true %>
</div>
<% cache [*@cache_params, ranking_timestamp, I18n.locale] do %>
<%
comp_ids = @rows.map { |r| r["competitionId"] }.uniq
@competitions_by_id = Hash[Competition.where(id: comp_ids).map { |c| [c.id, c] }]
%>
<div id="search-results" class="results">
<div id="results-list">
<% if @is_by_region %>
<%= render 'rankings_by_region_table' %>
<% else %>
<%= render 'rankings_table' %>
<% end %>
</div>
</div>
<% end %>
<%= react_component("Results/Rankings", {
event: params[:event_id],
region: params[:region],
rankingType: params[:type],
year: params[:year],
gender: params[:gender],
show: params[:show],
}) %>
</div>
125 changes: 125 additions & 0 deletions app/webpacker/components/Results/Rankings/RankingsTable.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import React, { useMemo } from 'react';
import { Table } from 'semantic-ui-react';
import _ from 'lodash';
import I18n from '../../../lib/i18n';
import { formatAttemptResult } from '../../../lib/wca-live/attempts';
import CountryFlag from '../../wca/CountryFlag';
import { continents, countries } from '../../../lib/wca-data.js.erb';
import { personUrl } from '../../../lib/requests/routes.js.erb';

function CountryCell({ country }) {
return (
<Table.Cell textAlign="left">
{country.iso2 && <CountryFlag iso2={country.iso2} />}
{' '}
{country.name}
</Table.Cell>
);
}

function ResultRow({
result, competition, rank, isAverage, show, country,
}) {
const attempts = [result.value1, result.value2, result.value3, result.value4, result.value5];
const bestResult = _.max(attempts);
const worstResult = _.min(attempts);
const bestResultIndex = attempts.findIndex((a) => a === bestResult);
const worstResultIndex = attempts.findIndex((a) => a === worstResult);
return (
<Table.Row>
{show === 'by region' ? <CountryCell country={country} />
: <Table.Cell textAlign="center">{rank}</Table.Cell> }
<Table.Cell>
<a href={personUrl(result.personId)}>{result.personName}</a>
</Table.Cell>
<Table.Cell>
{formatAttemptResult(result.value, result.eventId)}
</Table.Cell>
{show !== 'by region'
&& <CountryCell country={country} />}
<Table.Cell>
<CountryFlag iso2={competition.country.iso2} />
{' '}
<a href={`/competition/${competition.id}`}>{competition.cellName}</a>
</Table.Cell>
{isAverage && (attempts.map((a, i) => (
<Table.Cell>
{ attempts.length === 5
&& (i === bestResultIndex || i === worstResultIndex)
? `(${formatAttemptResult(a, result.eventId)})` : formatAttemptResult(a, result.eventId)}
</Table.Cell>
))
)}
</Table.Row>
);
}

export default function RankingsTable({
rows, competitionsById, isAverage, show,
}) {
const r = useMemo(() => {
let rowsToMap = rows;
let firstContinentIndex = 0;
let firstCountryIndex = 0;
if (show === 'by region') {
[rowsToMap, firstContinentIndex, firstCountryIndex] = rows;
}

let previousValue = 0;
let previousRank = 0;
return rowsToMap.map((result, index) => {
const competition = competitionsById[result.competitionId];
const { value } = result;
const rank = value === previousValue ? previousRank : index + 1;
const tiedPrevious = rank === previousRank;
let country = countries.real.find((c) => c.id === result.countryId);

if (index < firstContinentIndex) {
country = { name: I18n.t('results.table_elements.world') };
} else if (index >= firstContinentIndex && index < firstCountryIndex) {
country = continents.real.find((c) => c.id === country.continentId);
}

previousValue = value;
previousRank = rank;

return (
<ResultRow
country={country}
key={result.id}
result={result}
competition={competition}
rank={rank}
tiedPrevious={tiedPrevious}
isAverage={isAverage}
show={show}
/>
);
});
}, [competitionsById, isAverage, rows, show]);

return (
<Table basic="very" compact="very" singleLine striped>
<Table.Header>
{show !== 'by region' ? <Table.HeaderCell textAlign="center">#</Table.HeaderCell>
: <Table.HeaderCell>{I18n.t('results.table_elements.region')}</Table.HeaderCell>}
<Table.HeaderCell>{I18n.t('results.table_elements.name')}</Table.HeaderCell>
<Table.HeaderCell>{I18n.t('results.table_elements.result')}</Table.HeaderCell>
{show !== 'by region' && <Table.HeaderCell textAlign="left">{I18n.t('results.table_elements.representing')}</Table.HeaderCell>}
<Table.HeaderCell>{I18n.t('results.table_elements.competition')}</Table.HeaderCell>
{isAverage && (
<>
<Table.HeaderCell>{I18n.t('results.table_elements.solves')}</Table.HeaderCell>
<Table.HeaderCell />
<Table.HeaderCell />
<Table.HeaderCell />
<Table.HeaderCell />
</>
)}
</Table.Header>
<Table.Body>
{r}
</Table.Body>
</Table>
);
}
74 changes: 74 additions & 0 deletions app/webpacker/components/Results/Rankings/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { useEffect, useMemo, useState } from 'react';
import { Container } from 'semantic-ui-react';
import { useQuery } from '@tanstack/react-query';
import RankingsTable from './RankingsTable';
import WCAQueryClientProvider from '../../../lib/providers/WCAQueryClientProvider';
import { getRankings } from '../api/rankings';
import Loading from '../../Requests/Loading';
import { rankingsUrl } from '../../../lib/requests/routes.js.erb';
import ResultsFilter from '../resultsFilter';

export default function Wrapper({
event, region, year, rankingType, gender, show,
}) {
return (
<WCAQueryClientProvider>
<Rankings
initialEvent={event}
initialRegion={region}
initialYear={year}
initialRankingType={rankingType}
initialGender={gender}
initialShow={show}
/>
</WCAQueryClientProvider>
);
}

export function Rankings({
initialEvent, initialRegion, initialRankingType, initialGender, initialShow,
}) {
const [event, setEvent] = useState(initialEvent);
const [region, setRegion] = useState(initialRegion ?? 'all');
const [rankingType, setRankingType] = useState(initialRankingType);
const [gender, setGender] = useState(initialGender);
const [show, setShow] = useState(initialShow ?? 'Persons');

const filterState = useMemo(() => ({
event,
setEvent,
region,
setRegion,
rankingType,
setRankingType,
gender,
setGender,
show,
setShow,
}), [event, gender, rankingType, region, show]);

const { data, isFetching } = useQuery({
queryKey: ['rankings', event, region, rankingType, gender, show],
queryFn: () => getRankings(event, rankingType, region, gender, show),
});

useEffect(() => {
window.history.replaceState(null, '', rankingsUrl(event, rankingType, region, gender, show));
}, [event, region, rankingType, gender, show]);

if (isFetching) {
return <Loading />;
}

return (
<Container>
<ResultsFilter filterState={filterState} />
<RankingsTable
competitionsById={data.competitionsById}
isAverage={rankingType === 'average'}
rows={data.rows}
show={show}
/>
</Container>
);
}
8 changes: 8 additions & 0 deletions app/webpacker/components/Results/api/rankings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { fetchJsonOrError } from '../../../lib/requests/fetchWithAuthenticityToken';
import { rankingsUrl } from '../../../lib/requests/routes.js.erb';

// eslint-disable-next-line import/prefer-default-export
export async function getRankings(eventId, rankingType, region, gender, show) {
const { data } = await fetchJsonOrError(rankingsUrl(eventId, rankingType, region, gender, show), { headers: { Accept: 'application/json' } });
return data;
}
Loading
Loading