Commit 9ebe3e64 by John Doe

added element data retrieval end point and associated services

parent ad5796e8
...@@ -200,6 +200,7 @@ class DbAdapter ...@@ -200,6 +200,7 @@ class DbAdapter
# create an array from string response # create an array from string response
def __parse_data(resp) def __parse_data(resp)
return [] if resp==nil #no data returned
data = [] data = []
add_break = false add_break = false
resp.split("\n").each do |row| resp.split("\n").each do |row|
...@@ -222,7 +223,7 @@ class DbAdapter ...@@ -222,7 +223,7 @@ class DbAdapter
data.push(nil) if(add_break) #add a data break data.push(nil) if(add_break) #add a data break
add_break = false add_break = false
#this is a normal row #this is a normal row
data.push(words.map(&:to_i)) data.push(words.map(&:to_f))
end end
data data
end end
......
class DbElementsController < ApplicationController
before_action :authenticate_user!
def index
req_elements = DbElement.find(params[:elements])
#make sure the user is allowed to view these elements
req_elements.each do |elem|
unless current_user.views_nilm?(elem.db_stream.db.nilm)
head :unauthorized
return
end
end
#make sure the time range makes sense
@start_time = params[:start_time].to_i
@end_time = params[:end_time].to_i
unless @end_time>@start_time
head :unprocessable_entity
return
end
#retrieve the data for the requested elements
@service = LoadElementData.new
@service.run(req_elements, @start_time, @end_time)
render status: @service.success? ? :ok : :unprocessable_entity
end
end
...@@ -25,6 +25,10 @@ class DbElement < ApplicationRecord ...@@ -25,6 +25,10 @@ class DbElement < ApplicationRecord
self.offset = 0.0 self.offset = 0.0
end end
def name_path
"#{db_stream.name_path}/#{self.name}"
end
def self.json_keys def self.json_keys
[:id, :name, :units, :column, :default_max, :discrete, [:id, :name, :units, :column, :default_max, :discrete,
:default_min, :scale_factor, :offset, :plottable] :default_min, :scale_factor, :offset, :plottable]
......
...@@ -27,6 +27,10 @@ class DbFolder < ApplicationRecord ...@@ -27,6 +27,10 @@ class DbFolder < ApplicationRecord
self.parent == nil self.parent == nil
end end
def name_path
return "" if root_folder?
return "#{parent.name_path}/#{self.name}"
end
def self.defined_attributes def self.defined_attributes
[:name, :description, :hidden] [:name, :description, :hidden]
......
...@@ -32,6 +32,10 @@ class DbStream < ApplicationRecord ...@@ -32,6 +32,10 @@ class DbStream < ApplicationRecord
[:name, :name_abbrev, :description, :hidden] [:name, :name_abbrev, :description, :hidden]
end end
def name_path
"#{db_folder.name_path}/#{self.name}"
end
def remove(db_service:) def remove(db_service:)
db_service.remove_file(path) db_service.remove_file(path)
destroy destroy
......
# frozen_string_literal: true
# Loads data for specified elements
class LoadElementData
include ServiceStatus
attr_reader :data
def initialize()
super()
@data = []
end
# load data for the array of specified elements
# sets data
# data:
# [{id: element_id, values: [...]},
# {id: element_id, values: [...]},...]
# see load_stream_data for details on the value structure
#
def run(elements, start_time, end_time)
#1 figure out what streams need to be pulled
req_streams = []
elements.each do |elem|
unless req_streams.include?(elem.db_stream)
req_streams << elem.db_stream
end
end
#2 pull data from streams
combined_data = []
req_streams.each do |stream|
adapter = DbAdapter.new(stream.db.url)
data_service = LoadStreamData.new(adapter)
data_service.run(stream, start_time, end_time)
if data_service.success?
combined_data.concat(data_service.data)
else
add_warning("unable to retrieve data for #{stream.name_path}")
end
end
#3 extract requested elements from the stream datasets
req_element_ids = elements.pluck(:id)
@data = combined_data.select{|d| req_element_ids.include? d[:id] }
return self
end
end
...@@ -34,6 +34,8 @@ class LoadStreamData ...@@ -34,6 +34,8 @@ class LoadStreamData
db_stream, valid_decim, start_time, end_time, resolution db_stream, valid_decim, start_time, end_time, resolution
) )
if plottable_decim.nil? if plottable_decim.nil?
#check if its nil becuase the nilm isn't available
return self unless self.success?
# data is not sufficiently decimated, get intervals from # data is not sufficiently decimated, get intervals from
# the valid decimation level (highest resolution) # the valid decimation level (highest resolution)
path = __build_path(db_stream, valid_decim.level) path = __build_path(db_stream, valid_decim.level)
......
...@@ -68,6 +68,12 @@ module ServiceStatus ...@@ -68,6 +68,12 @@ module ServiceStatus
!errors? !errors?
end end
def reset_messages
@errors = []
@warnings = []
@notices = []
end
def run def run
raise 'Implement in client, return service object' raise 'Implement in client, return service object'
end end
......
...@@ -5,4 +5,5 @@ ...@@ -5,4 +5,5 @@
# run a service (eg a simple update) # run a service (eg a simple update)
class StubService class StubService
include ServiceStatus include ServiceStatus
attr_reader :data
end end
json.data do
json.array! @service.data.each do |element_data|
elem = DbElement.find(element_data[:id])
json.extract! elem, *DbElement.json_keys
json.path elem.name_path
json.data element_data[:values]
json.start_time @start_time
json.end_time @end_time
end
end
json.partial! "helpers/messages", service: @service
...@@ -8,6 +8,7 @@ Rails.application.routes.draw do ...@@ -8,6 +8,7 @@ Rails.application.routes.draw do
resources :dbs, only: [:show, :update] resources :dbs, only: [:show, :update]
resources :db_folders, only: [:show, :update] resources :db_folders, only: [:show, :update]
resources :db_streams, only: [:update] resources :db_streams, only: [:update]
resources :db_elements, only: [:index]
mount_devise_token_auth_for 'User', at: 'auth' mount_devise_token_auth_for 'User', at: 'auth'
resources :users, only: [:index, :create, :destroy] resources :users, only: [:index, :create, :destroy]
......
require 'rails_helper'
RSpec.describe DbElementsController, type: :request do
let(:user1) { create(:confirmed_user, first_name: 'John')}
let(:user2) { create(:confirmed_user, first_name: 'Sam')}
describe 'GET #index' do
#retrieve data for elements listed by array of ids
context 'with authenticated user' do
before do
nilm = create(:nilm, admins: [user1])
stream = create(:db_stream, elements_count: 0,
db: nilm.db,
db_folder: nilm.db.root_folder)
@elem1 = create(:db_element)
@elem2 = create(:db_element)
stream.db_elements << @elem1
stream.db_elements << @elem2
@service_data = [{id: @elem1.id, data: 'mock1'},
{id: @elem2.id, data: 'mock2'}]
@mock_service = instance_double(LoadElementData,
run: StubService.new,
success?: true, notices: [], warnings: [], errors: [],
data: @service_data)
allow(LoadElementData).to receive(:new).and_return(@mock_service)
end
it 'returns elements with data' do
@auth_headers = user1.create_new_auth_token
get "/db_elements.json",
params: {elements: [@elem1.id, @elem2.id],
start_time: 0, end_time: 100},
headers: @auth_headers
expect(response).to have_http_status(:ok)
# check to make sure JSON renders the elements
body = JSON.parse(response.body)
expect(body['data'].count).to eq(2)
end
it 'returns error if time bounds are invalid' do
@auth_headers = user1.create_new_auth_token
get "/db_elements.json",
params: {elements: [@elem1.id, @elem2.id],
start_time: 100, end_time: 0},
headers: @auth_headers
expect(response).to have_http_status(:unprocessable_entity)
end
it 'only allows access to permitted elements' do
nilm2 = create(:nilm, admins: [user2])
stream2 = create(:db_stream, elements_count: 0,
db: nilm2.db,
db_folder: nilm2.db.root_folder)
@elem3 = create(:db_element)
stream2.db_elements << @elem3
@auth_headers = user1.create_new_auth_token
get "/db_elements.json",
params: {elements: [@elem1.id, @elem3.id],
start_time: 100, end_time: 0},
headers: @auth_headers
expect(response).to have_http_status(:unauthorized)
end
end
context 'without sign-in' do
it 'returns unauthorized' do
get "/db_elements.json"
expect(response).to have_http_status(:unauthorized)
end
end
end
end
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
# generic DbStream # generic DbStream
FactoryGirl.define do FactoryGirl.define do
factory :db_element do factory :db_element do
name { Faker::Lorem.unique.words(3) } name { Faker::Lorem.unique.words(3).join(' ') }
units 'volts' units 'volts'
sequence(:column) sequence(:column)
default_max 100 default_max 100
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
# generic DbStream # generic DbStream
FactoryGirl.define do FactoryGirl.define do
factory :db_stream do factory :db_stream do
name { Faker::Lorem.words(3) } name { Faker::Lorem.words(3).join(' ') }
name_abbrev { Faker::Lorem.word } name_abbrev { Faker::Lorem.word }
description { Faker::Lorem.sentence } description { Faker::Lorem.sentence }
delete_locked false delete_locked false
......
class MockLoadStreamData
include ServiceStatus
attr_reader :data, :run_count
def initialize(dataset)
super()
@dataset = dataset
@data = nil
@run_count = 0
end
def run(db_stream, start_time, end_time)
@data = @dataset.select{|d| d[:stream]==db_stream}.first[:data]
@run_count += 1
if(@data == nil)
self.add_error('could not retrieve stream data')
return nil
else
self.reset_messages
end
return self
end
end
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'LoadElementData' do
let(:db) { create(:db, max_points_per_plot: 100) }
describe 'when elements are from the same stream' do
before do
db = create(:db, url: 'http://test/nilmdb')
@db_stream = create(:db_stream, db: db, elements_count: 0)
@elem0 = create(:db_element, column: 0, db_stream: @db_stream)
@elem1 = create(:db_element, column: 1, db_stream: @db_stream)
@elem2 = create(:db_element, column: 2, db_stream: @db_stream)
@stream_data = [{id: @elem0.id, values: 'mock0'},
{id: @elem1.id, values: 'mock1'},
{id: @elem2.id, values: 'mock2'}]
@mock_stream_service = MockLoadStreamData.new(
[stream: @db_stream, data: @stream_data])
allow(LoadStreamData).to receive(:new).and_return(@mock_stream_service)
end
it 'makes one request for the stream data' do
expect(@mock_stream_service).to receive(:data).and_return(@stream_data)
service = LoadElementData.new
service.run([@elem0,@elem2],0,100)
expect(service.success?).to be true
expect(service.data).to eq [
{id: @elem0.id, values: 'mock0'},
{id: @elem2.id, values: 'mock2'}
]
end
end
describe 'when elements are from different streams' do
before do
db = create(:db, url: 'http://test/nilmdb')
@db_stream1 = create(:db_stream, db: db, elements_count: 0)
@elem0 = create(:db_element, column: 0, db_stream: @db_stream1)
@elem1 = create(:db_element, column: 1, db_stream: @db_stream1)
@stream1_data = [{id: @elem0.id, values: 'mock0'},
{id: @elem1.id, values: 'mock1'}]
@db_stream2 = create(:db_stream, db: db, elements_count: 0)
@elem2 = create(:db_element, column: 2, db_stream: @db_stream2)
@elem3 = create(:db_element, column: 3, db_stream: @db_stream2)
@stream2_data = [{id: @elem2.id, values: 'mock2'},
{id: @elem3.id, values: 'mock3'}]
@mock_stream_service = MockLoadStreamData.new(
[{stream: @db_stream1, data: @stream1_data},
{stream: @db_stream2, data: @stream2_data}])
allow(LoadStreamData).to receive(:new).and_return(@mock_stream_service)
end
it 'makes one request per stream' do
service = LoadElementData.new
service.run([@elem0, @elem3],0,100)
expect(service.success?).to be true
expect(service.data).to eq [
{id: @elem0.id, values: 'mock0'},
{id: @elem3.id, values: 'mock3'}
]
expect(@mock_stream_service.run_count).to eq 2
end
end
describe 'when a nilm does not respond' do
before do
db = create(:db, url: 'http://test/nilmdb')
@db_stream1 = create(:db_stream, db: db, elements_count: 0)
@elem0 = create(:db_element, column: 0, db_stream: @db_stream1)
@elem1 = create(:db_element, column: 1, db_stream: @db_stream1)
@stream1_data = [{id: @elem0.id, values: 'mock0'},
{id: @elem1.id, values: 'mock1'}]
@db_stream2 = create(:db_stream, db: db, elements_count: 0)
@elem2 = create(:db_element, column: 2, db_stream: @db_stream2)
@elem3 = create(:db_element, column: 3, db_stream: @db_stream2)
@stream2_data = [{id: @elem2.id, values: 'mock2'},
{id: @elem3.id, values: 'mock3'}]
@mock_stream_service = MockLoadStreamData.new(
[{stream: @db_stream1, data: @stream1_data},
{stream: @db_stream2, data: nil}])
allow(LoadStreamData).to receive(:new).and_return(@mock_stream_service)
end
it 'fills in the data that is available' do
service = LoadElementData.new
service.run([@elem0, @elem3],0,100)
expect(service.warnings.length).to eq 1
expect(service.data).to eq [
{id: @elem0.id, values: 'mock0'}
]
expect(@mock_stream_service.run_count).to eq 2
end
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment