Skip to content

Commit

Permalink
registration of legacy device
Browse files Browse the repository at this point in the history
  • Loading branch information
timcowlishaw committed Jan 15, 2025
1 parent c65d35a commit 1167331
Show file tree
Hide file tree
Showing 18 changed files with 265 additions and 19 deletions.
Binary file added app/assets/images/sckit_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/assets/images/sckit_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/assets/images/sckit_2.png~
Binary file not shown.
18 changes: 18 additions & 0 deletions app/assets/stylesheets/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ input[type="file"] {
margin-left: 1rem;
}

.form-text {
margin-left: 1rem;
}

.form-check-label {
display: inline;
}
Expand Down Expand Up @@ -208,3 +212,17 @@ input[type="file"] {
height: 1px;
overflow: hidden;
}

.contain-image {
width: 100%;
object-fit: contain;
aspect-ratio: 1;
}

a.subtle-link {
color: $black;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
39 changes: 39 additions & 0 deletions app/controllers/ui/devices_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,43 @@ def download_confirm
redirect_to ui_device_path(@device.id)
end

def register
unless current_user
flash[:alert] = I18n.t(:register_device_forbidden)
redirect_to login_path
return
end
@title = I18n.t(:register_device_title)
end

def new
unless current_user
flash[:alert] = I18n.t(:register_device_forbidden)
redirect_to login_path
return
end
@title = I18n.t(:new_device_title)
@device = Device.new(owner: current_user)
end

def create
unless current_user
flash[:alert] = I18n.t(:register_device_forbidden)
redirect_to login_path
return
end
@device = Device.new(device_params)
@device.owner = current_user
if @device.valid?
@device.save
flash[:success] = I18n.t(:new_device_success)
redirect_to ui_device_path(@device.id)
else
flash[:alert] = I18n.t(:new_device_failure)
render :new, status: :unprocessable_entity
end
end

private

def device_params
Expand All @@ -95,6 +132,8 @@ def device_params
:enable_forwarding,
:notify_low_battery,
:notify_stopped_publishing,
:hardware_version_override,
:mac_address,
{ :tag_ids => [] },
{ :postprocessing_attributes => :hardware_url },
)
Expand Down
1 change: 1 addition & 0 deletions app/models/device.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
class Device < ActiveRecord::Base

EXPOSURE_VALUES = %w{indoor outdoor}
HARDWARE_VERSION_OVERRIDE_VALUES = %w{1 1.1}

default_scope { with_active_state }

Expand Down
4 changes: 4 additions & 0 deletions app/policies/user_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,8 @@ def update_password?
def show_secrets?
user.try(:is_admin?) || user == record
end

def register_device?
user == record
end
end
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
<div>
<div class="mb-5">
<h2 class="mb-3"><%= t(:device_form_details_subhead) %></h2>
<%= form.text_field :name %>
<%= form.text_field :description %>
<%= form.select :exposure, options_for_select(Device::EXPOSURE_VALUES.map { |v| [v.capitalize, v]}, device.exposure) %>
</div>
<div class="mt-5">

<% if device.mac_address || local_assigns[:include_hardware_info] %>
<div class="mb-5">
<h2 class="mb-0"><%= t(:device_form_hardware_info_subhead) %></h2>
<p class="mb-3"> <%= t(:device_form_hardware_info_blurb_html) %></p>
<%= form.select :hardware_version_override,
options_for_select(
Device::HARDWARE_VERSION_OVERRIDE_VALUES.map { |v| [t(:"device_form_hardware_version_override_option_#{v.gsub(".", "_")}"), v]},
device.hardware_version_override
),
required: true, label: t(:device_form_hardware_version_override_label) %>
<%= form.text_field :mac_address, required: true, label: t(:device_form_mac_address_label), pattern: "^([0-9A-Fa-f]{2}[:\\-]){5}([0-9A-Fa-f]{2})$", help: t(:device_form_mac_address_help) %>
</div>
<% end %>

<div class="mb-5">
<h2 class="mb-0" ><%= t(:device_form_location_subhead) %></h2>
<p class="mb-3"> <%= t(:device_form_location_blurb) %></p>
<%= form.hidden_field :latitude %>
<%= form.hidden_field :longitude %>
<%= render partial: "ui/shared/map_location_picker", locals: { latitude_input_id: "device_latitude", longitude_input_id: "device_longitude"} %>
</div>
<div class="mt-5">
<div class="mb-5">
<h2 class="mb-0"><%= t(:device_form_open_data_subhead) %></h2>
<p class="mb-3"> <%= t(:device_form_open_data_blurb) %></p>
<%= form.check_box :is_private, label: t(:device_form_is_private_label) %>
Expand All @@ -20,28 +35,21 @@
<%= form.check_box :enable_forwarding, label: t(:device_form_enable_forwarding_label) %>
<% end %>
</div>
<div class="mt-5">
<div class="mb-5">
<h2 class="mb-0"><%= t(:device_form_notifications_subhead) %></h2>
<p class="mb-3"> <%= t(:device_form_notifications_blurb) %></p>
<%= form.check_box :notify_low_battery, label: t(:device_form_notify_low_battery_label) %>
<%= form.check_box :notify_stopped_publishing, label: t(:device_form_notify_stopped_publishing_label) %>
</div>
<div class="mt-5">
<div class="mb-5">
<h2 class="mb-0"><%= t(:device_form_tags_subhead) %></h2>
<p class="mb-3"> <%= t(:device_form_tags_blurb) %></p>
<%= form.select :tag_ids, options_from_collection_for_select(Tag.all, :id, :name, { selected: device.tag_ids }), { label: t(:device_form_tags_label)}, { multiple: true, class: "tag-select" } %>
</div>
<div class="mt-5">
<div>
<h2 class="mb-0"><%= t(:device_form_postprocessing_subhead) %></h2>
<p class="mb-3"> <%= t(:device_form_postprocessing_blurb) %></p>
<p class="mb-3"> <%= t(:device_form_postprocessing_blurb_html) %></p>
<%= form.fields_for :postprocessing, device.postprocessing || Postprocessing.new do |fp| %>
<%= fp.text_field :hardware_url, label: t(:device_form_hardware_url_label) %>
<% end %>
</div>
<div class="mt-5">
<%= form.primary t(:edit_user_submit), class: "btn btn-primary w-100 w-md-auto" %>
</div>
<% if authorize? device, :destroy? %>
<h2 class="mt-5 mb-3"><%= t(:device_form_other_actions_subhead) %></h2>
<div><%= link_to t(:device_form_delete_device_submit), delete_ui_device_path(device.id), class: "btn btn-danger w-100 w-md-auto" %></div>
<% end %>
9 changes: 8 additions & 1 deletion app/views/ui/devices/edit.html.erb
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
<%= bootstrap_form_for @device, url: ui_device_path(@device) do |form| %>
<%= render partial: "form", locals: { device: @device, form: form } %>
<%= render partial: "fields", locals: { device: @device, form: form } %>
<div class="mt-5">
<%= form.primary t(:edit_device_submit), class: "btn btn-primary w-100 w-md-auto" %>
</div>
<% end %>
<% if authorize? @device, :destroy? %>
<h2 class="mt-5 mb-3"><%= t(:edit_device_other_actions_subhead) %></h2>
<div><%= link_to t(:edit_device_delete_device_submit), delete_ui_device_path(@device.id), class: "btn btn-danger w-100 w-md-auto" %></div>
<% end %>
6 changes: 6 additions & 0 deletions app/views/ui/devices/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<%= bootstrap_form_for @device, url: ui_devices_path do |form| %>
<%= render partial: "fields", locals: { device: @device, form: form, include_hardware_info: true } %>
<div class="mt-5">
<%= form.primary t(:new_device_submit), class: "btn btn-primary w-100 w-md-auto" %>
</div>
<% end %>
18 changes: 18 additions & 0 deletions app/views/ui/devices/register.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<p><%= t(:register_device_blurb) %></p>
<div class="row">
<div class="col-12 col-md-6 col-lg-3 offset-lg-3">
<h2><%= t(:register_device_v1_subheading) %></h2>
<%= link_to new_ui_device_path, class: "subtle-link" do %>
<%= image_tag("sckit_1.png", alt: t(:register_device_v1_subheading), class: "contain-image") %>
<p class="text-center"><%= t(:register_device_v1_description_html) %></p>
<% end %>
</div>
<div class="col-12 col-md-6 col-lg-3">
<h2><%= t(:register_device_v2_subheading) %></h2>
<%= link_to t(:register_device_v2_registration_link), class: "subtle-link" do %>
<%= image_tag("sckit_2.png", alt: t(:register_device_v2_subheading), class: "contain-image") %>
<p class="text-center"><%= t(:register_device_v2_description_html) %></p>
<% end %>
</div>
</div>

3 changes: 3 additions & 0 deletions app/views/ui/users/_actions.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
<% if authorize? user, :show_secrets? %>
<%= link_to(t(:show_user_secrets_cta, owner: possessive(user, current_user)), secrets_ui_user_path(user), class: "btn btn-dark me-md-2 mt-3 mt-md-0 w-100 w-md-auto") %>
<% end %>
<% if authorize? user, :register_device? %>
<%= link_to(t(:show_user_register_cta), register_ui_devices_path, class: "btn btn-dark me-md-2 mt-3 mt-md-0 w-100 w-md-auto") %>
<% end %>
<% if current_user == user %>
<%= link_to(t(:show_user_log_out_cta), logout_path, class: "btn btn-dark mt-3 mt-lg-0 w-100 w-md-auto") %>
<% end %>
Expand Down
6 changes: 6 additions & 0 deletions config/locales/controllers/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,9 @@ en:
download_device_forbidden: "You are not allowed to download data for that kit!"
download_device_success: "Your CSV download has been requested, you'll shortly receive an email with a download link!"
download_device_requested_too_soon: "Sorry, requests for CSV downloads are rate-limited. Please wait before requesting another download."
register_device_forbidden: "You need to be logged in to register a new kit!"
register_device_title: "Register a new kit"
new_device_title: "Register a new legacy kit"
new_device_success: "The kit has been registered!"
new_device_failure: "Some errors prevented us from registering your device. Please check below and try again!"

23 changes: 19 additions & 4 deletions config/locales/views/devices/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@ en:
show_device_download_cta: "Download data as CSV"
device_meta_last_reading_at: "Last reading %{time} ago"
device_meta_no_readings_message: "No readings received yet!"
device_form_hardware_info_subhead: "Hardware information"
device_form_hardware_info_blurb_html: "In order to connect your kit to the Smart Citizen platform, we need some information to connect your kit to your computer. If this is your first time, maybe you would like to follow the <a target='_blank' href='https://docs.smartcitizen.me/Components/legacy/?h=serial+way#manual-set-up-the-serial-way'>startup guide</a>. You can find your kit's MAC address using the <a target='_blank' href='https://docs.smartcitizen.me/Guides/getting%20started/Using%20the%20Shell/'>onboard shell utility</a>."
device_form_hardware_version_override_label: "Hardware version"
device_form_hardware_version_override_option_1: "Smart Citizen Kit 1.0"
device_form_hardware_version_override_option_1_1: "Smart Citizen Kit 1.1"
device_form_mac_address_label: "MAC address"
device_form_mac_address_help: "This will be six pairs of letters and numbers separated by colons, for example: 80:e7:a5:47:09:da."
device_form_details_subhead: "Basic information"
device_form_location_subhead: "Location"
device_form_location_blurb: "You can adjust the location by dragging the marker on the map."
device_form_open_data_subhead: "Open data"
device_form_open_data_blurb: "Manage how others access your data: Sometimes, your kits might be collecting sensitive personal data (i.e. your exact location or by GPS using in your bike). Check the box in case you want to prevent others from accesssing your data. You can also choose to blur the location, or enable MQTT forwarding."
device_form_open_data_blurb: "Manage how others access your data: Sometimes, your kits might be collecting sensitive personal data (for instance, your precise location). Check the box in case you want to prevent others from accesssing your data. You can also choose to blur the location, or enable MQTT forwarding."
device_form_is_private_label: "Make this kit private"
device_form_precise_location_label: "Enable precise location"
device_form_enable_forwarding_label: "Enable MQTT forwarding"
Expand All @@ -20,14 +27,22 @@ en:
device_form_tags_blurb: "Kits can be grouped by tags. Choose from the available tags or submit a tag request on the Forum."
device_form_tags_label: "Tags"
device_form_postprocessing_subhead: "Postprocessing information"
device_form_postprocessing_blurb: "Follow the instructions here to generate a valid JSON containing the postprocessing information for your kit. This is an advanced feature and it's not required for standard Smart Citizen Kits!"
device_form_postprocessing_blurb_html: "Follow the instructions <a href='https://docs.smartcitizen.me/Guides/data/Handling%20calibration%20data/' target='_blank'>here</a> to generate a valid JSON containing the postprocessing information for your kit. This is an advanced feature and it's not required for standard Smart Citizen Kits!"
device_form_hardware_url_label: "Hardware URL"
device_form_other_actions_subhead: "Other actions"
device_form_delete_device_submit: "Delete this kit"
edit_device_submit: "Update"
edit_device_other_actions_subhead: "Other actions"
edit_device_delete_device_submit: "Delete this kit"
delete_device_warning_html: "🚨<strong>Warning!</strong> This will permanently delete the kit <strong>%{name}</strong>.🚨"
delete_device_name_label: "To confirm, type the kit name below:"
delete_device_submit: "I understand, delete the kit"
download_device_confirmation_blurb: "Click below to confirm - we will send you an email with a link to download your kit's data as a CSV."
download_device_submit: "Request data download"
download_device_recently_requested_blurb: "You've recently requested a CSV archive for this kit, please wait a little while before re-requesting. you will receive an email with a link to your CSV file shortly!"
download_device_back: "Return to kit page"
register_device_blurb: "Select what type of kit you'd like to register. If it's a new kit, you should select Smartcitizen Kit version 2+:"
register_device_v1_subheading: "SCK Version 1"
register_device_v1_description_html: "Legacy Smart Citizen Kit version 1.0 and 1.1<br />(from 2012 to 2016)"
register_device_v2_subheading: "SCK Version 2"
register_device_v2_description_html: "Smart Citizen Kit version 2.0 to 2.3<br />(from 2017 onwards)"
register_device_v2_registration_link: "https://start.smartcitizen.me"
new_device_submit: "Register"
1 change: 1 addition & 0 deletions config/locales/views/users/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ en:
show_user_headline: "User: %{username}"
show_user_edit_cta: "Edit %{owner} profile"
show_user_secrets_cta: "Show %{owner} API keys"
show_user_register_cta: "Register a new kit"
show_user_log_out_cta: "Sign out"
show_user_devices_heading: "Kits"
show_user_no_devices_message: "%{username} has no kits yet!"
Expand Down
5 changes: 5 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
get :download
post :download, to: "devices#download_confirm"
end
collection do
get :register
get :new
post :create
end
end

get "sessions/destroy", to: "sessions#destroy"
Expand Down
91 changes: 91 additions & 0 deletions spec/controllers/ui/devices_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -301,5 +301,96 @@
end
end
end

describe "register" do
context "when no user is logged in" do
let(:user) { nil }

it "redirects to the login page" do
get :register, session: { user_id: user.try(:id) }
expect(response).to redirect_to(login_path)
expect(flash[:alert]).to be_present
end
end

context "when a user is logged in" do
let(:user) { create(:user) }

it "displays the register device page" do
get :register, session: { user_id: user.try(:id) }
expect(response).to have_http_status(:success)
expect(response).to render_template(:register)
end
end
end

describe "new" do
context "when no user is logged in" do
let(:user) { nil }

it "redirects to the login page" do
get :new, session: { user_id: user.try(:id) }
expect(response).to redirect_to(login_path)
expect(flash[:alert]).to be_present
end
end

context "when a user is logged in" do
let(:user) { create(:user) }

it "displays the register device page" do
get :new, session: { user_id: user.try(:id) }
expect(response).to have_http_status(:success)
expect(assigns[:device]).to be_present
expect(response).to render_template(:new)
end
end
end

describe "create" do
let(:device_params) {
{ name: "A device", description: "A device description" }
}

context "when no user is logged in" do
let(:user) { nil }

it "redirects to the login page" do
post :create, params: { device: device_params }, session: { user_id: user.try(:id) }
expect(response).to redirect_to(login_path)
expect(flash[:alert]).to be_present
end
end

context "when a user is logged in" do
let(:user) { create(:user) }

context "when the parameters provided are valid" do
it "creates a device and redirects to the device page" do
expect_any_instance_of(Device).to receive(:save).and_call_original
post :create, params: { device: device_params }, session: { user_id: user.try(:id) }
expect(response).to redirect_to(ui_device_path(Device.last.id))
expect(flash[:success]).to be_present
expect(Device.last.name).to eq(device_params[:name])
expect(Device.last.description).to eq(device_params[:description])
expect(Device.last.owner).to eq(user)
end
end

context "when the parameters provided are invalid" do
let(:device_params) {
{ name: nil, description: "A device description" }
}

it "does not create a device, and rerenders the new device page" do
expect_any_instance_of(Device).not_to receive(:save)
post :create, params: { device: device_params }, session: { user_id: user.try(:id) }
expect(response).to have_http_status(:unprocessable_entity)
expect(response).to render_template(:new)
expect(flash[:alert]).to be_present
end
end
end
end
end

Loading

0 comments on commit 1167331

Please sign in to comment.