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

WCA Live: Podiums and Competitors Views #10819

Merged
merged 13 commits into from
Feb 13, 2025
22 changes: 22 additions & 0 deletions app/controllers/live_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,26 @@ def schedule_admin

@rounds = Round.joins(:competition_event).where(competition_event: { competition_id: @competition_id })
end

def podiums
@competition = Competition.find(params[:competition_id])
@competitors = @competition.registrations.includes(:user).accepted
@final_rounds = @competition.rounds.select(&:final_round?)
end

def competitors
@competition = Competition.find(params[:competition_id])
@competitors = @competition.registrations.includes(:user).accepted
end

def by_person
registration_id = params.require(:registration_id)
registration = Registration.find(registration_id)

@competition_id = params[:competition_id]
@competition = Competition.find(@competition_id)

@user = registration.user
@results = registration.live_results.includes(:live_attempts)
end
end
8 changes: 8 additions & 0 deletions app/models/round.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ def round_type_id
end
end

def event_id
event.id
end

def formats_used
cutoff_format = Format.c_find!(cutoff.number_of_attempts.to_s) if cutoff
[cutoff_format, format].compact
Expand Down Expand Up @@ -137,6 +141,10 @@ def advancement_condition_to_s(short: false)
advancement_condition ? advancement_condition.to_s(self, short: short) : ""
end

def live_podium
live_results.where(ranking: 1..3)
end

def previous_round
return nil if number == 1
Round.joins(:competition_event).find_by(competition_event: competition_event, number: number - 1)
Expand Down
5 changes: 5 additions & 0 deletions app/views/live/by_person.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<% provide(:title, 'WCA Live Integration Test Page') %>

<%= render layout: 'competitions/nav' do %>
<%= react_component('Live/ByPerson', { allResults: @results, user: @user, competitionId: @competition_id, }) %>
<% end %>
5 changes: 5 additions & 0 deletions app/views/live/competitors.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<% provide(:title, 'WCA Live Integration Test Page') %>

<%= render layout: 'competitions/nav' do %>
<%= react_component('Live/Competitors', { competitors: @competitors.as_json({ methods: [:user]}), competitionId: @competition.id, }) %>
<% end %>
5 changes: 5 additions & 0 deletions app/views/live/podiums.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<% provide(:title, 'WCA Live Integration Test Page') %>

<%= render layout: 'competitions/nav' do %>
<%= react_component('Live/Podiums', { podiums: @final_rounds.as_json({ methods: [:event_id, :live_podium]}), competitors: @competitors.as_json({ methods: [:user]}), competitionId: @competition.id, }) %>
<% end %>
85 changes: 85 additions & 0 deletions app/webpacker/components/Live/ByPerson/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react';
import {
Header, Segment, Table,
} from 'semantic-ui-react';
import _ from 'lodash';
import { events } from '../../../lib/wca-data.js.erb';
import WCAQueryClientProvider from '../../../lib/providers/WCAQueryClientProvider';
import { formatAttemptResult } from '../../../lib/wca-live/attempts';
import { liveUrls } from '../../../lib/requests/routes.js.erb';
import { rankingCellStyle } from '../components/ResultsTable';

export default function Wrapper({
results, user, competitionId,
}) {
return (
<WCAQueryClientProvider>
<PersonResults results={results} user={user} competitionId={competitionId} />
</WCAQueryClientProvider>
);
}

function PersonResults({
results, user, competitionId,
}) {
const resultsByEvent = _.groupBy(results, 'event_id');

return (
<Segment>
<Header>
{user.name}
</Header>
{_.map(resultsByEvent, (eventResults, key) => (
<>
<Header as="h3">{events.byId[key].name}</Header>
<Table>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Round</Table.HeaderCell>
<Table.HeaderCell>Rank</Table.HeaderCell>
{_.times(events.byId[key].recommendedFormat().expectedSolveCount).map((num) => (
<Table.HeaderCell key={num}>
{num + 1}
</Table.HeaderCell>
))}
<Table.HeaderCell>Average</Table.HeaderCell>
<Table.HeaderCell>Best</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{eventResults.map((result) => {
const {
round,
attempts,
ranking,
average,
best,
} = result;

return (
<Table.Row>
<Table.Cell width={1}>
<a href={liveUrls.roundResults(competitionId, round.id)}>
Round
{' '}
{round.number}
</a>
</Table.Cell>
<Table.Cell width={1} style={rankingCellStyle(result)}>{ranking}</Table.Cell>
{attempts.map((a) => (
<Table.Cell>
{formatAttemptResult(a.result, events.byId[key].id)}
</Table.Cell>
))}
<Table.Cell>{formatAttemptResult(average, events.byId[key].id)}</Table.Cell>
<Table.Cell>{formatAttemptResult(best, events.byId[key].id)}</Table.Cell>
</Table.Row>
);
})}
</Table.Body>
</Table>
</>
))}
</Segment>
);
}
46 changes: 46 additions & 0 deletions app/webpacker/components/Live/Competitors/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import {
Input, Segment, Table,
} from 'semantic-ui-react';
import WCAQueryClientProvider from '../../../lib/providers/WCAQueryClientProvider';
import { liveUrls } from '../../../lib/requests/routes.js.erb';
import useInputState from '../../../lib/hooks/useInputState';
import CountryFlag from '../../wca/CountryFlag';

export default function Wrapper({
competitionId, competitors,
}) {
return (
<WCAQueryClientProvider>
<Competitors competitionId={competitionId} competitors={competitors} />
</WCAQueryClientProvider>
);
}

function Competitors({
competitionId, competitors,
}) {
const [searchInput, setSearchInput] = useInputState('');

return (
<Segment>
<Input placeholder="Search Competitor" value={searchInput} onChange={setSearchInput} icon="magnifying_glass" />
<Table basic="very" selectable>
<Table.Header>
<Table.HeaderCell width={1} />
<Table.HeaderCell />
</Table.Header>
<Table.Body>
{competitors.filter((c) => c.user.name.includes(searchInput)).map((c) => (
<Table.Row>
<Table.Cell><CountryFlag iso2={c.user.country_iso2} /></Table.Cell>
<Table.Cell>
<a href={liveUrls.personResults(competitionId, c.id)}>{c.user.name}</a>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</Segment>
);
}
43 changes: 43 additions & 0 deletions app/webpacker/components/Live/Podiums/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import {
Container,
Header,
} from 'semantic-ui-react';
import { events } from '../../../lib/wca-data.js.erb';
import WCAQueryClientProvider from '../../../lib/providers/WCAQueryClientProvider';
import ResultsTable from '../components/ResultsTable';

export default function Wrapper({
podiums, competitionId, competitors,
}) {
return (
<WCAQueryClientProvider>
<Podiums podiums={podiums} competitionId={competitionId} competitors={competitors} />
</WCAQueryClientProvider>
);
}

function Podiums({
podiums, competitionId, competitors,
}) {
return (
<Container fluid>
<Header>
Podiums
</Header>
{podiums.map((finalRound) => (
<>
<Header as="h3">{events.byId[finalRound.event_id].name}</Header>
{finalRound.live_podium.length > 0 ? (
<ResultsTable
results={finalRound.live_podium}
competitionId={competitionId}
competitors={competitors}
event={events.byId[finalRound.event_id]}
/>
) : 'Podiums to be determined'}
</>
))}
</Container>
);
}
7 changes: 2 additions & 5 deletions app/webpacker/components/Live/components/ResultsTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,11 @@ const customOrderBy = (competitor, resultsByRegistrationId, sortBy) => {
};

export const rankingCellStyle = (result) => {
if (!result) {
return {};
}
if (result.advancing) {
if (result?.advancing) {
return { backgroundColor: `rgb(${advancingColor})` };
}

if (result.advancing_questionable) {
if (result?.advancing_questionable) {
return { backgroundColor: `rgba(${advancingColor}, 0.5)` };
}

Expand Down
1 change: 1 addition & 0 deletions app/webpacker/lib/requests/routes.js.erb
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ export const actionUrls = {
}

export const liveUrls = {
personResults: (competitionId, registrationId) => `<%= CGI.unescape(Rails.application.routes.url_helpers.live_person_results_path(competition_id: "${competitionId}", registration_id: "${registrationId}")) %>`,
roundResultsAdmin: (competitionId, roundId) => `<%= CGI.unescape(Rails.application.routes.url_helpers.live_admin_round_results_path(competition_id: "${competitionId}", round_id: "${roundId}")) %>`,
checkRoundResultsAdmin: (competitionId, roundId) => `<%= CGI.unescape(Rails.application.routes.url_helpers.live_admin_check_round_results_path(competition_id: "${competitionId}", round_id: "${roundId}")) %>`,
api: {
Expand Down
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@
get 'competitions/edit/series-eligible-competitions-json' => 'competitions#series_eligible_competitions_json', as: :series_eligible_competitions_json

if WcaLive.enabled?
get 'competitions/:competition_id/live/competitors/:registration_id' => 'live#by_person', as: :live_person_results
get 'competitions/:competition_id/live/podiums' => 'live#podiums', as: :live_podiums
get 'competitions/:competition_id/live/competitors' => 'live#competitors', as: :live_competitors
get 'competitions/:competition_id/live/rounds/:round_id/admin' => 'live#admin', as: :live_admin_round_results
get 'competitions/:competition_id/live/rounds/:round_id/admin/check' => 'live#double_check', as: :live_admin_check_round_results
get 'competitions/:competition_id/live/admin' => 'live#schedule_admin', as: :live_schedule_admin
Expand Down