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 44 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
7db290f
create rankings table
FinnIckler Jan 7, 2025
e0d12ca
create seperate index for filters
FinnIckler Jan 8, 2025
40b29cb
Merge branch 'main' into react/rankings
FinnIckler Jan 8, 2025
8b8c66e
implement backend loading and filters
FinnIckler Jan 8, 2025
4e1cb69
remove segment
FinnIckler Jan 8, 2025
f6f0d47
fix stylings in filters
FinnIckler Jan 10, 2025
4a87829
fix gender and region not being filtered
FinnIckler Jan 10, 2025
86e2ed8
added show filters
FinnIckler Jan 10, 2025
6f37b6d
implement by regions table
FinnIckler Jan 10, 2025
985887c
add i18n to resultsfilter
FinnIckler Jan 10, 2025
c3dfcf0
Merge branch 'thewca:main' into react/rankings
FinnIckler Jan 13, 2025
81f81a4
fix competitions_by_id serialization
FinnIckler Jan 13, 2025
a7b0a7c
don't include organizers or delegates in competition serialization
FinnIckler Jan 13, 2025
b476045
fix indentation
FinnIckler Jan 13, 2025
42e8d19
fix country being imported in routes
FinnIckler Jan 13, 2025
7fb1151
Update app/webpacker/components/Results/resultsFilter.jsx
FinnIckler Jan 13, 2025
1375c9f
mobile changes
FinnIckler Jan 13, 2025
e03bbd4
Update app/webpacker/components/Results/Rankings/RankingsTable.jsx
FinnIckler Jan 13, 2025
9330171
change useMemo to try and fix by region rendering
FinnIckler Jan 13, 2025
eb42088
force Table Body to rerender when changing show modes
FinnIckler Jan 13, 2025
5837c0a
use reduce instead of map
FinnIckler Jan 13, 2025
d1c7b7e
use a reducer instead of state
FinnIckler Jan 13, 2025
3461642
add title prop
FinnIckler Jan 13, 2025
03a1d3d
parse initial State from the URL
FinnIckler Jan 13, 2025
791a743
Update app/webpacker/components/Results/Rankings/RankingsTable.jsx
FinnIckler Jan 14, 2025
fed3a8d
Wrap table header into Row
gregorbg Jan 14, 2025
b8a4176
Improve filter box responsive layout
gregorbg Jan 14, 2025
026dabe
don't use let in useMemo
FinnIckler Jan 14, 2025
4d3436c
use initializer Function
FinnIckler Jan 14, 2025
78279c8
add country.name to key
FinnIckler Jan 15, 2025
b92d81a
Merge branch 'main' into react/rankings
FinnIckler Jan 15, 2025
43c5275
use react table to handle the keys
FinnIckler Jan 17, 2025
7479bf6
don't show mbld average
FinnIckler Jan 17, 2025
7a37464
Merge branch 'main' into react/rankings
FinnIckler Jan 17, 2025
7f711e9
fxi average header and remove cell call
FinnIckler Jan 20, 2025
131831a
introduce countries.byId
FinnIckler Jan 21, 2025
3fdd944
revert gregor change
FinnIckler Jan 21, 2025
513c057
don't use country.name
FinnIckler Jan 21, 2025
e5b36f8
actually use country.name
FinnIckler Jan 21, 2025
d89627a
use array spreading
FinnIckler Jan 21, 2025
95ac88e
introduce getCountryOrContinent helper function
FinnIckler Jan 21, 2025
edbbd1c
Merge branch 'thewca:main' into react/rankings
FinnIckler Jan 21, 2025
d8d12df
fix lint
FinnIckler Jan 21, 2025
362e3ff
move WCA_EVENT_IDS to wca-data.js.erb
FinnIckler Jan 21, 2025
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 ["results-page-api", *@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).index_by(&:id).transform_values { |comp| comp.as_json(methods: %w[], include: [], only: %w[cellName id countryId]) }
{
rows: rows.as_json, competitionsById: competitions_by_id
}
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)
FinnIckler marked this conversation as resolved.
Show resolved Hide resolved
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
24 changes: 2 additions & 22 deletions app/views/results/rankings.html.erb
Original file line number Diff line number Diff line change
@@ -1,29 +1,9 @@
<% 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") %>
</div>
4 changes: 1 addition & 3 deletions app/webpacker/components/CompetitionsOverview/filterUtils.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { DateTime } from 'luxon';
import {
continents, countries, events, nonFutureCompetitionYears,
continents, countries, nonFutureCompetitionYears, WCA_EVENT_IDS,
} from '../../lib/wca-data.js.erb';

const WCA_EVENT_IDS = Object.values(events.official).map((e) => e.id);

// note: inconsistencies with previous search params
// - year value was 'all+years', is now 'all_years'
// --> handled by the fact that every non-number is interpreted as "default value" all_years
Expand Down
191 changes: 191 additions & 0 deletions app/webpacker/components/Results/Rankings/RankingsTable.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import React, { useMemo } from 'react';
import { Table } from 'semantic-ui-react';
import _ from 'lodash';
import { flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';
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 getCountryOrContinent(result, firstContinentIndex, firstCountryIndex, index) {
if (index < firstContinentIndex) {
return { name: I18n.t('results.table_elements.world') };
}
if (index >= firstContinentIndex && index < firstCountryIndex) {
return continents.real.find((c) => c.id === countries.byId[result.countryId].continentId);
}
return countries.byId[result.countryId];
}

function CountryCell({ country }) {
return (
<Table.Cell textAlign="left">
{country.iso2 && <CountryFlag iso2={country.iso2} />}
{' '}
{country.name}
FinnIckler marked this conversation as resolved.
Show resolved Hide resolved
</Table.Cell>
);
}

function ResultRow({
result, competition, rank, isAverage, show, country,
}) {
const attempts = [result.value1, result.value2, result.value3, result.value4, result.value5]
.filter(Boolean);

const bestResult = _.max(attempts);
const worstResult = _.min(attempts);
const bestResultIndex = attempts.indexOf(bestResult);
const worstResultIndex = attempts.indexOf(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={countries.byId[competition.countryId].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 results = useMemo(() => {
const isByRegion = show === 'by region';
const [rowsToMap, firstContinentIndex, firstCountryIndex] = isByRegion ? rows : [rows, 0, 0];

return rowsToMap.reduce((acc, result, index) => {
const competition = competitionsById[result.competitionId];
const { value } = result;

const lastItem = acc[acc.length - 1];
const previousValue = lastItem?.result.value || 0;
const previousRank = lastItem?.rank || 0;

const rank = value === previousValue ? previousRank : index + 1;
const tiedPrevious = rank === previousRank;

const country = getCountryOrContinent(result, firstContinentIndex, firstCountryIndex, index);

return [...acc, {
result,
competition,
country,
rank,
tiedPrevious,
key: `${result.id}-${show}-${country.name}`,
}];
}, []);
}, [competitionsById, rows, show]);

const columns = useMemo(() => {
const commonColumns = [
{
accessorKey: 'rank',
header: show !== 'by region' ? '#' : I18n.t('results.table_elements.region'),
},
{
accessorKey: 'result.name',
header: I18n.t('results.table_elements.name'),
},
{
accessorKey: 'result.value',
header: I18n.t('results.table_elements.result'),
},
];

if (show !== 'by region') {
commonColumns.push({
accessorKey: 'country.name',
header: I18n.t('results.table_elements.representing'),
});
}

commonColumns.push({
accessorKey: 'competition.name',
header: I18n.t('results.table_elements.competition'),
});

if (isAverage) {
// One Cell per Solve of an Average
commonColumns.push({
accessorKey: 'solves',
header: I18n.t('results.table_elements.solves'),
});
commonColumns.push(
...Array(4).fill({
accessorKey: '',
header: ' ',
}),
);
FinnIckler marked this conversation as resolved.
Show resolved Hide resolved
}

return commonColumns;
}, [show, isAverage]);

const table = useReactTable({
data: results,
columns,
getCoreRowModel: getCoreRowModel(),
});

return (
<div style={{ overflowX: 'scroll' }}>
<Table basic="very" compact="very" singleLine striped unstackable>
<Table.Header>
{table.getHeaderGroups().map((headerGroup) => (
<Table.Row key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<Table.HeaderCell key={header.id}>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
</Table.HeaderCell>
))}
</Table.Row>
))}
</Table.Header>
<Table.Body>
{table.getRowModel().rows.map((row) => {
const {
country, result, competition, rank, tiedPrevious,
} = row.original;

return (
FinnIckler marked this conversation as resolved.
Show resolved Hide resolved
<ResultRow
country={country}
key={row.key}
result={result}
competition={competition}
rank={rank}
tiedPrevious={tiedPrevious}
isAverage={isAverage}
show={show}
/>
);
})}
</Table.Body>
</Table>
</div>
);
}
Loading
Loading