diff --git a/dotcom-rendering/src/components/FootballMatchInfo.tsx b/dotcom-rendering/src/components/FootballMatchInfo.tsx index d3c9059741d..110468b6c73 100644 --- a/dotcom-rendering/src/components/FootballMatchInfo.tsx +++ b/dotcom-rendering/src/components/FootballMatchInfo.tsx @@ -3,7 +3,7 @@ import type { FootballMatchStats, FootballMatchTeamWithStats, } from '../footballMatchStats'; -import type { FootballTable as FootballTableData } from '../footballTables'; +import type { FootballTableSummary } from '../footballTables'; import { grid } from '../grid'; import { FootballMatchStat } from './FootballMatchStat'; import { LeagueTable } from './LeagueTable'; @@ -11,7 +11,7 @@ import { Lineups } from './Lineups'; type Props = { match: FootballMatchStats; - table?: FootballTableData; + table?: FootballTableSummary; }; function teamHasStats({ diff --git a/dotcom-rendering/src/components/LeagueTable.tsx b/dotcom-rendering/src/components/LeagueTable.tsx index 0146ce49de1..143b0188d26 100644 --- a/dotcom-rendering/src/components/LeagueTable.tsx +++ b/dotcom-rendering/src/components/LeagueTable.tsx @@ -7,14 +7,11 @@ import { textSansBold14, textSansBold15, } from '@guardian/source/foundations'; -import type { - Entry, - FootballTable as FootballTableData, -} from '../footballTables'; +import type { EntrySummary, FootballTableSummary } from '../footballTables'; import { palette } from '../palette'; type Props = { - table: FootballTableData; + table: FootballTableSummary; }; export const LeagueTable = ({ table }: Props) => { @@ -43,7 +40,7 @@ const Title = ({ text }: { text: string }) => ( ); -const Table = ({ table }: { table: FootballTableData }) => { +const Table = ({ table }: { table: FootballTableSummary }) => { return ( { ); }; -const TableRow = ({ entry }: { entry: Entry }) => { +const TableRow = ({ entry }: { entry: EntrySummary }) => { return (
diff --git a/dotcom-rendering/src/footballTables.ts b/dotcom-rendering/src/footballTables.ts index 4d69a6aab4a..4e74d071267 100644 --- a/dotcom-rendering/src/footballTables.ts +++ b/dotcom-rendering/src/footballTables.ts @@ -3,7 +3,9 @@ import { listParse } from './footballMatches'; import type { FEFootballTable, FEGroup, + FEGroupSummary, FELeagueTableEntry, + FELeagueTableEntrySummary, FETeamResult, } from './frontend/feFootballTablesPage'; import { error, ok, type Result } from './lib/result'; @@ -26,7 +28,7 @@ type Team = { url?: string; }; -export type Entry = { +export type EntrySummary = { position: number; team: Team; gamesPlayed: number; @@ -37,6 +39,9 @@ export type Entry = { goalsAgainst: number; goalDifference: number; points: number; +}; + +type Entry = EntrySummary & { results: TeamResult[]; }; @@ -53,6 +58,11 @@ export type FootballTable = { entries: Entry[]; }; +export type FootballTableSummary = { + groupName?: string; + entries: EntrySummary[]; +}; + export type FootballTableCompetitions = FootballTableCompetition[]; type MissingScore = { @@ -62,12 +72,22 @@ type MissingScore = { type ParserError = MissingScore; -const parseTable = (feGroup: FEGroup): Result => +export const parseTable = ( + feGroup: FEGroup, +): Result => parseEntries(feGroup.entries).map((entries) => ({ groupName: feGroup.round.name, entries: entries.sort((a, b) => a.position - b.position), })); +export const parseTableSummary = ( + feGroup: FEGroupSummary, +): Result => + parseEntriesSummaries(feGroup.entries).map((entries) => ({ + groupName: feGroup.round.name, + entries: entries.sort((a, b) => a.position - b.position), + })); + const parseTables = listParse(parseTable); const parseResult = (result: FETeamResult): Result => { @@ -102,12 +122,12 @@ const parseResult = (result: FETeamResult): Result => { const parseResults = listParse(parseResult); -const parseEntry = ( - feEntry: FELeagueTableEntry, -): Result => { +const mapBaseEntryFields = ( + feEntry: FELeagueTableEntrySummary, +): EntrySummary => { const { team, teamUrl } = feEntry; - return parseResults(feEntry.results).map((results) => ({ + return { position: team.rank, team: { name: cleanTeamName(team.name), @@ -122,12 +142,28 @@ const parseEntry = ( goalsAgainst: team.total.goalsAgainst, goalDifference: team.goalDifference, points: team.points, + }; +}; + +const parseEntry = ( + feEntry: FELeagueTableEntry, +): Result => { + return parseResults(feEntry.results).map((results) => ({ + ...mapBaseEntryFields(feEntry), results, })); }; const parseEntries = listParse(parseEntry); +const parseEntrySummary = ( + feEntry: FELeagueTableEntrySummary, +): Result => { + return ok(mapBaseEntryFields(feEntry)); +}; + +const parseEntriesSummaries = listParse(parseEntrySummary); + const parseFootballTableCompetition = ( table: FEFootballTable, ): Result => diff --git a/dotcom-rendering/src/frontend/feFootballMatchPage.ts b/dotcom-rendering/src/frontend/feFootballMatchPage.ts index b67f5fb9e62..975456e8791 100644 --- a/dotcom-rendering/src/frontend/feFootballMatchPage.ts +++ b/dotcom-rendering/src/frontend/feFootballMatchPage.ts @@ -1,4 +1,5 @@ import type { FEFootballDataPage } from './feFootballDataPage'; +import { type FEGroupSummary } from './feFootballTablesPage'; export type FEFootballPlayerEvent = { eventTime: string; @@ -42,4 +43,5 @@ export type FEFootballMatch = { export type FEFootballMatchPage = FEFootballDataPage & { footballMatch: FEFootballMatch; + group?: FEGroupSummary; }; diff --git a/dotcom-rendering/src/frontend/feFootballTablesPage.ts b/dotcom-rendering/src/frontend/feFootballTablesPage.ts index 81109fce21c..58eebd3f116 100644 --- a/dotcom-rendering/src/frontend/feFootballTablesPage.ts +++ b/dotcom-rendering/src/frontend/feFootballTablesPage.ts @@ -42,11 +42,14 @@ export type FERecentResultsPerTeam = { results: FETeamResult[]; }; -export type FELeagueTableEntry = { +export type FELeagueTableEntrySummary = { stageNumber: string; round: FERound; team: FELeagueTeam; teamUrl?: string; +}; + +export type FELeagueTableEntry = FELeagueTableEntrySummary & { results: FETeamResult[]; }; @@ -55,6 +58,11 @@ export type FEGroup = { entries: FELeagueTableEntry[]; }; +export type FEGroupSummary = { + round: FERound; + entries: FELeagueTableEntrySummary[]; +}; + export type FEFootballTable = { competition: FECompetitionSummary; groups: FEGroup[]; diff --git a/dotcom-rendering/src/frontend/schemas/feFootballMatchPage.json b/dotcom-rendering/src/frontend/schemas/feFootballMatchPage.json index a5f1ef61a5e..fdc9d4a4d27 100644 --- a/dotcom-rendering/src/frontend/schemas/feFootballMatchPage.json +++ b/dotcom-rendering/src/frontend/schemas/feFootballMatchPage.json @@ -294,6 +294,185 @@ "id", "status" ] + }, + "group": { + "type": "object", + "properties": { + "round": { + "type": "object", + "properties": { + "roundNumber": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "roundNumber" + ] + }, + "entries": { + "type": "array", + "items": { + "type": "object", + "properties": { + "stageNumber": { + "type": "string" + }, + "round": { + "type": "object", + "properties": { + "roundNumber": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "roundNumber" + ] + }, + "team": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "rank": { + "type": "number" + }, + "total": { + "type": "object", + "properties": { + "played": { + "type": "number" + }, + "won": { + "type": "number" + }, + "drawn": { + "type": "number" + }, + "lost": { + "type": "number" + }, + "goalsFor": { + "type": "number" + }, + "goalsAgainst": { + "type": "number" + } + }, + "required": [ + "drawn", + "goalsAgainst", + "goalsFor", + "lost", + "played", + "won" + ] + }, + "home": { + "type": "object", + "properties": { + "played": { + "type": "number" + }, + "won": { + "type": "number" + }, + "drawn": { + "type": "number" + }, + "lost": { + "type": "number" + }, + "goalsFor": { + "type": "number" + }, + "goalsAgainst": { + "type": "number" + } + }, + "required": [ + "drawn", + "goalsAgainst", + "goalsFor", + "lost", + "played", + "won" + ] + }, + "away": { + "type": "object", + "properties": { + "played": { + "type": "number" + }, + "won": { + "type": "number" + }, + "drawn": { + "type": "number" + }, + "lost": { + "type": "number" + }, + "goalsFor": { + "type": "number" + }, + "goalsAgainst": { + "type": "number" + } + }, + "required": [ + "drawn", + "goalsAgainst", + "goalsFor", + "lost", + "played", + "won" + ] + }, + "goalDifference": { + "type": "number" + }, + "points": { + "type": "number" + } + }, + "required": [ + "away", + "goalDifference", + "home", + "id", + "name", + "points", + "rank", + "total" + ] + }, + "teamUrl": { + "type": "string" + } + }, + "required": [ + "round", + "stageNumber", + "team" + ] + } + } + }, + "required": [ + "entries", + "round" + ] } }, "required": [ diff --git a/dotcom-rendering/src/frontend/schemas/feFootballTablesPage.json b/dotcom-rendering/src/frontend/schemas/feFootballTablesPage.json index 5c2716d518e..b54613460ff 100644 --- a/dotcom-rendering/src/frontend/schemas/feFootballTablesPage.json +++ b/dotcom-rendering/src/frontend/schemas/feFootballTablesPage.json @@ -105,210 +105,7 @@ "entries": { "type": "array", "items": { - "type": "object", - "properties": { - "stageNumber": { - "type": "string" - }, - "round": { - "type": "object", - "properties": { - "roundNumber": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "required": [ - "roundNumber" - ] - }, - "team": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "rank": { - "type": "number" - }, - "total": { - "type": "object", - "properties": { - "played": { - "type": "number" - }, - "won": { - "type": "number" - }, - "drawn": { - "type": "number" - }, - "lost": { - "type": "number" - }, - "goalsFor": { - "type": "number" - }, - "goalsAgainst": { - "type": "number" - } - }, - "required": [ - "drawn", - "goalsAgainst", - "goalsFor", - "lost", - "played", - "won" - ] - }, - "home": { - "type": "object", - "properties": { - "played": { - "type": "number" - }, - "won": { - "type": "number" - }, - "drawn": { - "type": "number" - }, - "lost": { - "type": "number" - }, - "goalsFor": { - "type": "number" - }, - "goalsAgainst": { - "type": "number" - } - }, - "required": [ - "drawn", - "goalsAgainst", - "goalsFor", - "lost", - "played", - "won" - ] - }, - "away": { - "type": "object", - "properties": { - "played": { - "type": "number" - }, - "won": { - "type": "number" - }, - "drawn": { - "type": "number" - }, - "lost": { - "type": "number" - }, - "goalsFor": { - "type": "number" - }, - "goalsAgainst": { - "type": "number" - } - }, - "required": [ - "drawn", - "goalsAgainst", - "goalsFor", - "lost", - "played", - "won" - ] - }, - "goalDifference": { - "type": "number" - }, - "points": { - "type": "number" - } - }, - "required": [ - "away", - "goalDifference", - "home", - "id", - "name", - "points", - "rank", - "total" - ] - }, - "teamUrl": { - "type": "string" - }, - "results": { - "type": "array", - "items": { - "type": "object", - "properties": { - "matchId": { - "type": "string" - }, - "self": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "score": { - "type": "number" - } - }, - "required": [ - "id", - "name" - ] - }, - "foe": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "score": { - "type": "number" - } - }, - "required": [ - "id", - "name" - ] - } - }, - "required": [ - "foe", - "matchId", - "self" - ] - } - } - }, - "required": [ - "results", - "round", - "stageNumber", - "team" - ] + "$ref": "#/definitions/FELeagueTableEntry" } } }, @@ -904,6 +701,223 @@ }, "Record": { "type": "object" + }, + "FELeagueTableEntry": { + "allOf": [ + { + "type": "object", + "properties": { + "stageNumber": { + "type": "string" + }, + "round": { + "type": "object", + "properties": { + "roundNumber": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "roundNumber" + ] + }, + "team": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "rank": { + "type": "number" + }, + "total": { + "type": "object", + "properties": { + "played": { + "type": "number" + }, + "won": { + "type": "number" + }, + "drawn": { + "type": "number" + }, + "lost": { + "type": "number" + }, + "goalsFor": { + "type": "number" + }, + "goalsAgainst": { + "type": "number" + } + }, + "required": [ + "drawn", + "goalsAgainst", + "goalsFor", + "lost", + "played", + "won" + ] + }, + "home": { + "type": "object", + "properties": { + "played": { + "type": "number" + }, + "won": { + "type": "number" + }, + "drawn": { + "type": "number" + }, + "lost": { + "type": "number" + }, + "goalsFor": { + "type": "number" + }, + "goalsAgainst": { + "type": "number" + } + }, + "required": [ + "drawn", + "goalsAgainst", + "goalsFor", + "lost", + "played", + "won" + ] + }, + "away": { + "type": "object", + "properties": { + "played": { + "type": "number" + }, + "won": { + "type": "number" + }, + "drawn": { + "type": "number" + }, + "lost": { + "type": "number" + }, + "goalsFor": { + "type": "number" + }, + "goalsAgainst": { + "type": "number" + } + }, + "required": [ + "drawn", + "goalsAgainst", + "goalsFor", + "lost", + "played", + "won" + ] + }, + "goalDifference": { + "type": "number" + }, + "points": { + "type": "number" + } + }, + "required": [ + "away", + "goalDifference", + "home", + "id", + "name", + "points", + "rank", + "total" + ] + }, + "teamUrl": { + "type": "string" + } + }, + "required": [ + "round", + "stageNumber", + "team" + ] + }, + { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "matchId": { + "type": "string" + }, + "self": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "score": { + "type": "number" + } + }, + "required": [ + "id", + "name" + ] + }, + "foe": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "score": { + "type": "number" + } + }, + "required": [ + "id", + "name" + ] + } + }, + "required": [ + "foe", + "matchId", + "self" + ] + } + } + }, + "required": [ + "results" + ] + } + ] } }, "$schema": "http://json-schema.org/draft-07/schema#" diff --git a/dotcom-rendering/src/server/handler.sportDataPage.web.ts b/dotcom-rendering/src/server/handler.sportDataPage.web.ts index d39a5ff1b92..6aa6b840526 100644 --- a/dotcom-rendering/src/server/handler.sportDataPage.web.ts +++ b/dotcom-rendering/src/server/handler.sportDataPage.web.ts @@ -5,7 +5,10 @@ import { getParserErrorMessage, parse as parseFootballMatches, } from '../footballMatches'; -import { parse as parseFootballTables } from '../footballTables'; +import { + parse as parseFootballTables, + parseTableSummary, +} from '../footballTables'; import type { FECricketMatchPage } from '../frontend/feCricketMatchPage'; import type { FEFootballCompetition } from '../frontend/feFootballDataPage'; import type { FEFootballMatchListPage } from '../frontend/feFootballMatchListPage'; @@ -216,6 +219,7 @@ const parseFEFootballMatch = ( data: FEFootballMatchPage, ): FootballMatchSummaryPage => { const parsedFootballMatch = parseFootballMatch(data.footballMatch); + const group = data.group && parseTableSummary(data.group); if (!parsedFootballMatch.ok) { throw new Error( @@ -223,8 +227,15 @@ const parseFEFootballMatch = ( ); } + if (group && !group.ok) { + throw new Error( + `Failed to parse football league table group: ${group.error.kind} ${group.error.message}`, + ); + } + return { match: parsedFootballMatch.value, + group: group?.value, kind: 'FootballMatchSummary', nav: { ...extractNAV(data.nav), diff --git a/dotcom-rendering/src/sportDataPage.ts b/dotcom-rendering/src/sportDataPage.ts index 4bab118adc4..62b6d475db8 100644 --- a/dotcom-rendering/src/sportDataPage.ts +++ b/dotcom-rendering/src/sportDataPage.ts @@ -1,7 +1,10 @@ import type { CricketMatch } from './cricketMatch'; import type { FootballMatch } from './footballMatch'; import type { FootballMatches } from './footballMatches'; -import type { FootballTableCompetitions } from './footballTables'; +import type { + FootballTableCompetitions, + FootballTableSummary, +} from './footballTables'; import type { FESportPageConfig } from './frontend/feFootballDataPage'; import type { EditionId } from './lib/edition'; import type { NavType } from './model/extract-nav'; @@ -48,6 +51,7 @@ export type CricketMatchPage = SportPageConfig & { export type FootballMatchSummaryPage = SportPageConfig & { match: FootballMatch; + group?: FootballTableSummary; kind: 'FootballMatchSummary'; };