From f2ac5277cd5bf7be71093fc5d76a87e9db152b9b Mon Sep 17 00:00:00 2001 From: Dave Clarke Date: Mon, 6 Jul 2026 16:29:14 +0100 Subject: [PATCH 1/5] Add OpenF1 plugin New community plugin for the free OpenF1 API (https://openf1.org). Indexes F1 meetings, sessions, and the current driver grid, with dashboards for session results, laps, pit stops, tyre stints, weather, race control, overtakes, and championship standings. No authentication required (public historical data). Co-Authored-By: Claude Opus 4.8 (1M context) --- plugins/OpenF1/v1/configValidation.json | 11 + plugins/OpenF1/v1/custom_types.json | 23 ++ .../v1/dataStreams/championshipDrivers.json | 75 ++++ .../v1/dataStreams/championshipTeams.json | 22 ++ .../v1/dataStreams/connectionCheck.json | 21 + plugins/OpenF1/v1/dataStreams/drivers.json | 24 ++ plugins/OpenF1/v1/dataStreams/laps.json | 106 +++++ .../v1/dataStreams/meetingSessions.json | 29 ++ plugins/OpenF1/v1/dataStreams/meetings.json | 28 ++ plugins/OpenF1/v1/dataStreams/overtakes.json | 76 ++++ plugins/OpenF1/v1/dataStreams/pitStops.json | 80 ++++ .../OpenF1/v1/dataStreams/raceControl.json | 89 +++++ .../OpenF1/v1/dataStreams/sessionDrivers.json | 84 ++++ .../OpenF1/v1/dataStreams/sessionResult.json | 111 ++++++ plugins/OpenF1/v1/dataStreams/sessions.json | 28 ++ plugins/OpenF1/v1/dataStreams/stints.json | 83 ++++ plugins/OpenF1/v1/dataStreams/weather.json | 25 ++ .../OpenF1/v1/defaultContent/driver.dash.json | 104 +++++ .../OpenF1/v1/defaultContent/manifest.json | 8 + .../v1/defaultContent/meeting.dash.json | 97 +++++ .../v1/defaultContent/overview.dash.json | 106 +++++ plugins/OpenF1/v1/defaultContent/scopes.json | 38 ++ .../v1/defaultContent/session.dash.json | 372 ++++++++++++++++++ plugins/OpenF1/v1/docs/README.md | 48 +++ plugins/OpenF1/v1/icon.svg | 4 + .../OpenF1/v1/indexDefinitions/default.json | 65 +++ plugins/OpenF1/v1/metadata.json | 37 ++ plugins/OpenF1/v1/ui.json | 1 + 28 files changed, 1795 insertions(+) create mode 100644 plugins/OpenF1/v1/configValidation.json create mode 100644 plugins/OpenF1/v1/custom_types.json create mode 100644 plugins/OpenF1/v1/dataStreams/championshipDrivers.json create mode 100644 plugins/OpenF1/v1/dataStreams/championshipTeams.json create mode 100644 plugins/OpenF1/v1/dataStreams/connectionCheck.json create mode 100644 plugins/OpenF1/v1/dataStreams/drivers.json create mode 100644 plugins/OpenF1/v1/dataStreams/laps.json create mode 100644 plugins/OpenF1/v1/dataStreams/meetingSessions.json create mode 100644 plugins/OpenF1/v1/dataStreams/meetings.json create mode 100644 plugins/OpenF1/v1/dataStreams/overtakes.json create mode 100644 plugins/OpenF1/v1/dataStreams/pitStops.json create mode 100644 plugins/OpenF1/v1/dataStreams/raceControl.json create mode 100644 plugins/OpenF1/v1/dataStreams/sessionDrivers.json create mode 100644 plugins/OpenF1/v1/dataStreams/sessionResult.json create mode 100644 plugins/OpenF1/v1/dataStreams/sessions.json create mode 100644 plugins/OpenF1/v1/dataStreams/stints.json create mode 100644 plugins/OpenF1/v1/dataStreams/weather.json create mode 100644 plugins/OpenF1/v1/defaultContent/driver.dash.json create mode 100644 plugins/OpenF1/v1/defaultContent/manifest.json create mode 100644 plugins/OpenF1/v1/defaultContent/meeting.dash.json create mode 100644 plugins/OpenF1/v1/defaultContent/overview.dash.json create mode 100644 plugins/OpenF1/v1/defaultContent/scopes.json create mode 100644 plugins/OpenF1/v1/defaultContent/session.dash.json create mode 100644 plugins/OpenF1/v1/docs/README.md create mode 100644 plugins/OpenF1/v1/icon.svg create mode 100644 plugins/OpenF1/v1/indexDefinitions/default.json create mode 100644 plugins/OpenF1/v1/metadata.json create mode 100644 plugins/OpenF1/v1/ui.json diff --git a/plugins/OpenF1/v1/configValidation.json b/plugins/OpenF1/v1/configValidation.json new file mode 100644 index 00000000..580dd453 --- /dev/null +++ b/plugins/OpenF1/v1/configValidation.json @@ -0,0 +1,11 @@ +{ + "steps": [ + { + "displayName": "Connect to OpenF1", + "dataStream": { "name": "connectionCheck" }, + "required": true, + "error": "Could not reach the OpenF1 API (https://api.openf1.org). Check your network connection or agent egress.", + "success": "Connected to OpenF1 successfully." + } + ] +} diff --git a/plugins/OpenF1/v1/custom_types.json b/plugins/OpenF1/v1/custom_types.json new file mode 100644 index 00000000..3eacf92c --- /dev/null +++ b/plugins/OpenF1/v1/custom_types.json @@ -0,0 +1,23 @@ +[ + { + "name": "F1 Meeting", + "sourceType": "F1 Meeting", + "icon": "flag-checkered", + "singular": "Meeting", + "plural": "Meetings" + }, + { + "name": "F1 Session", + "sourceType": "F1 Session", + "icon": "stopwatch", + "singular": "Session", + "plural": "Sessions" + }, + { + "name": "F1 Driver", + "sourceType": "F1 Driver", + "icon": "helmet-safety", + "singular": "Driver", + "plural": "Drivers" + } +] diff --git a/plugins/OpenF1/v1/dataStreams/championshipDrivers.json b/plugins/OpenF1/v1/dataStreams/championshipDrivers.json new file mode 100644 index 00000000..e0d44195 --- /dev/null +++ b/plugins/OpenF1/v1/dataStreams/championshipDrivers.json @@ -0,0 +1,75 @@ +{ + "name": "championshipDrivers", + "displayName": "Championship Drivers", + "description": "Current drivers' championship standings", + "tags": [ + "Championship", + "Driver" + ], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/championship_drivers", + "getArgs": [ + { + "key": "session_key", + "value": "latest" + } + ] + }, + "matches": "none", + "timeframes": false, + "defaultShaping": { + "sort": { + "by": [ + [ + "position_current", + "asc" + ] + ] + } + }, + "metadata": [ + { + "name": "position_current", + "displayName": "Position", + "shape": "number", + "role": "value" + }, + { + "name": "driver_number", + "displayName": "Driver #", + "shape": "number", + "role": "label" + }, + { + "name": "driverName", + "displayName": "Driver", + "sourceId": "driver_number", + "sourceType": "F1 Driver", + "objectPropertyPath": "name" + }, + { + "name": "driverTeam", + "displayName": "Team", + "sourceId": "driver_number", + "sourceType": "F1 Driver", + "objectPropertyPath": "team_name" + }, + { + "name": "points_current", + "displayName": "Points", + "shape": "number" + }, + { + "name": "position_start", + "displayName": "Prev Position", + "shape": "number" + }, + { + "name": "points_start", + "displayName": "Prev Points", + "shape": "number" + } + ] +} diff --git a/plugins/OpenF1/v1/dataStreams/championshipTeams.json b/plugins/OpenF1/v1/dataStreams/championshipTeams.json new file mode 100644 index 00000000..9b8dea29 --- /dev/null +++ b/plugins/OpenF1/v1/dataStreams/championshipTeams.json @@ -0,0 +1,22 @@ +{ + "name": "championshipTeams", + "displayName": "Championship Teams", + "description": "Current constructors' championship standings, one row per team", + "tags": ["Championship", "Results"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/championship_teams", + "getArgs": [{ "key": "session_key", "value": "latest" }] + }, + "matches": "none", + "timeframes": false, + "defaultShaping": { "sort": { "by": [["position_current", "asc"]] } }, + "metadata": [ + { "name": "position_current", "displayName": "Position", "shape": "number", "role": "value" }, + { "name": "team_name", "displayName": "Team", "shape": "string", "role": "label" }, + { "name": "points_current", "displayName": "Points", "shape": "number" }, + { "name": "position_start", "displayName": "Prev Position", "shape": "number" }, + { "name": "points_start", "displayName": "Prev Points", "shape": "number" } + ] +} diff --git a/plugins/OpenF1/v1/dataStreams/connectionCheck.json b/plugins/OpenF1/v1/dataStreams/connectionCheck.json new file mode 100644 index 00000000..c2f03fd5 --- /dev/null +++ b/plugins/OpenF1/v1/dataStreams/connectionCheck.json @@ -0,0 +1,21 @@ +{ + "name": "connectionCheck", + "displayName": "Connection Check", + "description": "Most recent session, used to confirm the OpenF1 API is reachable", + "tags": ["Session"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/sessions", + "getArgs": [{ "key": "session_key", "value": "latest" }] + }, + "matches": "none", + "timeframes": false, + "visibility": { "type": "hidden" }, + "metadata": [ + { "name": "session_key", "displayName": "Session Key", "shape": "string", "role": "id" }, + { "name": "session_name", "displayName": "Session", "shape": "string", "role": "label" }, + { "name": "country_name", "displayName": "Country", "shape": "string" }, + { "name": "year", "displayName": "Year", "shape": "number" } + ] +} diff --git a/plugins/OpenF1/v1/dataStreams/drivers.json b/plugins/OpenF1/v1/dataStreams/drivers.json new file mode 100644 index 00000000..68c7c7aa --- /dev/null +++ b/plugins/OpenF1/v1/dataStreams/drivers.json @@ -0,0 +1,24 @@ +{ + "name": "drivers", + "displayName": "Drivers", + "description": "Current Formula 1 drivers with car number, team, nationality and headshot, from the latest session", + "tags": ["Driver"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/drivers", + "getArgs": [{ "key": "session_key", "value": "latest" }] + }, + "matches": "none", + "timeframes": false, + "visibility": { "type": "hidden" }, + "metadata": [ + { "name": "driver_number", "displayName": "Number", "shape": "number", "role": "id" }, + { "name": "full_name", "displayName": "Driver", "shape": "string", "role": "label" }, + { "name": "name_acronym", "displayName": "Code", "shape": "string" }, + { "name": "team_name", "displayName": "Team", "shape": "string" }, + { "name": "team_colour", "displayName": "Team Colour", "shape": "string" }, + { "name": "country_code", "displayName": "Country", "shape": "string" }, + { "name": "headshot_url", "displayName": "Headshot", "shape": "url" } + ] +} diff --git a/plugins/OpenF1/v1/dataStreams/laps.json b/plugins/OpenF1/v1/dataStreams/laps.json new file mode 100644 index 00000000..7d81b5bb --- /dev/null +++ b/plugins/OpenF1/v1/dataStreams/laps.json @@ -0,0 +1,106 @@ +{ + "name": "laps", + "displayName": "Laps", + "description": "Lap-by-lap timing for each driver in a session", + "tags": [ + "Session", + "Laps" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/laps", + "getArgs": [ + { + "key": "session_key", + "value": "{{object.rawId}}" + } + ] + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "F1 Session" + ] + } + }, + "timeframes": false, + "defaultShaping": { + "sort": { + "by": [ + [ + "lap_number", + "asc" + ] + ] + } + }, + "metadata": [ + { + "name": "date_start", + "displayName": "Lap Start", + "shape": "date", + "role": "timestamp" + }, + { + "name": "driver_number", + "displayName": "Driver Number", + "shape": "number", + "role": "label" + }, + { + "name": "driverName", + "displayName": "Driver", + "sourceId": "driver_number", + "sourceType": "F1 Driver", + "objectPropertyPath": "name" + }, + { + "name": "lap_number", + "displayName": "Lap", + "shape": "number" + }, + { + "name": "lap_duration", + "displayName": "Lap Time", + "shape": "seconds", + "role": "value" + }, + { + "name": "duration_sector_1", + "displayName": "Sector 1", + "shape": "seconds" + }, + { + "name": "duration_sector_2", + "displayName": "Sector 2", + "shape": "seconds" + }, + { + "name": "duration_sector_3", + "displayName": "Sector 3", + "shape": "seconds" + }, + { + "name": "i1_speed", + "displayName": "Speed I1 (km/h)", + "shape": "number" + }, + { + "name": "i2_speed", + "displayName": "Speed I2 (km/h)", + "shape": "number" + }, + { + "name": "st_speed", + "displayName": "Speed Trap (km/h)", + "shape": "number" + }, + { + "name": "is_pit_out_lap", + "displayName": "Pit Out Lap", + "shape": "boolean" + } + ] +} diff --git a/plugins/OpenF1/v1/dataStreams/meetingSessions.json b/plugins/OpenF1/v1/dataStreams/meetingSessions.json new file mode 100644 index 00000000..55900e4b --- /dev/null +++ b/plugins/OpenF1/v1/dataStreams/meetingSessions.json @@ -0,0 +1,29 @@ +{ + "name": "meetingSessions", + "displayName": "Meeting Sessions", + "description": "Sessions that make up a race weekend", + "tags": ["Session"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/sessions", + "getArgs": [{ "key": "meeting_key", "value": "{{object.rawId}}" }] + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["F1 Meeting"] } }, + "timeframes": false, + "defaultShaping": { "sort": { "by": [["date_start", "asc"]] } }, + "metadata": [ + { + "name": "session_name", + "displayName": "Session", + "shape": "string", + "role": "label", + "sourceId": "session_key", + "sourceType": "F1 Session" + }, + { "name": "session_type", "displayName": "Type", "shape": "string" }, + { "name": "date_start", "displayName": "Start", "shape": "date", "role": "timestamp" }, + { "name": "date_end", "displayName": "End", "shape": "date" }, + { "name": "session_key", "displayName": "Session Key", "shape": "number", "visible": false } + ] +} diff --git a/plugins/OpenF1/v1/dataStreams/meetings.json b/plugins/OpenF1/v1/dataStreams/meetings.json new file mode 100644 index 00000000..8decd435 --- /dev/null +++ b/plugins/OpenF1/v1/dataStreams/meetings.json @@ -0,0 +1,28 @@ +{ + "name": "meetings", + "displayName": "Meetings", + "description": "Formula 1 race weekends with circuit, country and dates", + "tags": ["Meeting"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/meetings" + }, + "matches": "none", + "timeframes": false, + "visibility": { "type": "hidden" }, + "metadata": [ + { "name": "meeting_key", "displayName": "Meeting Key", "shape": "string", "role": "id" }, + { "name": "meeting_name", "displayName": "Meeting", "shape": "string" }, + { "name": "objectName", "displayName": "Name", "shape": "string", "role": "label", "computed": true, "valueExpression": "{{ $['meeting_name'] }} {{ $['year'] }}" }, + { "name": "meeting_official_name", "displayName": "Official Name", "shape": "string" }, + { "name": "year", "displayName": "Year", "shape": "number" }, + { "name": "country_name", "displayName": "Country", "shape": "string" }, + { "name": "country_code", "displayName": "Country Code", "shape": "string" }, + { "name": "location", "displayName": "Location", "shape": "string" }, + { "name": "circuit_short_name", "displayName": "Circuit", "shape": "string" }, + { "name": "circuit_type", "displayName": "Circuit Type", "shape": "string" }, + { "name": "date_start", "displayName": "Start", "shape": "date" }, + { "name": "date_end", "displayName": "End", "shape": "date" } + ] +} diff --git a/plugins/OpenF1/v1/dataStreams/overtakes.json b/plugins/OpenF1/v1/dataStreams/overtakes.json new file mode 100644 index 00000000..abdd7de1 --- /dev/null +++ b/plugins/OpenF1/v1/dataStreams/overtakes.json @@ -0,0 +1,76 @@ +{ + "name": "overtakes", + "displayName": "Overtakes", + "description": "On-track position changes recorded during a session", + "tags": [ + "Session" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/overtakes", + "getArgs": [ + { + "key": "session_key", + "value": "{{object.rawId}}" + } + ] + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "F1 Session" + ] + } + }, + "timeframes": false, + "defaultShaping": { + "sort": { + "by": [ + [ + "date", + "asc" + ] + ] + } + }, + "metadata": [ + { + "name": "date", + "displayName": "Time", + "shape": "date", + "role": "timestamp" + }, + { + "name": "overtaking_driver_number", + "displayName": "Overtaking Driver #", + "shape": "number", + "role": "label" + }, + { + "name": "overtakingDriver", + "displayName": "Overtaking Driver", + "sourceId": "overtaking_driver_number", + "sourceType": "F1 Driver", + "objectPropertyPath": "name" + }, + { + "name": "overtaken_driver_number", + "displayName": "Overtaken Driver #", + "shape": "number" + }, + { + "name": "overtakenDriver", + "displayName": "Overtaken Driver", + "sourceId": "overtaken_driver_number", + "sourceType": "F1 Driver", + "objectPropertyPath": "name" + }, + { + "name": "position", + "displayName": "New Position", + "shape": "number" + } + ] +} diff --git a/plugins/OpenF1/v1/dataStreams/pitStops.json b/plugins/OpenF1/v1/dataStreams/pitStops.json new file mode 100644 index 00000000..83efaa79 --- /dev/null +++ b/plugins/OpenF1/v1/dataStreams/pitStops.json @@ -0,0 +1,80 @@ +{ + "name": "pitStops", + "displayName": "Pit Stops", + "description": "Pit stops recorded during a session", + "tags": [ + "Session" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/pit", + "getArgs": [ + { + "key": "session_key", + "value": "{{object.rawId}}" + } + ] + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "F1 Session" + ] + } + }, + "timeframes": false, + "defaultShaping": { + "sort": { + "by": [ + [ + "lap_number", + "asc" + ] + ] + } + }, + "metadata": [ + { + "name": "date", + "displayName": "Time", + "shape": "date", + "role": "timestamp" + }, + { + "name": "driver_number", + "displayName": "Driver Number", + "shape": "number", + "role": "label" + }, + { + "name": "driverName", + "displayName": "Driver", + "sourceId": "driver_number", + "sourceType": "F1 Driver", + "objectPropertyPath": "name" + }, + { + "name": "lap_number", + "displayName": "Lap", + "shape": "number" + }, + { + "name": "pit_duration", + "displayName": "Pit Duration", + "shape": "seconds", + "role": "value" + }, + { + "name": "lane_duration", + "displayName": "Lane Duration", + "shape": "seconds" + }, + { + "name": "stop_duration", + "displayName": "Stationary Time", + "shape": "seconds" + } + ] +} diff --git a/plugins/OpenF1/v1/dataStreams/raceControl.json b/plugins/OpenF1/v1/dataStreams/raceControl.json new file mode 100644 index 00000000..beb524bb --- /dev/null +++ b/plugins/OpenF1/v1/dataStreams/raceControl.json @@ -0,0 +1,89 @@ +{ + "name": "raceControl", + "displayName": "Race Control", + "description": "Race control messages for a session, including flags, safety car and incidents", + "tags": [ + "Session" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/race_control", + "getArgs": [ + { + "key": "session_key", + "value": "{{object.rawId}}" + } + ] + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "F1 Session" + ] + } + }, + "timeframes": false, + "defaultShaping": { + "sort": { + "by": [ + [ + "date", + "asc" + ] + ] + } + }, + "metadata": [ + { + "name": "date", + "displayName": "Time", + "shape": "date", + "role": "timestamp" + }, + { + "name": "category", + "displayName": "Category", + "shape": "string" + }, + { + "name": "flag", + "displayName": "Flag", + "shape": "string" + }, + { + "name": "scope", + "displayName": "Scope", + "shape": "string" + }, + { + "name": "sector", + "displayName": "Sector", + "shape": "number" + }, + { + "name": "lap_number", + "displayName": "Lap", + "shape": "number" + }, + { + "name": "driver_number", + "displayName": "Driver #", + "shape": "number" + }, + { + "name": "driverName", + "displayName": "Driver", + "sourceId": "driver_number", + "sourceType": "F1 Driver", + "objectPropertyPath": "name" + }, + { + "name": "message", + "displayName": "Message", + "shape": "string", + "role": "label" + } + ] +} diff --git a/plugins/OpenF1/v1/dataStreams/sessionDrivers.json b/plugins/OpenF1/v1/dataStreams/sessionDrivers.json new file mode 100644 index 00000000..2e417af5 --- /dev/null +++ b/plugins/OpenF1/v1/dataStreams/sessionDrivers.json @@ -0,0 +1,84 @@ +{ + "name": "sessionDrivers", + "displayName": "Session Drivers", + "description": "Driver lineup for a session, one row per driver", + "tags": [ + "Session", + "Drivers" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/drivers", + "getArgs": [ + { + "key": "session_key", + "value": "{{object.rawId}}" + } + ] + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "F1 Session" + ] + } + }, + "timeframes": false, + "defaultShaping": { + "sort": { + "by": [ + [ + "driver_number", + "asc" + ] + ] + } + }, + "metadata": [ + { + "name": "driver_number", + "displayName": "Number", + "shape": "number", + "role": "value" + }, + { + "name": "full_name", + "displayName": "Driver", + "shape": "string", + "role": "label" + }, + { + "name": "name_acronym", + "displayName": "Code", + "shape": "string" + }, + { + "name": "team_name", + "displayName": "Team", + "shape": "string" + }, + { + "name": "country_code", + "displayName": "Country", + "shape": "string" + }, + { + "name": "headshot_url", + "displayName": "Headshot", + "shape": "url" + }, + { + "name": "team_colour", + "displayName": "Team Colour", + "shape": "string", + "visible": false + }, + { + "name": "full_name", + "sourceId": "driver_number", + "sourceType": "F1 Driver" + } + ] +} diff --git a/plugins/OpenF1/v1/dataStreams/sessionResult.json b/plugins/OpenF1/v1/dataStreams/sessionResult.json new file mode 100644 index 00000000..f5d58405 --- /dev/null +++ b/plugins/OpenF1/v1/dataStreams/sessionResult.json @@ -0,0 +1,111 @@ +{ + "name": "sessionResult", + "displayName": "Session Result", + "description": "Final classification for a session, one row per driver", + "tags": [ + "Session", + "Results" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/session_result", + "getArgs": [ + { + "key": "session_key", + "value": "{{object.rawId}}" + } + ] + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "F1 Session" + ] + } + }, + "timeframes": false, + "defaultShaping": { + "sort": { + "by": [ + [ + "position", + "asc" + ] + ] + } + }, + "metadata": [ + { + "name": "position", + "displayName": "Position", + "shape": "number", + "role": "value" + }, + { + "name": "driver_number", + "displayName": "Car Number", + "shape": "number", + "role": "label" + }, + { + "name": "driverName", + "displayName": "Driver", + "sourceId": "driver_number", + "sourceType": "F1 Driver", + "objectPropertyPath": "name" + }, + { + "name": "number_of_laps", + "displayName": "Laps", + "shape": "number" + }, + { + "name": "points", + "displayName": "Points", + "shape": "number" + }, + { + "name": "dnf", + "displayName": "DNF", + "shape": "boolean" + }, + { + "name": "dns", + "displayName": "DNS", + "shape": "boolean" + }, + { + "name": "dsq", + "displayName": "DSQ", + "shape": "boolean" + }, + { + "name": "gap_to_leader", + "visible": false + }, + { + "name": "gapToLeader", + "displayName": "Gap to Leader (s)", + "shape": [ + "number", + { + "decimalPlaces": 2 + } + ], + "computed": true, + "valueExpression": "{{ typeof $['gap_to_leader'] === 'number' ? $['gap_to_leader'] : null }}" + }, + { + "name": "duration", + "displayName": "Race Time", + "shape": "seconds" + }, + { + "name": "driver_number", + "sourceId": "driver_number", + "sourceType": "F1 Driver" + } + ] +} diff --git a/plugins/OpenF1/v1/dataStreams/sessions.json b/plugins/OpenF1/v1/dataStreams/sessions.json new file mode 100644 index 00000000..466cfac6 --- /dev/null +++ b/plugins/OpenF1/v1/dataStreams/sessions.json @@ -0,0 +1,28 @@ +{ + "name": "sessions", + "displayName": "Sessions", + "description": "Formula 1 sessions (practice, qualifying, sprint, race) within each meeting", + "tags": ["Session"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/sessions" + }, + "matches": "none", + "timeframes": false, + "visibility": { "type": "hidden" }, + "metadata": [ + { "name": "session_key", "displayName": "Session Key", "shape": "string", "role": "id" }, + { "name": "objectName", "displayName": "Name", "shape": "string", "role": "label", "computed": true, "valueExpression": "{{ $['circuit_short_name'] }} {{ $['year'] }} - {{ $['session_name'] }}" }, + { "name": "session_name", "displayName": "Session", "shape": "string" }, + { "name": "session_type", "displayName": "Session Type", "shape": "string" }, + { "name": "meeting_key", "displayName": "Meeting Key", "shape": "string" }, + { "name": "country_name", "displayName": "Country", "shape": "string" }, + { "name": "country_code", "displayName": "Country Code", "shape": "string" }, + { "name": "location", "displayName": "Location", "shape": "string" }, + { "name": "circuit_short_name", "displayName": "Circuit", "shape": "string" }, + { "name": "year", "displayName": "Year", "shape": "number" }, + { "name": "date_start", "displayName": "Start", "shape": "date" }, + { "name": "date_end", "displayName": "End", "shape": "date" } + ] +} diff --git a/plugins/OpenF1/v1/dataStreams/stints.json b/plugins/OpenF1/v1/dataStreams/stints.json new file mode 100644 index 00000000..a44fce93 --- /dev/null +++ b/plugins/OpenF1/v1/dataStreams/stints.json @@ -0,0 +1,83 @@ +{ + "name": "stints", + "displayName": "Stints", + "description": "Tyre stints for each driver in a session", + "tags": [ + "Session", + "Tyres" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/stints", + "getArgs": [ + { + "key": "session_key", + "value": "{{object.rawId}}" + } + ] + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "F1 Session" + ] + } + }, + "timeframes": false, + "defaultShaping": { + "sort": { + "by": [ + [ + "driver_number", + "asc" + ], + [ + "stint_number", + "asc" + ] + ] + } + }, + "metadata": [ + { + "name": "driver_number", + "displayName": "Driver Number", + "shape": "number", + "role": "label" + }, + { + "name": "driverName", + "displayName": "Driver", + "sourceId": "driver_number", + "sourceType": "F1 Driver", + "objectPropertyPath": "name" + }, + { + "name": "stint_number", + "displayName": "Stint", + "shape": "number" + }, + { + "name": "compound", + "displayName": "Compound", + "shape": "string" + }, + { + "name": "lap_start", + "displayName": "First Lap", + "shape": "number" + }, + { + "name": "lap_end", + "displayName": "Last Lap", + "shape": "number" + }, + { + "name": "tyre_age_at_start", + "displayName": "Tyre Age at Start (laps)", + "shape": "number" + } + ] +} diff --git a/plugins/OpenF1/v1/dataStreams/weather.json b/plugins/OpenF1/v1/dataStreams/weather.json new file mode 100644 index 00000000..bdfbe392 --- /dev/null +++ b/plugins/OpenF1/v1/dataStreams/weather.json @@ -0,0 +1,25 @@ +{ + "name": "weather", + "displayName": "Weather", + "description": "Weather readings recorded roughly once per minute during a session", + "tags": ["Session", "Weather"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/weather", + "getArgs": [{ "key": "session_key", "value": "{{object.rawId}}" }] + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["F1 Session"] } }, + "timeframes": false, + "defaultShaping": { "sort": { "by": [["date", "asc"]] } }, + "metadata": [ + { "name": "date", "displayName": "Time", "shape": "date", "role": "timestamp" }, + { "name": "air_temperature", "displayName": "Air Temp (°C)", "shape": ["number", { "decimalPlaces": 1 }], "role": "value" }, + { "name": "track_temperature", "displayName": "Track Temp (°C)", "shape": ["number", { "decimalPlaces": 1 }] }, + { "name": "humidity", "displayName": "Humidity (%)", "shape": "number" }, + { "name": "pressure", "displayName": "Pressure (mbar)", "shape": "number" }, + { "name": "wind_speed", "displayName": "Wind Speed (m/s)", "shape": "number" }, + { "name": "wind_direction", "displayName": "Wind Dir (°)", "shape": "number" }, + { "name": "rainfall", "displayName": "Rainfall", "shape": "number" } + ] +} diff --git a/plugins/OpenF1/v1/defaultContent/driver.dash.json b/plugins/OpenF1/v1/defaultContent/driver.dash.json new file mode 100644 index 00000000..fb9f5036 --- /dev/null +++ b/plugins/OpenF1/v1/defaultContent/driver.dash.json @@ -0,0 +1,104 @@ +{ + "name": "F1 Driver", + "schemaVersion": "1.5", + "variables": [ + "{{variables.[F1 Driver]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "a9d1414d-7738-41eb-873a-7d4d3d7aa78d", + "x": 0, + "y": 0, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Driver Details", + "description": "", + "timeframe": "none", + "variables": [ + "{{variables.[F1 Driver]}}" + ], + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[F1 Drivers]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[F1 Driver]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "columnOrder": [ + "name", + "name_acronym", + "team_name", + "team_colour", + "country_code", + "headshot_url" + ], + "hiddenColumns": [ + "id", + "link", + "links", + "label", + "sourceId", + "sourceType" + ] + } + } + } + } + }, + { + "i": "11118704-99cb-4394-aad1-a8e14dd09325", + "x": 2, + "y": 0, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Driver Photo", + "description": "", + "timeframe": "none", + "variables": [ + "{{variables.[F1 Driver]}}" + ], + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[F1 Drivers]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[F1 Driver]}}" + }, + "visualisation": { + "type": "data-stream-iframe", + "config": { + "data-stream-iframe": { + "title": "Driver", + "scale": 1, + "linkColumn": "headshot_url" + } + } + } + } + } + ] + } +} diff --git a/plugins/OpenF1/v1/defaultContent/manifest.json b/plugins/OpenF1/v1/defaultContent/manifest.json new file mode 100644 index 00000000..9398e0ff --- /dev/null +++ b/plugins/OpenF1/v1/defaultContent/manifest.json @@ -0,0 +1,8 @@ +{ + "items": [ + { "name": "overview", "type": "dashboard" }, + { "name": "meeting", "type": "dashboard" }, + { "name": "session", "type": "dashboard" }, + { "name": "driver", "type": "dashboard" } + ] +} diff --git a/plugins/OpenF1/v1/defaultContent/meeting.dash.json b/plugins/OpenF1/v1/defaultContent/meeting.dash.json new file mode 100644 index 00000000..b0faf707 --- /dev/null +++ b/plugins/OpenF1/v1/defaultContent/meeting.dash.json @@ -0,0 +1,97 @@ +{ + "name": "F1 Meeting", + "schemaVersion": "1.5", + "variables": ["{{variables.[F1 Meeting]}}"], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "fe7440ac-a11e-4f73-998a-34379af82dfc", + "x": 0, + "y": 0, + "w": 1, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Meeting Details", + "description": "", + "timeframe": "none", + "variables": ["{{variables.[F1 Meeting]}}"], + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[F1 Meetings]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[F1 Meeting]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "columnOrder": [ + "name", + "meeting_official_name", + "circuit_short_name", + "circuit_type", + "country_name", + "location", + "year", + "date_start", + "date_end" + ], + "hiddenColumns": ["id", "link", "links", "label", "sourceId", "sourceType", "meeting_name", "country_code"] + } + } + } + } + }, + { + "i": "a65964fa-5deb-4d80-bb3e-87a493ea9065", + "x": 1, + "y": 0, + "w": 3, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Sessions", + "description": "", + "timeframe": "none", + "variables": ["{{variables.[F1 Meeting]}}"], + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.meetingSessions}}", + "name": "meetingSessions", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["date_start", "asc"]] } + }, + "scope": { + "scope": "{{scopes.[F1 Meetings]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[F1 Meeting]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": ["session_name", "session_type", "date_start", "date_end"], + "hiddenColumns": ["session_key"] + } + } + } + } + } + ] + } +} diff --git a/plugins/OpenF1/v1/defaultContent/overview.dash.json b/plugins/OpenF1/v1/defaultContent/overview.dash.json new file mode 100644 index 00000000..e1ababd9 --- /dev/null +++ b/plugins/OpenF1/v1/defaultContent/overview.dash.json @@ -0,0 +1,106 @@ +{ + "name": "Overview", + "schemaVersion": "1.5", + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "18c23872-8188-4f7d-abe6-b2613dcfc58c", + "x": 0, + "y": 0, + "w": 4, + "h": 1, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/text", + "title": "", + "description": "", + "visualisation": { + "config": { + "content": "Standings for the latest available F1 season", + "fontSize": 16, + "align": "center", + "autoSize": true + } + } + } + }, + { + "i": "64c1a1ad-7ac7-4352-9a12-1fcf6b56067a", + "x": 0, + "y": 1, + "w": 2, + "h": 6, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Drivers' Championship", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.championshipDrivers}}", + "name": "championshipDrivers", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["position_current", "asc"]] } + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "position_current", + "driver_number", + "driverName", + "driverTeam", + "points_current", + "position_start", + "points_start" + ] + } + } + } + } + }, + { + "i": "8f130088-bc11-41a5-9b25-db40c621304b", + "x": 2, + "y": 1, + "w": 2, + "h": 6, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Constructors' Championship", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.championshipTeams}}", + "name": "championshipTeams", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["position_current", "asc"]] } + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": ["position_current", "team_name", "points_current", "position_start", "points_start"] + } + } + } + } + } + ] + } +} diff --git a/plugins/OpenF1/v1/defaultContent/scopes.json b/plugins/OpenF1/v1/defaultContent/scopes.json new file mode 100644 index 00000000..35bb0dd2 --- /dev/null +++ b/plugins/OpenF1/v1/defaultContent/scopes.json @@ -0,0 +1,38 @@ +[ + { + "name": "F1 Meetings", + "matches": { + "sourceType": { "type": "oneOf", "values": ["F1 Meeting"] } + }, + "variable": { + "name": "F1 Meeting", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "F1 Sessions", + "matches": { + "sourceType": { "type": "oneOf", "values": ["F1 Session"] } + }, + "variable": { + "name": "F1 Session", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "F1 Drivers", + "matches": { + "sourceType": { "type": "oneOf", "values": ["F1 Driver"] } + }, + "variable": { + "name": "F1 Driver", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + } +] diff --git a/plugins/OpenF1/v1/defaultContent/session.dash.json b/plugins/OpenF1/v1/defaultContent/session.dash.json new file mode 100644 index 00000000..f95691a6 --- /dev/null +++ b/plugins/OpenF1/v1/defaultContent/session.dash.json @@ -0,0 +1,372 @@ +{ + "name": "F1 Session", + "schemaVersion": "1.5", + "variables": ["{{variables.[F1 Session]}}"], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "ffd77b8e-c72a-471d-af02-944342109420", + "x": 0, + "y": 0, + "w": 2, + "h": 5, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Session Result", + "description": "", + "timeframe": "none", + "variables": ["{{variables.[F1 Session]}}"], + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.sessionResult}}", + "name": "sessionResult", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["position", "asc"]] } + }, + "scope": { + "scope": "{{scopes.[F1 Sessions]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[F1 Session]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "position", + "driver_number", + "driverName", + "number_of_laps", + "points", + "gapToLeader", + "duration", + "dnf", + "dns", + "dsq" + ], + "hiddenColumns": ["gap_to_leader"] + } + } + } + } + }, + { + "i": "c4dbec4b-94ad-416d-9e4a-f7a792de768c", + "x": 2, + "y": 0, + "w": 2, + "h": 5, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Session Drivers", + "description": "", + "timeframe": "none", + "variables": ["{{variables.[F1 Session]}}"], + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.sessionDrivers}}", + "name": "sessionDrivers", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["driver_number", "asc"]] } + }, + "scope": { + "scope": "{{scopes.[F1 Sessions]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[F1 Session]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": ["driver_number", "full_name", "name_acronym", "team_name", "country_code"], + "hiddenColumns": ["headshot_url", "team_colour"] + } + } + } + } + }, + { + "i": "851b75f1-6c17-49f4-9b50-62155c9a39f0", + "x": 0, + "y": 5, + "w": 4, + "h": 5, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Lap Times", + "description": "", + "timeframe": "none", + "variables": ["{{variables.[F1 Session]}}"], + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.laps}}", + "name": "laps", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["lap_number", "asc"]] } + }, + "scope": { + "scope": "{{scopes.[F1 Sessions]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[F1 Session]}}" + }, + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "lap_number", + "yAxisColumn": ["lap_duration"], + "seriesColumn": "driver_number", + "showLegend": true, + "legendPosition": "bottom", + "yAxisLabel": "Lap Time (s)", + "showYAxisLabel": true, + "showTrendLine": false + } + } + } + } + }, + { + "i": "ac06afd9-d49f-470c-a937-0da66474e8d9", + "x": 0, + "y": 10, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Tyre Stints", + "description": "", + "timeframe": "none", + "variables": ["{{variables.[F1 Session]}}"], + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.stints}}", + "name": "stints", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["driver_number", "asc"], ["stint_number", "asc"]] } + }, + "scope": { + "scope": "{{scopes.[F1 Sessions]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[F1 Session]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "driver_number", + "driverName", + "stint_number", + "compound", + "lap_start", + "lap_end", + "tyre_age_at_start" + ] + } + } + } + } + }, + { + "i": "0f267f07-0bde-4bc9-98dd-1df746f6cb15", + "x": 2, + "y": 10, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Pit Stops", + "description": "", + "timeframe": "none", + "variables": ["{{variables.[F1 Session]}}"], + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.pitStops}}", + "name": "pitStops", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["lap_number", "asc"]] } + }, + "scope": { + "scope": "{{scopes.[F1 Sessions]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[F1 Session]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "driver_number", + "driverName", + "lap_number", + "date", + "pit_duration", + "lane_duration", + "stop_duration" + ] + } + } + } + } + }, + { + "i": "2519a308-231e-4787-af8d-a6f3fae3a299", + "x": 0, + "y": 14, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Weather", + "description": "", + "timeframe": "none", + "variables": ["{{variables.[F1 Session]}}"], + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.weather}}", + "name": "weather", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["date", "asc"]] } + }, + "scope": { + "scope": "{{scopes.[F1 Sessions]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[F1 Session]}}" + }, + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "date", + "yAxisColumn": ["air_temperature", "track_temperature"], + "seriesColumn": "none", + "showLegend": true, + "legendPosition": "bottom", + "yAxisLabel": "Temperature (°C)", + "showYAxisLabel": true, + "showTrendLine": false + } + } + } + } + }, + { + "i": "35c96332-6abe-41af-921b-9951ea375320", + "x": 2, + "y": 14, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Race Control", + "description": "", + "timeframe": "none", + "variables": ["{{variables.[F1 Session]}}"], + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.raceControl}}", + "name": "raceControl", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["date", "asc"]] } + }, + "scope": { + "scope": "{{scopes.[F1 Sessions]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[F1 Session]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "date", + "category", + "flag", + "message", + "lap_number", + "driver_number", + "driverName" + ] + } + } + } + } + }, + { + "i": "f8453d47-c0aa-459a-98b5-5b1c11ee8f33", + "x": 0, + "y": 18, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Overtakes", + "description": "", + "timeframe": "none", + "variables": ["{{variables.[F1 Session]}}"], + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.overtakes}}", + "name": "overtakes", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["date", "asc"]] } + }, + "scope": { + "scope": "{{scopes.[F1 Sessions]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[F1 Session]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "date", + "overtaking_driver_number", + "overtakingDriver", + "overtaken_driver_number", + "overtakenDriver", + "position" + ] + } + } + } + } + } + ] + } +} diff --git a/plugins/OpenF1/v1/docs/README.md b/plugins/OpenF1/v1/docs/README.md new file mode 100644 index 00000000..9db2c752 --- /dev/null +++ b/plugins/OpenF1/v1/docs/README.md @@ -0,0 +1,48 @@ +# OpenF1 + +Formula 1 timing data from the free, open [OpenF1 API](https://openf1.org/). This plugin imports F1 race weekends (meetings), their sessions, and the current driver grid, and provides dashboards for session results, lap times, pit stops, tyre stints, weather, race control messages, overtakes, and championship standings. + +> OpenF1 is an unofficial, community-run project and is not associated with Formula 1, the FIA, or any related company. + +## What this plugin monitors + +- **Meetings** — each Grand Prix or testing weekend (e.g. "Singapore Grand Prix 2023"), with circuit, country, and dates. +- **Sessions** — each session within a meeting (Practice 1/2/3, Qualifying, Sprint, Race). +- **Drivers** — the current driver grid, with team, nationality, and headshot. + +Per session, the dashboards show the final classification, lap-by-lap times, pit stops, tyre stints, weather, race control flags/incidents, overtakes, and the driver lineup. A summary dashboard shows the current drivers' and constructors' championship standings, and each driver has a detail dashboard with their photo and team. + +## Prerequisites + +**None.** OpenF1's historical data (2023 onwards) is completely free and requires **no account, API key, or credentials**. Just add the plugin and it works. + +> Only OpenF1's *real-time* (live) data requires a paid subscription. This plugin uses the free historical endpoints, so no subscription is needed. + +## Configuration + +This plugin has **no configuration fields** — there is nothing to fill in. On setup, the plugin runs a quick connectivity check against the OpenF1 API to confirm it is reachable, then you are done. + +| Field | Description | Required | +| ----- | ----------- | -------- | +| _(none)_ | No credentials or settings are required. | — | + +## What gets indexed + +| Object type | Represents | +| ----------- | ---------- | +| **F1 Meeting** | A Grand Prix or testing weekend. Around 100 objects (2023–present). | +| **F1 Session** | A single session (Practice, Qualifying, Sprint, or Race) within a meeting. Around 490 objects. | +| **F1 Driver** | A driver on the current grid, keyed by car number. Around 20 objects. | + +Each session stores its parent meeting's key, so you can navigate from a session to its meeting. On the Session dashboard, the Session Result and Session Drivers tables link each driver through to their F1 Driver object. + +## Known limitations + +- **Rate limit:** OpenF1 allows a maximum of **3 requests per second**. Dashboards that open many tiles at once may occasionally see a tile retry. +- **Historical data only:** This plugin covers free historical data from **2023 onwards**. Live/real-time timing (intervals, positions during a race) is a paid OpenF1 feature and is not included. +- **High-frequency telemetry omitted:** Car telemetry (`/car_data`) and GPS location (`/location`), sampled at ~3.7 Hz, are intentionally not included — the data volume would exceed dashboard query limits. +- **Team radio omitted:** Radio clip data is sparse and its coverage decreased significantly from 2026 onwards. +- **Driver names reflect the current grid:** The API identifies drivers only by car number within each session, and the only shared key between the timing data and driver details is that number. This plugin indexes the **current grid** (the drivers in the latest session) as F1 Driver objects keyed by car number, and uses it to label the number with a driver name across the results, laps, pit stops, stints, race control, and championship tiles. Because the mapping reflects the latest session, a historical row shows the number's *current* driver — or no name if that number isn't on the current grid. For the exact driver behind each number in a given session, use that session's **Session Drivers** tile, which reads number → name, team, and colour directly from the session. +- **Driver team is the current team:** F1 Driver objects (and the Drivers' Championship tile) show each driver's *current* team. The team a driver raced for at the time of a historical session is available in that session's **Session Drivers** tile. +- **Starting grid unavailable:** The OpenF1 `/starting_grid` endpoint currently returns no data for any session, so it is not included. Final race positions are available from session results. +- **Unofficial data:** Data is sourced by the OpenF1 project and may contain gaps or inaccuracies, particularly for older or interrupted sessions. diff --git a/plugins/OpenF1/v1/icon.svg b/plugins/OpenF1/v1/icon.svg new file mode 100644 index 00000000..612f905b --- /dev/null +++ b/plugins/OpenF1/v1/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/OpenF1/v1/indexDefinitions/default.json b/plugins/OpenF1/v1/indexDefinitions/default.json new file mode 100644 index 00000000..f5403127 --- /dev/null +++ b/plugins/OpenF1/v1/indexDefinitions/default.json @@ -0,0 +1,65 @@ +{ + "steps": [ + { + "name": "meetings", + "dataStream": { "name": "meetings" }, + "timeframe": "none", + "objectMapping": { + "id": "meeting_key", + "name": "objectName", + "type": { "value": "F1 Meeting" }, + "properties": [ + "meeting_name", + "meeting_official_name", + "year", + "country_name", + "country_code", + "location", + "circuit_short_name", + "circuit_type", + "date_start", + "date_end" + ] + } + }, + { + "name": "sessions", + "dataStream": { "name": "sessions" }, + "timeframe": "none", + "objectMapping": { + "id": "session_key", + "name": "objectName", + "type": { "value": "F1 Session" }, + "properties": [ + "meeting_key", + "session_name", + "session_type", + "year", + "country_name", + "country_code", + "location", + "circuit_short_name", + "date_start", + "date_end" + ] + } + }, + { + "name": "drivers", + "dataStream": { "name": "drivers" }, + "timeframe": "none", + "objectMapping": { + "id": "driver_number", + "name": "full_name", + "type": { "value": "F1 Driver" }, + "properties": [ + "name_acronym", + "team_name", + "team_colour", + "country_code", + "headshot_url" + ] + } + } + ] +} diff --git a/plugins/OpenF1/v1/metadata.json b/plugins/OpenF1/v1/metadata.json new file mode 100644 index 00000000..e8c793cd --- /dev/null +++ b/plugins/OpenF1/v1/metadata.json @@ -0,0 +1,37 @@ +{ + "name": "openf1", + "displayName": "OpenF1", + "version": "1.1.0", + "author": { + "name": "@clarkd", + "type": "community" + }, + "description": "Formula 1 timing data from the free OpenF1 API: race weekends and sessions, with results, laps, pit stops, tyre stints, weather, race control, and championship standings.", + "category": "Fun", + "type": "hybrid", + "schemaVersion": "2.1", + "importNotSupported": false, + "restrictedToPlatforms": [], + "keywords": ["formula 1", "f1", "motorsport", "racing", "openf1", "grand prix"], + "objectTypes": ["F1 Meeting", "F1 Session", "F1 Driver"], + "links": [ + { + "category": "source", + "url": "https://github.com/squaredup/plugins/tree/main/plugins/OpenF1/v1", + "label": "Repository" + }, + { + "category": "documentation", + "url": "https://github.com/squaredup/plugins/blob/main/plugins/OpenF1/v1/docs/README.md", + "label": "Help adding this plugin" + } + ], + "base": { + "plugin": "WebAPI", + "majorVersion": "1", + "config": { + "authMode": "none", + "baseUrl": "https://api.openf1.org/v1" + } + } +} diff --git a/plugins/OpenF1/v1/ui.json b/plugins/OpenF1/v1/ui.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/plugins/OpenF1/v1/ui.json @@ -0,0 +1 @@ +[] From 7815426c69f6b348467673b7469ae0caf2bd2961 Mon Sep 17 00:00:00 2001 From: Dave Clarke Date: Mon, 6 Jul 2026 17:34:01 +0100 Subject: [PATCH 2/5] OpenF1: address PR review feedback - sessionResult: merge duplicate driver_number metadata into one entry (keeps Car Number label + F1 Driver drilldown) - sessionDrivers: merge duplicate full_name metadata into one entry (keeps Driver label + F1 Driver drilldown) - connectionCheck, sessions: session_key shape string -> number to match the numeric OpenF1 id and meetingSessions Co-Authored-By: Claude Opus 4.8 (1M context) --- .../v1/dataStreams/connectionCheck.json | 41 +++++++-- .../OpenF1/v1/dataStreams/sessionDrivers.json | 9 +- .../OpenF1/v1/dataStreams/sessionResult.json | 9 +- plugins/OpenF1/v1/dataStreams/sessions.json | 84 +++++++++++++++---- 4 files changed, 110 insertions(+), 33 deletions(-) diff --git a/plugins/OpenF1/v1/dataStreams/connectionCheck.json b/plugins/OpenF1/v1/dataStreams/connectionCheck.json index c2f03fd5..5ef78e97 100644 --- a/plugins/OpenF1/v1/dataStreams/connectionCheck.json +++ b/plugins/OpenF1/v1/dataStreams/connectionCheck.json @@ -2,20 +2,47 @@ "name": "connectionCheck", "displayName": "Connection Check", "description": "Most recent session, used to confirm the OpenF1 API is reachable", - "tags": ["Session"], + "tags": [ + "Session" + ], "baseDataSourceName": "httpRequestUnscoped", "config": { "httpMethod": "get", "endpointPath": "/sessions", - "getArgs": [{ "key": "session_key", "value": "latest" }] + "getArgs": [ + { + "key": "session_key", + "value": "latest" + } + ] }, "matches": "none", "timeframes": false, - "visibility": { "type": "hidden" }, + "visibility": { + "type": "hidden" + }, "metadata": [ - { "name": "session_key", "displayName": "Session Key", "shape": "string", "role": "id" }, - { "name": "session_name", "displayName": "Session", "shape": "string", "role": "label" }, - { "name": "country_name", "displayName": "Country", "shape": "string" }, - { "name": "year", "displayName": "Year", "shape": "number" } + { + "name": "session_key", + "displayName": "Session Key", + "shape": "number", + "role": "id" + }, + { + "name": "session_name", + "displayName": "Session", + "shape": "string", + "role": "label" + }, + { + "name": "country_name", + "displayName": "Country", + "shape": "string" + }, + { + "name": "year", + "displayName": "Year", + "shape": "number" + } ] } diff --git a/plugins/OpenF1/v1/dataStreams/sessionDrivers.json b/plugins/OpenF1/v1/dataStreams/sessionDrivers.json index 2e417af5..5bf9a236 100644 --- a/plugins/OpenF1/v1/dataStreams/sessionDrivers.json +++ b/plugins/OpenF1/v1/dataStreams/sessionDrivers.json @@ -47,7 +47,9 @@ "name": "full_name", "displayName": "Driver", "shape": "string", - "role": "label" + "role": "label", + "sourceId": "driver_number", + "sourceType": "F1 Driver" }, { "name": "name_acronym", @@ -74,11 +76,6 @@ "displayName": "Team Colour", "shape": "string", "visible": false - }, - { - "name": "full_name", - "sourceId": "driver_number", - "sourceType": "F1 Driver" } ] } diff --git a/plugins/OpenF1/v1/dataStreams/sessionResult.json b/plugins/OpenF1/v1/dataStreams/sessionResult.json index f5d58405..7d539965 100644 --- a/plugins/OpenF1/v1/dataStreams/sessionResult.json +++ b/plugins/OpenF1/v1/dataStreams/sessionResult.json @@ -47,7 +47,9 @@ "name": "driver_number", "displayName": "Car Number", "shape": "number", - "role": "label" + "role": "label", + "sourceId": "driver_number", + "sourceType": "F1 Driver" }, { "name": "driverName", @@ -101,11 +103,6 @@ "name": "duration", "displayName": "Race Time", "shape": "seconds" - }, - { - "name": "driver_number", - "sourceId": "driver_number", - "sourceType": "F1 Driver" } ] } diff --git a/plugins/OpenF1/v1/dataStreams/sessions.json b/plugins/OpenF1/v1/dataStreams/sessions.json index 466cfac6..d60b6058 100644 --- a/plugins/OpenF1/v1/dataStreams/sessions.json +++ b/plugins/OpenF1/v1/dataStreams/sessions.json @@ -2,7 +2,9 @@ "name": "sessions", "displayName": "Sessions", "description": "Formula 1 sessions (practice, qualifying, sprint, race) within each meeting", - "tags": ["Session"], + "tags": [ + "Session" + ], "baseDataSourceName": "httpRequestUnscoped", "config": { "httpMethod": "get", @@ -10,19 +12,73 @@ }, "matches": "none", "timeframes": false, - "visibility": { "type": "hidden" }, + "visibility": { + "type": "hidden" + }, "metadata": [ - { "name": "session_key", "displayName": "Session Key", "shape": "string", "role": "id" }, - { "name": "objectName", "displayName": "Name", "shape": "string", "role": "label", "computed": true, "valueExpression": "{{ $['circuit_short_name'] }} {{ $['year'] }} - {{ $['session_name'] }}" }, - { "name": "session_name", "displayName": "Session", "shape": "string" }, - { "name": "session_type", "displayName": "Session Type", "shape": "string" }, - { "name": "meeting_key", "displayName": "Meeting Key", "shape": "string" }, - { "name": "country_name", "displayName": "Country", "shape": "string" }, - { "name": "country_code", "displayName": "Country Code", "shape": "string" }, - { "name": "location", "displayName": "Location", "shape": "string" }, - { "name": "circuit_short_name", "displayName": "Circuit", "shape": "string" }, - { "name": "year", "displayName": "Year", "shape": "number" }, - { "name": "date_start", "displayName": "Start", "shape": "date" }, - { "name": "date_end", "displayName": "End", "shape": "date" } + { + "name": "session_key", + "displayName": "Session Key", + "shape": "number", + "role": "id" + }, + { + "name": "objectName", + "displayName": "Name", + "shape": "string", + "role": "label", + "computed": true, + "valueExpression": "{{ $['circuit_short_name'] }} {{ $['year'] }} - {{ $['session_name'] }}" + }, + { + "name": "session_name", + "displayName": "Session", + "shape": "string" + }, + { + "name": "session_type", + "displayName": "Session Type", + "shape": "string" + }, + { + "name": "meeting_key", + "displayName": "Meeting Key", + "shape": "string" + }, + { + "name": "country_name", + "displayName": "Country", + "shape": "string" + }, + { + "name": "country_code", + "displayName": "Country Code", + "shape": "string" + }, + { + "name": "location", + "displayName": "Location", + "shape": "string" + }, + { + "name": "circuit_short_name", + "displayName": "Circuit", + "shape": "string" + }, + { + "name": "year", + "displayName": "Year", + "shape": "number" + }, + { + "name": "date_start", + "displayName": "Start", + "shape": "date" + }, + { + "name": "date_end", + "displayName": "End", + "shape": "date" + } ] } From 96021d1ff6b21f677353d07bd3f49a4e8e2224a7 Mon Sep 17 00:00:00 2001 From: Dave Clarke Date: Mon, 6 Jul 2026 17:36:32 +0100 Subject: [PATCH 3/5] OpenF1: add driver drilldown to championshipDrivers standings The Driver # column now links through to the F1 Driver object, consistent with the session result and drivers tables. (championshipTeams has no indexed object to drill to.) Co-Authored-By: Claude Opus 4.8 (1M context) --- plugins/OpenF1/v1/dataStreams/championshipDrivers.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/OpenF1/v1/dataStreams/championshipDrivers.json b/plugins/OpenF1/v1/dataStreams/championshipDrivers.json index e0d44195..ad2a5256 100644 --- a/plugins/OpenF1/v1/dataStreams/championshipDrivers.json +++ b/plugins/OpenF1/v1/dataStreams/championshipDrivers.json @@ -40,7 +40,9 @@ "name": "driver_number", "displayName": "Driver #", "shape": "number", - "role": "label" + "role": "label", + "sourceId": "driver_number", + "sourceType": "F1 Driver" }, { "name": "driverName", From 040135f70a89b0f7c584ec66787e2c53ad42466a Mon Sep 17 00:00:00 2001 From: Dave Clarke Date: Mon, 6 Jul 2026 17:38:55 +0100 Subject: [PATCH 4/5] OpenF1: hide prev position/points on Overview championship tables Co-Authored-By: Claude Opus 4.8 (1M context) --- .../v1/defaultContent/overview.dash.json | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/plugins/OpenF1/v1/defaultContent/overview.dash.json b/plugins/OpenF1/v1/defaultContent/overview.dash.json index e1ababd9..abefd784 100644 --- a/plugins/OpenF1/v1/defaultContent/overview.dash.json +++ b/plugins/OpenF1/v1/defaultContent/overview.dash.json @@ -43,12 +43,21 @@ "title": "Drivers' Championship", "description": "", "timeframe": "none", - "activePluginConfigIds": ["{{configId}}"], + "activePluginConfigIds": [ + "{{configId}}" + ], "dataStream": { "id": "{{dataStreams.championshipDrivers}}", "name": "championshipDrivers", "pluginConfigId": "{{configId}}", - "sort": { "by": [["position_current", "asc"]] } + "sort": { + "by": [ + [ + "position_current", + "asc" + ] + ] + } }, "visualisation": { "type": "data-stream-table", @@ -63,6 +72,10 @@ "points_current", "position_start", "points_start" + ], + "hiddenColumns": [ + "position_start", + "points_start" ] } } @@ -83,19 +96,38 @@ "title": "Constructors' Championship", "description": "", "timeframe": "none", - "activePluginConfigIds": ["{{configId}}"], + "activePluginConfigIds": [ + "{{configId}}" + ], "dataStream": { "id": "{{dataStreams.championshipTeams}}", "name": "championshipTeams", "pluginConfigId": "{{configId}}", - "sort": { "by": [["position_current", "asc"]] } + "sort": { + "by": [ + [ + "position_current", + "asc" + ] + ] + } }, "visualisation": { "type": "data-stream-table", "config": { "data-stream-table": { "transpose": false, - "columnOrder": ["position_current", "team_name", "points_current", "position_start", "points_start"] + "columnOrder": [ + "position_current", + "team_name", + "points_current", + "position_start", + "points_start" + ], + "hiddenColumns": [ + "position_start", + "points_start" + ] } } } From 4ddf31dad8867c7d749653135861827b3f4b0219 Mon Sep 17 00:00:00 2001 From: Dave Clarke Date: Mon, 6 Jul 2026 17:43:17 +0100 Subject: [PATCH 5/5] OpenF1: stop the Team column linking to the driver in standings Drop sourceType from championshipDrivers.driverTeam so it shows the team name as plain text (resolved via the driver_number lookup) instead of drilling to the F1 Driver, matching the AutoTask objectPropertyPath pattern. Co-Authored-By: Claude Opus 4.8 (1M context) --- plugins/OpenF1/v1/dataStreams/championshipDrivers.json | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/OpenF1/v1/dataStreams/championshipDrivers.json b/plugins/OpenF1/v1/dataStreams/championshipDrivers.json index ad2a5256..fa10f78c 100644 --- a/plugins/OpenF1/v1/dataStreams/championshipDrivers.json +++ b/plugins/OpenF1/v1/dataStreams/championshipDrivers.json @@ -55,7 +55,6 @@ "name": "driverTeam", "displayName": "Team", "sourceId": "driver_number", - "sourceType": "F1 Driver", "objectPropertyPath": "team_name" }, {