Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
barmintor committed Jul 11, 2024
1 parent 0a57dcd commit e7c89d7
Show file tree
Hide file tree
Showing 13 changed files with 212 additions and 37 deletions.
13 changes: 9 additions & 4 deletions app/controllers/locations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ def index
path.blank? or path == '/'
end
if home_page
@locations = Location.where(front_page: true).where.not(code: 'all', suppress_display: true)
@locations = Location.where(front_page: true).where.not(code: 'all', suppress_display: true).includes(:access_points)
else
@locations = Location.where.not(code: 'all', suppress_display: true)
@locations = Location.where.not(code: 'all', suppress_display: true).includes(:access_points)
end
@locations = @locations.order(:name)
@now = Time.current
Expand Down Expand Up @@ -44,7 +44,12 @@ def create

def update
authorize! :update, @location
if @location.update(update_params)
clean_params = update_params
clean_params.dig(:access_points)&.map! do |ap|
ap.present? ? AccessPoint.find_or_create_by(id: ap.to_i) : nil
end
clean_params.dig(:access_points)&.compact!
if @location.update(clean_params)
flash[:success] = "Location successfully updated"
redirect_to admin_url
else
Expand Down Expand Up @@ -136,7 +141,7 @@ def create_params

def update_params
if current_user.administrator?
params.require(:location).permit(:name, :code, :comment, :comment_two, :url, :summary, :primary, :primary_location_id, :front_page, :short_note, :short_note_url, :suppress_display)
params.require(:location).permit(:name, :code, :comment, :comment_two, :url, :summary, :primary, :primary_location_id, :front_page, :short_note, :short_note_url, :suppress_display, :wifi_connections_baseline, access_points: [])
else
params.require(:location).permit(:comment, :comment_two, :url, :summary, :short_note, :short_note_url)
end
Expand Down
23 changes: 23 additions & 0 deletions app/helpers/edit_location_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module EditLocationHelper
# <label for="location_access_points_2"><input type="checkbox" value="2" name="location[access_points][]" id="location_access_points_2" />2 (Location 2)</label>
def access_point_checkboxes(access_point_branches, count_iterator, current_values = [])
content_tag(:ul) do
access_point_branches.map do |branch|
branch_tag = content_tag(:li) do
cb_index = count_iterator.next
content_tag(:label, for: "location_access_points_#{cb_index}") do
checked = current_values.include?(branch.id)
tag(:input, type: 'checkbox', value: branch.id, name: "location[access_points][]", id: "location_access_points_#{cb_index}", checked: checked).safe_concat("#{branch.name} #{branch.updated_at}")
end
end
if branch.children.size > 0
child_tag = content_tag(:li) do
access_point_checkboxes(branch.children.values, count_iterator)
end
branch_tag.safe_concat child_tag
end
branch_tag
end.join.html_safe
end
end
end
4 changes: 4 additions & 0 deletions app/models/access_point.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class AccessPoint < ApplicationRecord
has_many :access_points_locations
has_many :locations, through: :access_points_locations
end
4 changes: 4 additions & 0 deletions app/models/access_points_location.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class AccessPointsLocation < ApplicationRecord
belongs_to :location
belongs_to :access_point
end
4 changes: 4 additions & 0 deletions app/models/location.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ class Location < ApplicationRecord
validate :primary_location_must_be_primary

has_many :timetables
has_many :access_points_locations
has_many :access_points, through: :access_points_locations

belongs_to :primary_location, class_name: 'Location', foreign_key: 'primary_location_id', optional: true
accepts_nested_attributes_for :access_points

# TODO: when a location is deleted any permissions related to that location should also be deleted

Expand Down
53 changes: 33 additions & 20 deletions app/views/locations/_location_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,31 @@
</div>

<div class="form-group">
<%= f.label :suppress_display %>
<% if current_user.administrator? %>
<%= f.check_box :suppress_display %>
<% elsif @location.suppress_display %>
<p class="warn static-control-form"><%= t("location.suppressed") %></p>
<% end %>
<h3>Location Display Options</h3>
<% if current_user.administrator? %>
<ul>
<li><%= f.label(:suppress_display) { f.check_box(:suppress_display).safe_concat(" Suppress Display") } %></li>
<li><%= f.label(:front_page) { f.check_box(:front_page).safe_concat(" Front Page") } %></li>
<li><%= f.label(:primary) { f.check_box(:primary).safe_concat(" Primary") } %></li>
</ul>
<div class="form-group">
<%= f.label :primary_location %>
<%= collection_select(:location, :primary_location_id, Location.where(primary: true), :id, :name, {include_blank: 'None'}) %>
</div>
<% else %>
<ul>
<% if @location.suppress_display %>
<li><span class="warn static-control-form"><%= t("location.suppressed") %></span></li>
<% end %>
<li>Front Page: <%= (!!@location.front_page).to_s %></li>
<li>Primary: <%= (!!@location.primary).to_s %></li>
<% if @location.primary_location %>
<li>Primary Location: <%= @location.primary_location.name %></li>
<% end %>
</ul>
<% end %>
</div>

<% if current_user.administrator? %>
<div class="form-group">
<%= f.label :front_page %>
<%= f.check_box :front_page %>
</div>
<div class="form-group">
<%= f.label :primary %>
<%= f.check_box :primary %>
</div>
<div class="form-group">
<%= f.label :primary_location %>
<%= collection_select(:location, :primary_location_id, Location.where(primary: true), :id, :name, {include_blank: 'None'}) %>
</div>
<% end %>
<div class="form-group">
<%= f.label :url %>
<%= f.text_field :url, :class => "form-control" %>
Expand All @@ -66,6 +69,16 @@
<%= f.text_field :short_note_url, :class => "form-control" %>
</div>


<div class="form-group">
<h3>WiFi Configuration</h3>
<div class="form-group">
<%= f.label :wifi_connections_baseline, 'WiFi Connections Baseline Count' %>
<%= f.text_field :wifi_connections_baseline, :class => "form-control" %>
</div>
<% current_aps = @location.access_point_ids.map(&:to_s) %>
<%= access_point_checkboxes(Hours::WifiDensity.access_point_trees(@location), (1...).each, current_aps) %>
</div>
<%= f.submit :class => 'btn btn-primary btn-sm'%>
<% end %>
</div>
2 changes: 1 addition & 1 deletion db/migrate/20170615203023_add_fields_to_time_tables.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def change
add_column :time_tables, :note, :string
remove_index :time_tables, column: [:code, :date]
remove_column :time_tables, :code
add_reference :time_tables, :library, index: true, foreign_key: true
add_reference :time_tables, :library, index: true, foreign_key: true, type: :bigint
add_index :time_tables, [:library_id, :date], :unique => true
rename_table :time_tables, :timetables
end
Expand Down
2 changes: 1 addition & 1 deletion db/migrate/20170627151458_change_library_table_name.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ def change
remove_index :timetables, name: :index_timetables_on_library_id_and_date
remove_reference :timetables, :library, index: true, foreign_key: true
rename_table :libraries, :locations
add_reference :timetables, :location, index: true, foreign_key: true
add_reference :timetables, :location, index: true, foreign_key: true, type: :bigint
add_index :timetables, [:location_id, :date], :unique => true
end
end
2 changes: 1 addition & 1 deletion db/migrate/20170814194528_add_primary_location.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class AddPrimaryLocation < ActiveRecord::Migration[5.1]
def up
add_reference :locations, :primary_location, foreign_key: {to_table: :locations}
add_reference :locations, :primary_location, type: :bigint, foreign_key: {to_table: :locations}
add_column :locations, :primary, :boolean, default: false
add_index :locations, :primary
end
Expand Down
17 changes: 17 additions & 0 deletions db/migrate/20240710210750_add_location_access_points.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class AddLocationAccessPoints < ActiveRecord::Migration[6.0]
def change
create_table :access_points do |t|
t.integer :client_count
t.timestamps null: false
end
create_table :access_points_locations do |t|
t.timestamps null: false
t.bigint :location_id, null: false
t.bigint :access_point_id, null: false
t.index :location_id
t.index :access_point_id
t.index [:location_id, :access_point_id], unique: true
end
add_column :locations, :wifi_connections_baseline, :int, null: true
end
end
19 changes: 18 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,23 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.0].define(version: 2024_07_10_150406) do
ActiveRecord::Schema[7.0].define(version: 2024_07_10_210750) do
create_table "access_points", force: :cascade do |t|
t.integer "client_count"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end

create_table "access_points_locations", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "location_id", null: false
t.bigint "access_point_id", null: false
t.index ["access_point_id"], name: "index_access_points_locations_on_access_point_id"
t.index ["location_id", "access_point_id"], name: "index_access_points_locations_on_location_id_and_access_point_id", unique: true
t.index ["location_id"], name: "index_access_points_locations_on_location_id"
end

create_table "locations", force: :cascade do |t|
t.string "name", null: false
t.string "code", null: false
Expand All @@ -26,6 +42,7 @@
t.string "short_note"
t.text "short_note_url"
t.boolean "suppress_display", default: false
t.integer "wifi_connections_baseline"
t.index ["code"], name: "index_locations_on_code"
t.index ["front_page"], name: "index_locations_on_front_page"
t.index ["primary"], name: "index_locations_on_primary"
Expand Down
95 changes: 88 additions & 7 deletions lib/hours/wifi_density.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,13 @@ def self.data_url
def self.percentage_for(location)
# If this location's code is not mapped in the wifi density config file,
# that means wifi density info is not supported for the location.
config_for_location = config_for_location_code(location.code)
config_for_location = location_wifi_configuration(location)
return nil if config_for_location.nil? || !config_for_location.key?(:high) || !config_for_location.key?(:cuit_location_ids)
Rails.cache.fetch('wifi_density_data-' + location.code, expires_in: wifi_data_cache_duration) do
wifi_density_data_for_locations = Rails.cache.fetch('wifi_density_data', expires_in: wifi_data_cache_duration) do
fetch_hierarchical_wifi_density_data
end
wifi_density_data_for_locations = hierarchical_wifi_density_data
aggregated_locations = {}

config_for_location[:cuit_location_ids].each do |cuit_location_id|
config_for_location[:cuit_location_ids]&.each do |cuit_location_id|
location_data = wifi_density_data_for_locations[cuit_location_id.to_s]
next if location_data.nil?
aggregated_locations.merge!({cuit_location_id.to_s => location_data})
Expand Down Expand Up @@ -52,20 +50,54 @@ def self.percentage_for(location)
end
end

def self.location_wifi_configuration(location)
{
high: (location.wifi_connections_baseline.to_i if location.wifi_connections_baseline.present?),
cuit_location_ids: (location.access_point_ids.map(&:to_s) if location.access_point_ids.present?)
}
end

# Obsolete
def self.config_for_location_code(location_code)
WIFI_DENSITY[:locations][location_code.to_sym]
end

def self.raw_wifi_density_data
Rails.cache.fetch('raw_wifi_density_data', expires_in: wifi_data_cache_duration) do
cacheable_data = fetch_raw_wifi_density_data
now = DateTime.now
cacheable_data.each do |ap_id, location_data|
ap = AccessPoint.find_or_create_by(id: ap_id)
if ap.client_count != location_data['client_count'].to_i
ap.update(client_count: location_data['client_count'].to_i)
location_data['updated_at'] = now
else
location_data['updated_at'] = ap.updated_at
end
end
cacheable_data
end
end

def self.fetch_raw_wifi_density_data
if Rails.env.development?
if Rails.env.development? || Rails.env.test?
JSON.parse(File.read(Rails.root.join('spec/fixtures/files/sample-wifi-density-response.json')))
else
JSON.parse(Net::HTTP.get(URI(data_url)))
end
end

def self.hierarchical_wifi_density_data
Rails.cache.fetch('hierarchical_wifi_density_data', expires_in: wifi_data_cache_duration) do
fetch_hierarchical_wifi_density_data
end
end

def self.fetch_hierarchical_wifi_density_data
all_location_data = fetch_raw_wifi_density_data
all_location_data = raw_wifi_density_data

stale_time = DateTime.now - 2.hours
all_location_data.reject! { |k, v| v['updated_at'] < stale_time }

# Convert raw data into hierarchical data, generating top level entries
# for any referenced parent_id values that don't exist.
Expand All @@ -90,6 +122,8 @@ def self.fetch_hierarchical_wifi_density_data
all_location_data
end

# add child access point nodes to their parent's children hash
# but keep them in the top-level hash
def self.recursively_collect_children(location_data, children = {})
if location_data['children']
location_data['children'].each do |child_location_id, child_location_data|
Expand All @@ -104,5 +138,52 @@ def self.recursively_collect_children(location_data, children = {})
end
children
end

def self.default_access_point_data_for_id(ap_id, suffix = "")
{ 'id' => ap_id, 'name' => "Location #{ap_id}#{suffix}" }
end

def self.access_point_trees(location = nil)
stored_ids = location ? location.access_point_ids.to_a : []
aggregated_locations = raw_wifi_density_data.reduce({}) do |acc, entry|
ap_id, ap_data = *entry
acc[ap_id] ||= default_access_point_data_for_id(ap_id)
acc[ap_id].merge!(ap_data)
if parent_id = ap_data['parent_id']
acc[parent_id] ||= default_access_point_data_for_id(parent_id)
end
acc
end

roots = rollup_children(aggregated_locations).transform_values(&:deep_symbolize_keys)
stored_ids.each { |id| roots[id.to_s] ||= default_access_point_data_for_id(id, " [no current data]").symbolize_keys }
roots.map { |k,v| AccessPointStruct.new(**v) }
end

# add child access point nodes to their parent's children hash
# and remove them from the top-level hash
def self.rollup_children(data = {})
parent_ids = data.values.map { |v| v['parent_id'] }.compact
return data if parent_ids.length == 0

(data.keys - parent_ids).each do |leaf_id|
leaf = data[leaf_id]
next unless parent_id = leaf['parent_id']
parent = data[parent_id] ||= default_access_point_data_for_id(parent_id)
(parent['children'] ||= {})[leaf_id] = data.delete(leaf_id)
end
rollup_children(data)
end

class AccessPointStruct
attr_accessor :children, :id, :name, :parent_id, :updated_at
def initialize(children: nil, id:, name:, parent_id: nil, updated_at: nil, **args)
@id = id
@name = name
@parent_id = parent_id
@updated_at = updated_at
@children = (children || {}).transform_values { |c| AccessPointStruct.new(**c) }
end
end
end
end
11 changes: 9 additions & 2 deletions spec/hours/wifi_density_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

context ".percentage_for" do
let(:butler_location) {
Location.new(code: 'butler-24')
Location.new(code: 'butler-24', wifi_connections_baseline: 1900, access_points: [AccessPoint.new(id: '115')])
}
let(:journalism_location) {
Location.new(code: 'journalism')
Expand Down Expand Up @@ -50,8 +50,15 @@
end

context ".fetch_hierarchical_wifi_density_data" do
let(:hierarchical_wifi_density_data) { described_class.fetch_hierarchical_wifi_density_data }
before do
hierarchical_wifi_density_data.each do |k, v|
v.delete('updated_at')
v['children']&.each {|v2| v2.delete('updated_at')}
end
end
it "returns a hierarchical version of the raw wifi data with child locations nested under parents" do
expect(described_class.fetch_hierarchical_wifi_density_data).to eq(sample_hierarchical_wifi_density_data)
expect(hierarchical_wifi_density_data).to eq(sample_hierarchical_wifi_density_data)
end
end

Expand Down

0 comments on commit e7c89d7

Please sign in to comment.