Skip to content

Commit

Permalink
Merge pull request #61 from dxw/80-update-forecast-predictions-on-tab…
Browse files Browse the repository at this point in the history
…-click

80 update forecast predictions on tab click
  • Loading branch information
patrickjfl authored Oct 22, 2024
2 parents afa2b22 + e3bfb7e commit e02c7cc
Show file tree
Hide file tree
Showing 16 changed files with 334 additions and 77 deletions.
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ RUN \
apt-get update && \
apt-get install -y \
shellcheck \
chromium-driver \
yarn

RUN yarn build:css && yarn build
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ end

group :test do
gem "capybara", ">= 2.15"
gem "cuprite"
gem "database_cleaner"
gem "launchy"
gem "selenium-webdriver"
Expand Down
9 changes: 9 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ GEM
cssbundling-rails (1.4.1)
railties (>= 6.0.0)
csv (3.3.0)
cuprite (0.15.1)
capybara (~> 3.0)
ferrum (~> 0.15.0)
database_cleaner (2.0.2)
database_cleaner-active_record (>= 2, < 3)
database_cleaner-active_record (2.2.0)
Expand All @@ -143,6 +146,11 @@ GEM
railties (>= 5.0.0)
faker (3.5.1)
i18n (>= 1.8.11, < 2)
ferrum (0.15)
addressable (~> 2.5)
concurrent-ruby (~> 1.1)
webrick (~> 1.7)
websocket-driver (~> 0.7)
ffi (1.16.3)
globalid (1.2.1)
activesupport (>= 6.1)
Expand Down Expand Up @@ -459,6 +467,7 @@ DEPENDENCIES
capybara (>= 2.15)
climate_control
cssbundling-rails (~> 1.4)
cuprite
database_cleaner
dotenv-rails
factory_bot_rails
Expand Down
26 changes: 26 additions & 0 deletions app/controllers/styled_forecasts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,30 @@ def show
@forecasts = CercApiClient
.forecasts_for(params.fetch("zone", "Southwark"))
end

def update
forecasts = CercApiClient
.forecasts_for(params.fetch("zone", "Southwark"))

day_forecast = forecast_for_day(params.fetch("day"), forecasts)

render turbo_stream: turbo_stream.replace(
"day_predictions",
partial: "predictions",
locals: {forecast: day_forecast}
)
end

def forecast_for_day(day, forecasts)
case day
when "today"
forecasts.first
when "tomorrow"
forecasts.second
when "day_after_tomorrow"
forecasts.third
else
raise ArgumentError, "Invalid day: #{day}"
end
end
end
4 changes: 2 additions & 2 deletions app/models/uv_prediction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ def initialize(value)
end

def name
"Ultravoilet rays (UV)"
"Ultraviolet rays (UV)"
end

def guidance
I18n.t("prediction.guidance.uv.#{daqi_level}")
I18n.t("prediction.guidance.ultraviolet_rays_uv.#{daqi_level}")
end

# :nocov:
Expand Down
34 changes: 18 additions & 16 deletions app/views/styled_forecasts/_day_tab.html.erb
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
<div class="tab py-4 flex-1 today mx-2 px-1 text-center daqi-low border-b-0 border-l-2 border-r-2 border-t-2 border-gray-300" data-date="<%= forecast.date %>" >
<div class="day">
<%= forecast.date == Date.today ? 'Today' : forecast.date.strftime('%A') %>
</div>
<div class="date">
<%= forecast.date.strftime("%d %B") %>
</div>
<div class="daqi-marker text-4xl py-2 text-green-600">
</div>
<div class="daqi-label text-base">
<%= forecast.air_pollution.label.capitalize %>
</div>
<div class="daqi-value font-normal">
Index <%= forecast.air_pollution.value %>/10
</div>
<div class="tab py-4 flex-1 <%= day %> mx-2 px-1 text-center daqi-low border-b-0 border-l-2 border-r-2 border-t-2 border-gray-300" data-date="<%= forecast.date %>" >
<%= link_to update_styled_forecast_path(day: day), data: { turbo_frame: "day_predictions" } do %>
<div class="day">
<%= forecast.date == Date.today ? 'Today' : forecast.date.strftime('%A') %>
</div>
<div class="date">
<%= forecast.date.strftime("%d %B") %>
</div>
<div class="daqi-marker text-4xl py-2 text-green-600">
</div>
<div class="daqi-label text-base">
<%= forecast.air_pollution.label.capitalize %>
</div>
<div class="daqi-value font-normal">
Index <%= forecast.air_pollution.value %>/10
</div>
<% end %>
</div>
8 changes: 4 additions & 4 deletions app/views/styled_forecasts/_forecast_tabs.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
<header class="text-xl px-4 font-bold">
Air pollution
</header>
<div class="tabs flex flex-row items-stretch mt-4 m-1 text-xs font-bold ">
<%= render "day_tab", forecast: @forecasts.first %>
<%= render "day_tab", forecast: @forecasts.second %>
<%= render "day_tab", forecast: @forecasts.third %>
<div class="tabs flex flex-row items-stretch mt-4 m-1 text-xs font-bold" data-turbo-prefetch="false">
<%= render "day_tab", forecast: @forecasts.first, day: :today %>
<%= render "day_tab", forecast: @forecasts.second, day: :tomorrow %>
<%= render "day_tab", forecast: @forecasts.third, day: :day_after_tomorrow %>
</div>
<%= render partial: "map_selector" %>
<div id="map" class="map w-full bg-green-200 h-80">
Expand Down
12 changes: 7 additions & 5 deletions app/views/styled_forecasts/_predictions.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<dl class="predictions p-4">
<%= render(PredictionComponent.new(prediction: @forecasts.first.uv)) %>
<%= render(PredictionComponent.new(prediction: @forecasts.first.pollen)) %>
<%= render "temperature_prediction", prediction: @forecasts.first.temperature %>
</dl>
<%= turbo_frame_tag "day_predictions" do %>
<dl class="predictions p-4">
<%= render(PredictionComponent.new(prediction: forecast.uv)) %>
<%= render(PredictionComponent.new(prediction: forecast.pollen)) %>
<%= render "temperature_prediction", prediction: forecast.temperature %>
</dl>
<% end %>
2 changes: 1 addition & 1 deletion app/views/styled_forecasts/show.html.erb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<%= render partial: "styled_forecasts/location" %>
<%= render partial: "styled_forecasts/forecast_tabs", forecasts: @forecasts %>
<%= render partial: "styled_forecasts/predictions" %>
<%= render partial: "styled_forecasts/predictions", locals: { forecast: @forecasts.first } %>
<%= render partial: "sharing" %>
<%= render partial: "learning" %>
<%= render partial: "subscribe" %>
2 changes: 1 addition & 1 deletion config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ en:
hello: "Hello world"
prediction:
guidance:
uv:
ultraviolet_rays_uv:
low: "No action required. You can safely stay outside."
moderate:
"Protection required. Seek shade during midday hours, cover up and
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

get :forecast, to: "forecasts#show"
get :styled_forecast, to: "styled_forecasts#show"
get :update_styled_forecast, to: "styled_forecasts#update"

# If the CANONICAL_HOSTNAME env var is present, and the request doesn't come from that
# hostname, redirect us to the canonical hostname with the path and query string present
Expand Down
186 changes: 147 additions & 39 deletions spec/controllers/styled_forecasts_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,53 +7,90 @@
ClimateControl.modify(env_vars) { example.run }
end

describe "GET :show" do
let(:forecast_from_api) do
{
"forecastdate" => "01-10-2024 14:40",
"timestamp" => 1727793654317.342,
"zones" => [
{
"forecasts" => [
{
"NO2" => 1,
"O3" => 2,
"PM10" => 1,
"PM2.5" => 1,
"forecast_date" => "2024-10-01",
"non_pollution_version" => nil,
"pollen" => -999,
"pollution_version" => 202410011407,
"rain_am" => 1.31,
"rain_pm" => 3.01,
"temp_max" => 14.0,
"temp_min" => 10.4,
"total" => 2,
"total_status" => "LOW",
"uv" => 1,
"wind_am" => 5.3,
"wind_pm" => 6.0
}
],
"zone_id" => 14,
"zone_name" => "Haringey",
"zone_type" => 1
}
]
}
end
let(:forecast_from_api) do
{
"forecastdate" => "01-10-2024 14:40",
"timestamp" => 1727793654317.342,
"zones" => [
{
"forecasts" => [
{
"NO2" => 1,
"O3" => 2,
"PM10" => 1,
"PM2.5" => 1,
"forecast_date" => "2024-10-01",
"non_pollution_version" => nil,
"pollen" => -999,
"pollution_version" => 202410011407,
"rain_am" => 1.31,
"rain_pm" => 3.01,
"temp_max" => 14.0,
"temp_min" => 10.4,
"total" => 2,
"total_status" => "LOW",
"uv" => 1,
"wind_am" => 5.3,
"wind_pm" => 6.0
},
{
"NO2" => 1,
"O3" => 2,
"PM10" => 1,
"PM2.5" => 1,
"forecast_date" => "2024-10-02",
"non_pollution_version" => nil,
"pollen" => -999,
"pollution_version" => 202410011407,
"rain_am" => 1.31,
"rain_pm" => 3.01,
"temp_max" => 14.0,
"temp_min" => 10.4,
"total" => 2,
"total_status" => "LOW",
"uv" => 1,
"wind_am" => 5.3,
"wind_pm" => 6.0
},
{
"NO2" => 1,
"O3" => 2,
"PM10" => 1,
"PM2.5" => 1,
"forecast_date" => "2024-10-03",
"non_pollution_version" => nil,
"pollen" => -999,
"pollution_version" => 202410011407,
"rain_am" => 1.31,
"rain_pm" => 3.01,
"temp_max" => 14.0,
"temp_min" => 10.4,
"total" => 2,
"total_status" => "LOW",
"uv" => 1,
"wind_am" => 5.3,
"wind_pm" => 6.0
}
],
"zone_id" => 14,
"zone_name" => "Haringey",
"zone_type" => 1
}
]
}
end

let(:forecasts) do
ForecastFactory.build(forecast_from_api)
end
let(:forecasts) do
ForecastFactory.build(forecast_from_api)
end

describe "GET :show" do
it "obtains forecasts for the default zone (Southwark) from the CercApiClient" do
allow(CercApiClient).to receive(:forecasts_for).and_return(forecasts)

get :show

expect(CercApiClient).to have_received(:forecasts_for).with("Southwark")
expect(response).to render_template("show")
end

it "renders the _show_ template" do
Expand All @@ -64,4 +101,75 @@
expect(response).to render_template("show")
end
end

describe "GET :update" do
let(:forecasts) do
ForecastFactory.build(forecast_from_api)
end

let(:tag_builder) do
instance_double(Turbo::Streams::TagBuilder, replace: true)
end

before do
allow(Turbo::Streams::TagBuilder).to receive(:new).and_return(tag_builder)
end

context "when a recognised _day_ parameter is received" do
describe "when the day is _today_" do
it "passes the first forecast to the view" do
allow(CercApiClient).to receive(:forecasts_for).and_return(forecasts)

get :update, params: {day: :today}

expect(CercApiClient).to have_received(:forecasts_for).with("Southwark")
expect(tag_builder).to have_received(:replace).with(
"day_predictions",
partial: "predictions",
locals: {forecast: forecasts.first}
)
end
end

describe "when the day is _tomorrow_" do
it "passes the second forecast to the view" do
allow(CercApiClient).to receive(:forecasts_for).and_return(forecasts)

get :update, params: {day: :tomorrow}

expect(CercApiClient).to have_received(:forecasts_for).with("Southwark")
expect(tag_builder).to have_received(:replace).with(
"day_predictions",
partial: "predictions",
locals: {forecast: forecasts.second}
)
end
end

describe "when the day is _day_after_tomorrow_" do
it "passes the third forecast to the view" do
allow(CercApiClient).to receive(:forecasts_for).and_return(forecasts)

get :update, params: {day: :day_after_tomorrow}

expect(CercApiClient).to have_received(:forecasts_for).with("Southwark")
expect(tag_builder).to have_received(:replace).with(
"day_predictions",
partial: "predictions",
locals: {forecast: forecasts.third}
)
end
end
end

context "when an unrecognised _day_ parameter is received" do
it "raises a helpful error" do
allow(CercApiClient).to receive(:forecasts_for).and_return(forecasts)

expect {
get :update, params: {day: :yesterday}
}.to raise_error(ArgumentError, "Invalid day: yesterday")
end
end
end
end
Loading

0 comments on commit e02c7cc

Please sign in to comment.