diff --git a/client/dashboard/energy/graph/graph.component.js b/client/dashboard/energy/graph/graph.component.js
index 7a76d79..2805230 100644
--- a/client/dashboard/energy/graph/graph.component.js
+++ b/client/dashboard/energy/graph/graph.component.js
@@ -1,4 +1,5 @@
import React from 'react';
+import c3 from 'c3';
import Templates from 'config/templates';
import CalendarGridChart from './../../../d3/grid/calendar_grid';
diff --git a/client/dashboard/energy/table/table.rt b/client/dashboard/energy/table/table.rt
index 78538b1..6cc43d1 100644
--- a/client/dashboard/energy/table/table.rt
+++ b/client/dashboard/energy/table/table.rt
@@ -4,6 +4,7 @@
|
Day |
Consumption (kWh) |
+
Daily Mean Irradiance (W/m2) |
Production (kWh) |
@@ -11,8 +12,9 @@
|
{energy_datum.day_to_s} |
- {energy_datum.consumption_to_s} |
- {energy_datum.production_to_s} |
+ {energy_datum.consumption} |
+ {energy_datum.irradiance} |
+ {energy_datum.production} |
diff --git a/client/dashboard/irradiance/graph/graph.component.js b/client/dashboard/irradiance/graph/graph.component.js
new file mode 100644
index 0000000..ce4e43b
--- /dev/null
+++ b/client/dashboard/irradiance/graph/graph.component.js
@@ -0,0 +1,152 @@
+import React from 'react';
+import Templates from 'config/templates';
+import c3 from 'c3';
+
+class GraphComponent extends React.Component {
+
+ constructor(props){
+ super(props);
+ }
+
+ get state_manager(){
+ return this.props.state_manager;
+ }
+
+ get houses(){
+ return this.state_manager.houses;
+ }
+
+ get chart_data(){
+ var irradiance_graph = this;
+ return Object.keys(irradiance_graph.state_manager.irradiance_data).map((day)=>{
+ var day_data = irradiance_graph.state_manager.irradiance_data[day],
+ day_datum = {date: day};
+ day_data.forEach((energy_datum)=>{
+ day_datum['irradiance'+energy_datum.house.data.id] = energy_datum.irradiance;
+ day_datum['production'+energy_datum.house.data.id] = energy_datum.production;
+ });
+ return day_datum;
+ }).filter((day_datum)=>{
+ // due to timezone offsets, some houses might not have an energy_datum point,
+ // where others do. Just filter those dates out to avoid UI confusion.
+ return Object.keys(day_datum).length === irradiance_graph.value_keys.length;
+ });
+ }
+
+ get value_keys(){
+ var irradiance_graph = this;
+ return ['date'].concat(Object.keys(irradiance_graph.names));
+ }
+
+ get irradiance_keys(){
+ return this.houses.map((house)=>{
+ return 'irradiance' + house.data.id;
+ });
+ }
+
+ get production_keys(){
+ return this.houses.map((house)=>{
+ return 'production' + house.data.id;
+ });
+ }
+
+ get colors(){
+ var fnColor = d3.scale.category20(),
+ irradiance_graph = this;
+ return Object.keys(irradiance_graph.names).reduce((colors, key)=>{
+ colors[key] = fnColor(key);
+ return colors;
+ }, {});
+ }
+
+ get names(){
+ var names = {};
+ this.houses.forEach((house)=>{
+ names['irradiance' + house.data.id] = house.data.name + ' Irradiance';
+ names['production' + house.data.id] = house.data.name + ' Production';
+ });
+ return names;
+ }
+
+ get axes(){
+ var irradiance_graph = this,
+ axes = {};
+ irradiance_graph.production_keys.forEach((production_key)=>{
+ axes[production_key] = 'y';
+ });
+ irradiance_graph.irradiance_keys.forEach((irradiance_key)=>{
+ axes[irradiance_key] = 'y2';
+ });
+ return axes;
+ }
+
+ get types(){
+ var irradiance_graph = this;
+ return irradiance_graph.production_keys.reduce((types, production_key)=>{
+ types[production_key] = 'bar';
+ return types;
+ }, {});
+ }
+
+ componentDidMount(){
+ var irradiance_graph = this;
+ irradiance_graph.updateGraph();
+ }
+
+ componentDidUpdate(prev_props, prev_state){
+ var irradiance_graph = this;
+ if (irradiance_graph.props.date_interval[0] != prev_props.date_interval[0] ||
+ irradiance_graph.props.date_interval[1] != prev_props.date_interval[1]){
+ irradiance_graph.updateGraph();
+ }
+ }
+
+ updateGraph(){
+ var irradiance_graph = this,
+ data = {
+ json: irradiance_graph.chart_data,
+ keys: {
+ x: 'date', // it's possible to specify 'x' when category axis
+ value: irradiance_graph.value_keys,
+ },
+ types: irradiance_graph.types,
+ names: irradiance_graph.names,
+ groups: [irradiance_graph.production_keys],
+ axes: irradiance_graph.axes,
+ colors: irradiance_graph.colors
+ };
+ if (!irradiance_graph.chart){
+ irradiance_graph.chart = c3.generate({
+ bindto: '#irradiance_graph',
+ data: data,
+ axis: {
+ x: {
+ type: 'timeseries',
+ tick: { format: d3.time.format('%d %B %y') }
+ },
+ y: {
+ label: 'Production'
+ },
+ y2: {
+ show: true,
+ label: 'Irradiance'
+ }
+ }
+ });
+ } else {
+ console.log('reloading data')
+ console.log(data)
+ data.unload = irradiance_graph.chart.data;
+ irradiance_graph.chart.load(data);
+ }
+ }
+
+ render() {
+ var irradianceGraphRt = Templates.forComponent('irradiance_graph');
+ return irradianceGraphRt.call(this);
+ }
+}
+GraphComponent.NAME = 'IrradianceGraph';
+
+module.exports = GraphComponent;
+
diff --git a/client/dashboard/irradiance/graph/graph.rt b/client/dashboard/irradiance/graph/graph.rt
new file mode 100644
index 0000000..6bce0b8
--- /dev/null
+++ b/client/dashboard/irradiance/graph/graph.rt
@@ -0,0 +1 @@
+
diff --git a/client/dashboard/irradiance/irradiance.component.js b/client/dashboard/irradiance/irradiance.component.js
new file mode 100644
index 0000000..ad18ce3
--- /dev/null
+++ b/client/dashboard/irradiance/irradiance.component.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import Templates from 'config/templates';
+
+class IrradianceComponent extends React.Component {
+
+ get state_manager(){
+ return this.props.state_manager;
+ }
+
+ render() {
+ var irradianceRt = Templates.forComponent('irradiance');
+ return irradianceRt.call(this);
+ }
+
+}
+IrradianceComponent.NAME = 'Irradiance';
+
+module.exports = IrradianceComponent;
diff --git a/client/dashboard/irradiance/irradiance.rt b/client/dashboard/irradiance/irradiance.rt
new file mode 100644
index 0000000..cb7e9d1
--- /dev/null
+++ b/client/dashboard/irradiance/irradiance.rt
@@ -0,0 +1,13 @@
+
+
+
+
Irradiance
+
+
+
diff --git a/client/dashboard/irradiance/irradiance.scss b/client/dashboard/irradiance/irradiance.scss
new file mode 100644
index 0000000..d931092
--- /dev/null
+++ b/client/dashboard/irradiance/irradiance.scss
@@ -0,0 +1,3 @@
+#irradiance_component {
+
+}
diff --git a/client/dashboard/irradiance/table/table.component.js b/client/dashboard/irradiance/table/table.component.js
new file mode 100644
index 0000000..e80fe06
--- /dev/null
+++ b/client/dashboard/irradiance/table/table.component.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import Templates from 'config/templates';
+
+class TableComponent extends React.Component {
+
+ get state_manager(){
+ return this.props.state_manager;
+ }
+
+ render() {
+ var irradianceTableRt = Templates.forComponent('irradiance_table');
+ return irradianceTableRt.call(this);
+ }
+}
+
+TableComponent.NAME = 'IrradianceTable';
+
+module.exports = TableComponent;
diff --git a/client/dashboard/irradiance/table/table.rt b/client/dashboard/irradiance/table/table.rt
new file mode 100644
index 0000000..48dea83
--- /dev/null
+++ b/client/dashboard/irradiance/table/table.rt
@@ -0,0 +1,18 @@
+
+
+
+ | Day |
+ House |
+ Daily Mean Irradiance (W/m2) |
+ Production (kWh) |
+
+
+
+
+ | {energy_datum.day_to_s} |
+ {energy_datum.house.data.name} |
+ {energy_datum.irradiance} |
+ {energy_datum.production} |
+
+
+
diff --git a/client/dashboard/layout/layout.component.js b/client/dashboard/layout/layout.component.js
index 7a04d09..5da1f66 100644
--- a/client/dashboard/layout/layout.component.js
+++ b/client/dashboard/layout/layout.component.js
@@ -6,6 +6,7 @@ import Templates from 'config/templates';
import House from './../../models/house';
import PowerDatum from './../../models/power_datum';
import StateManager from './../state_manager';
+import DateRangeSlider from './../../d3/sliders/date_range';
class LayoutComponent extends React.Component {
@@ -18,6 +19,8 @@ class LayoutComponent extends React.Component {
house: null,
dataset: null,
year: null,
+ month: null,
+ date_interval: null,
view: null
}
}
@@ -50,9 +53,35 @@ class LayoutComponent extends React.Component {
});
}
+ componentDidUpdate(prev_props, prev_state){
+ var layout = this;
+ if (layout.shouldShowDateRange() && !layout.datesMatch(prev_state)){
+ layout.updateDateRange();
+ } else if (!layout.shouldShowDateRange()){
+ layout.destroyDateRange();
+ }
+ }
+
+ datesMatch(prev_state){
+ var layout = this;
+ return layout.state.month == prev_state.month &&
+ layout.state.year == prev_state.year &&
+ !layout.shouldShowDateRange() ||
+ layout.state.date_interval && prev_state.date_interval &&
+ layout.state.date_interval[0] == prev_state.date_interval[0] &&
+ layout.state.date_interval[1] == prev_state.date_interval[1];
+ }
+
+ shouldShowDateRange(){
+ var layout = this;
+ return layout.state.house && layout.state.dataset === 'power' || layout.state.dataset === 'irradiance';
+ }
+
syncFromStateManager(fnStateSet){
var layout = this;
- layout.setState(layout.state_manager.state, fnStateSet);
+ layout.setState(layout.state_manager.state, ()=>{
+ fnStateSet()
+ });
}
setHouse(event){
@@ -72,6 +101,50 @@ class LayoutComponent extends React.Component {
layout.state_manager.setParams(update, layout);
}
+ destroyDateRange(){
+ var layout = this,
+ container = document.getElementById('date_interval');
+ if (container) container.innerHTML = '';
+ layout.date_interval_slider = undefined;
+ }
+
+ updateDateRange(){
+ var layout = this,
+ house = layout.house,
+ state_manager = layout.state_manager;
+ if (layout.date_interval_slider === undefined){
+ layout.date_interval_slider = new DateRangeSlider({
+ container: '#date_interval',
+ outer_height: 100,
+ maxDelta: function(changed_date, other_date){
+ if (Math.abs(changed_date.getTime() - other_date.getTime()) > House.MAX_POWER_RANGE * 1000){
+ if (changed_date > other_date){
+ return new Date(changed_date.getTime() - House.MAX_POWER_RANGE * 1000);
+ } else {
+ return new Date(changed_date.getTime() + House.MAX_POWER_RANGE * 1000);
+ }
+ }
+ return false;
+ }
+ });
+ }
+ layout.date_interval_slider.onRangeUpdated = (min, max)=>{
+ if (layout.date_interval_update) clearTimeout(layout.date_interval_update);
+ // This will update the URL -> state_manager.state -> component states.
+ layout.date_interval_update = setTimeout(()=>{
+ var date_interval = [Math.round(min.getTime() / 1000), Math.round(max.getTime() / 1000)];
+ layout.state_manager.setParams({date_interval: date_interval}, layout);
+ }, 500);
+ };
+ var month_range = state_manager.month_range;
+ layout.date_interval_slider.drawData({
+ abs_min: house.toDate(month_range[0]),
+ abs_max: house.toDate(month_range[1]),
+ current_min: house.toDate(state_manager.state.date_interval[0]),
+ current_max: house.toDate(state_manager.state.date_interval[1])
+ });
+ }
+
refreshData(){
var layout = this,
houses = layout.state.houses,
diff --git a/client/dashboard/layout/layout.rt b/client/dashboard/layout/layout.rt
index 46db55a..08a5386 100644
--- a/client/dashboard/layout/layout.rt
+++ b/client/dashboard/layout/layout.rt
@@ -1,4 +1,5 @@
+
@@ -9,7 +10,7 @@
- React
- React Templates
- - React Router
+ - ReactJs History
- LokiJs - persisting API calls to indexedDb
- Webpack - hot mode developing and app bundling
- Babel - ES6 transpiler
@@ -22,15 +23,23 @@
Retrieving houses...
- Select household:
-
+
+
Select household:
+
+
-
+
Select dataset:
+
+ type="button" class="btn btn-primary">Daily Mean Irradiance
View as:
@@ -72,26 +81,44 @@
class="btn-info btn btn-sm"
rt-class="{active: year == this.state.year}"
onClick="{this.setParam.bind(this)}">{year}
+
+
+
+
+
+
+
+ Retrieving {this.state.loading_data} data...
+
+ date_interval="{this.state.date_interval}" />
diff --git a/client/dashboard/power/graph/graph.component.js b/client/dashboard/power/graph/graph.component.js
index 0da9a0b..f68b705 100644
--- a/client/dashboard/power/graph/graph.component.js
+++ b/client/dashboard/power/graph/graph.component.js
@@ -1,8 +1,8 @@
import React from 'react';
import Templates from 'config/templates';
+import c3 from 'c3';
import House from './../../../models/house';
-import c3 from 'c3';
class GraphComponent extends React.Component {
@@ -21,7 +21,7 @@ class GraphComponent extends React.Component {
componentDidUpdate(prev_props, prev_state){
var power_graph = this;
- if (prev_props.house != power_graph.props.house || prev_props.power_range != power_graph.props.power_range){
+ if (prev_props.house != power_graph.props.house || prev_props.date_interval != power_graph.props.date_interval){
power_graph.updateGraph();
}
}
diff --git a/client/dashboard/power/power.component.js b/client/dashboard/power/power.component.js
index 3b60aaf..35753f9 100644
--- a/client/dashboard/power/power.component.js
+++ b/client/dashboard/power/power.component.js
@@ -4,7 +4,6 @@ import _ from 'lodash';
import Templates from 'config/templates';
import House from './../../models/house';
-import DateRangeSlider from './../../d3/sliders/date_range';
class PowerComponent extends React.Component {
@@ -25,75 +24,11 @@ class PowerComponent extends React.Component {
return this.props.state_manager;
}
- get loading_power_data(){
- return this.props.loading_power_data || this.state.loading_power_data;
- }
-
- componentDidMount(){
- var power = this;
- power.initDateRange();
- }
-
- componentDidUpdate(prev_props, prev_state){
- var power = this,
- state_manager = power.state_manager;
- if (prev_props.power_range[0] != power.props.power_range[0] ||
- prev_props.power_range[1] != power.props.power_range[1] ||
- prev_props.house != power.props.house){
- power.initDateRange();
- state_manager.powerDataRendered();
- }
- }
-
syncFromStateManager(fnStateSet){
var power = this;
power.setState(power.state_manager.state, fnStateSet);
}
- initDateRange(){
- var power = this,
- house = power.house;
- if (power.date_range_slider === undefined){
- power.date_range_slider = new DateRangeSlider({
- container: '#power_date_setter',
- outer_height: 100,
- maxDelta: function(changed_date, other_date){
- if (Math.abs(changed_date.getTime() - other_date.getTime()) > 3600 * 24 * 4 * 1000){
- if (changed_date > other_date){
- return new Date(changed_date.getTime() - 3600 * 24 * 4 * 1000);
- } else {
- return new Date(changed_date.getTime() + 3600 * 24 * 4 * 1000);
- }
- }
- return false;
- }
- });
- }
- power.date_range_slider.onRangeUpdated = (min, max)=>{
- if (power.date_range_update) clearTimeout(power.date_range_update);
- power.date_range_update = setTimeout(()=>{
- var power_range = [Math.round(min.getTime() / 1000), Math.round(max.getTime() / 1000)];
- power.state_manager.setParams({power_range: power_range}, power);
- }, 500);
- };
- power.date_range_slider.drawData({
- abs_min: house.state.current_month_moment.toDate(),
- abs_max: house.state.end_of_current_data_moment.toDate(),
- current_min: house.toDate(house.state.power_range[0]),
- current_max: house.toDate(house.state.power_range[1])
- });
- }
-
- setParam(event){
- var power = this,
- param = event.target.dataset.param,
- value = event.target.dataset.value,
- update = {}, route_helper;
- update[param] = value;
- if (value == power.state_manager.state[param]) return false;
- power.state_manager.setParams(update, power);
- }
-
render() {
var powerRt = Templates.forComponent('power');
return powerRt.call(this);
diff --git a/client/dashboard/power/power.rt b/client/dashboard/power/power.rt
index 281d102..938467b 100644
--- a/client/dashboard/power/power.rt
+++ b/client/dashboard/power/power.rt
@@ -1,33 +1,18 @@
-
-
-
-
- Retrieving power data...
-
-
+ date_interval="{this.props.date_interval}" >
+ date_interval="{this.props.date_interval}" >
diff --git a/client/dashboard/state_manager.js b/client/dashboard/state_manager.js
index 4025a77..21464cc 100644
--- a/client/dashboard/state_manager.js
+++ b/client/dashboard/state_manager.js
@@ -1,5 +1,7 @@
import query_string from 'query-string';
+import moment from 'moment-timezone';
+import EnergyDatum from './../models/energy_datum';
import ObjectUtil from './../../shared/utils/object';
import ArrayUtil from './../../shared/utils/array';
@@ -13,6 +15,9 @@ const ROUTES = [
}, {
path: /houses\/(\d+)\/(power)\/([^\/]+)\/(\d+)\/([^\/]+)\/?$/,
parameters: { 1: 'house_id', 2: 'dataset', 3: 'month', 4: 'year', 5: 'view' }
+ }, {
+ path: /(irradiance)\/([^\/]+)\/(\d+)\/([^\/]+)\/?$/,
+ parameters: { 1: 'dataset', 2: 'month', 3: 'year', 4: 'view' }
}
];
@@ -24,8 +29,7 @@ class StateManager {
state_manager.houses = houses;
state_manager.state = {
- loading_energy_data: false,
- loading_power_data: false,
+ loading_data: false,
graph_attr: 'consumption',
view: 'graph',
dataset: 'power',
@@ -33,55 +37,94 @@ class StateManager {
house: null,
month: null,
year: null,
- power_range: null };
+ date_interval: null };
state_manager.history = createHistory();
state_manager.update_in_progress = false;
}
+ get month_i(){
+ return moment.monthsShort().indexOf(this.state.month);
+ }
+
get date_params(){
- return ObjectUtil.filterKeys(this.state, ['year', 'month', 'power_range']);
+ return ObjectUtil.filterKeys(this.state, ['year', 'month', 'date_interval']);
+ }
+
+ get month_range(){
+ var state_manager = this,
+ house = state_manager.state.house,
+ start_time = house.parseMoment(`${state_manager.state.year}-${state_manager.month_i + 1}-01`, 'YYYY-M-DD'),
+ end_time = start_time.clone().endOf('month').unix();
+
+ start_time = start_time.unix();
+ if (start_time < house.data.data_from) start_time = house.data.data_from;
+ if (end_time > house.data.data_until) end_time = house.data.data_until;
+ return [start_time, end_time];
+ }
+
+ get year_range(){
+ var state_manager = this,
+ house = state_manager.state.house,
+ start_time = house.parseMoment(`${state_manager.state.year}-01-01`, 'YYYY-MM-DD'),
+ end_time = start_time.clone().endOf('year').unix();
+
+ start_time = start_time.unix();
+ if (start_time < house.data.data_from) start_time = house.data.data_from;
+ if (end_time > house.data.data_until) end_time = house.data.data_until;
+ return [start_time, end_time];
+ }
+
+ matchesEnergyState(){
+ var state_manager = this,
+ house = state_manager.state.house,
+ energy_range = state_manager.state.graph_attr === 'irradiance' ? state_manager.state.date_interval : state_manager.year_range;
+ if (!house.state.energy_range) return false;
+ return energy_range[0] === house.state.energy_range[0] && energy_range[1] === house.state.energy_range[1];
+ }
+
+ matchesPowerState(){
+ var state_manager = this,
+ house = state_manager.state.house,
+ month_range = state_manager.month_range;
+ if (!house.state.power_range) return false;
+ return month_range[0] === house.state.power_range[0] && month_range[1] === house.state.power_range[1];
}
// This will update the house state acccording to passed update parameters.
- updateHouseFromState(component, fnResolve){
+ updateHouseFromState(component){
var state_manager = this,
house = state_manager.state.house,
promise;
if (!house) {
promise = Promise.resolve();
- } else if (state_manager.state.dataset === 'energy' && (!house.energy_data || !house.matchesEnergyState(state_manager.state))){
- house.setMonthState(state_manager.state);
+ } else if (state_manager.state.dataset === 'energy' && !state_manager.matchesEnergyState()){
promise = state_manager.setHouseEnergyFromState(component);
- } else if (state_manager.state.dataset === 'power' && !house.power_data || !house.matchesPowerState(state_manager.state)){
- house.setMonthState(state_manager.state);
+ } else if (state_manager.state.dataset === 'power' && !state_manager.matchesPowerState()){
promise = state_manager.setHousePowerFromState(component);
+ } else if (state_manager.state.dataset === 'irradiance'){
+ promise = state_manager.setIrradianceData(component);
} else {
- promise = new Promise((fnResolve, fnReject)=>{
- component.syncFromStateManager(fnResolve);
- });
+ promise = Promise.resolve();
}
- return promise.then(()=>{ state_manager.update_in_progress = false; })
- }
-
- setHouseEnergyFromState(component){
- var state_manager = this;
- state_manager.power_data_updated = true;
- return new Promise((fnResolve, fnReject)=>{
- component.setState({
- loading_energy_data: true
- }, ()=>{
- state_manager.state.house.setEnergyData()
- .then(()=>{
- component.syncFromStateManager(fnResolve);
- });
+ return promise.then(()=>{
+ state_manager.update_in_progress = false;
+ return new Promise((fnResolve, fnReject)=>{
+ component.syncFromStateManager(fnResolve);
});
});
}
- powerDataRendered(){
+ setHouseEnergyFromState(component){
var state_manager = this;
- state_manager.power_data_updated = false;
+ return new Promise((fnResolve, fnReject)=>{
+ component.setState({
+ loading_data: 'power'
+ }, ()=>{
+ state_manager.state.house.setEnergyData(state_manager.year_range)
+ .then(fnResolve);
+ });
+ });
}
setHousePowerFromState(component){
@@ -89,11 +132,49 @@ class StateManager {
house = state_manager.state.house;
return new Promise((fnResolve, fnReject)=>{
component.setState({
- loading_power_data: true
+ loading_data: 'energy'
}, ()=>{
- house.setPowerData()
- .then(()=>{
- component.syncFromStateManager(fnResolve);
+ house.setPowerData(state_manager.state.date_interval)
+ .then(fnResolve);
+ });
+ });
+ }
+
+ setIrradianceData(component){
+ var state_manager = this,
+ houses = state_manager.houses,
+ date_interval = state_manager.state.date_interval;
+ return new Promise((fnResolve, fnReject)=>{
+ component.setState({
+ loading_data: 'irradiance'
+ }, ()=>{
+ EnergyDatum.ensureEnergyDataForHouses(houses, date_interval)
+ .then((res)=>{
+ if (res instanceof Promise){
+ throw new Error('promise returned promise')
+ }
+ var promises = [],
+ data = {};
+ houses.forEach((house)=>{
+ var promise = house.setEnergyData(date_interval)
+ .then(()=>{
+ house.energy_data.forEach((energy_datum)=>{
+ var date_data = data[energy_datum.day_to_s];
+ if (!date_data){
+ date_data = [];
+ data[energy_datum.day_to_s] = date_data;
+ }
+ date_data.push(energy_datum);
+ });
+ house.closeDb();
+ });
+ promises.push(promise);
+ });
+ Promise.all(promises)
+ .then(()=>{
+ state_manager.irradiance_data = data;
+ fnResolve();
+ });
});
});
});
@@ -105,25 +186,29 @@ class StateManager {
setParams(params){
var state_manager = this,
- url;
+ url, house, params;
if (state_manager.update_in_progress) return false;
state_manager.update_in_progress = true;
- params = Object.assign({}, state_manager.state, params);
- if (!params.house_id){
- url = '/';
- } else {
- var house = state_manager.houses.find((h)=>{ return h.data.id == params.house_id; })
- house.verifyMonthState(params);
- if (params.dataset === 'energy'){
- url = `/houses/${params.house_id}/energy/${params.year}/${params.graph_attr}/${params.view}`;
- } else if (params.dataset === 'power'){
- house.verifyPowerRange(params);
- url = `/houses/${params.house_id}/power/${params.month}/${params.year}/${params.view}?${query_string.stringify({dates: params.power_range})}`;
- } else {
- url = `/houses/${house.house_id}`;
- }
+ params = Object.assign({}, state_manager.state, params);
+ if (params.house_id){
+ house = state_manager.houses.find((h)=>{ return h.data.id == params.house_id; });
+ } else {
+ house = state_manager.state.house || state_manager.houses[0];
+ params.house_id = house.data.id;
}
+
+ house.verifyMonthState(params);
+ if (params.dataset === 'irradiance'){
+ params.date_interval = house.verifyPowerRange(params.date_interval || [], params);
+ url = `/irradiance/${params.month}/${params.year}/${params.view}?${query_string.stringify({dates: params.date_interval})}`;
+ } else if (params.dataset === 'energy'){
+ url = `/houses/${params.house_id}/energy/${params.year}/${params.graph_attr}/${params.view}`;
+ } else {
+ params.date_interval = house.verifyPowerRange(params.date_interval || [], params);
+ url = `/houses/${params.house_id}/power/${params.month}/${params.year}/${params.view}?${query_string.stringify({dates: params.date_interval})}`;
+ }
+
state_manager.history.push(url);
}
@@ -132,16 +217,28 @@ class StateManager {
*/
updateStateFromUrl(location, component){
- var state_manager = this;
- var params = state_manager.parseUrl(location.pathname),
+ var state_manager = this,
+ params = state_manager.parseUrl(location.pathname),
house = null;
- if (params.dataset === 'power' && location.query.dates) {
- params.power_range = [+location.query.dates[0], +location.query.dates[1]];
- }
- if (params.house_id || params.house_id != state_manager.state.house_id){
+ if (params.house_id){
house = state_manager.houses.find((h)=>{ return h.data.id == params.house_id; });
+ } else if (params.dataset === 'irradiance'){
+ // Irradiance needs a house to verify params and
+ house = state_manager.state.house || state_manager.houses[0];
}
- state_manager.state.house = house;
+
+ if (house){
+ // params should already be verified if set through StateManager#setParams, but
+ // verify here again before setting state in case URL manually loaded.
+ house.verifyMonthState(params);
+ if (params.dataset === 'power' || params.dataset === 'irradiance') {
+ var date_interval = location.query.dates || [];
+ params.date_interval = house.verifyPowerRange([+date_interval[0], +date_interval[1]], params);
+ }
+ state_manager.state.house = house;
+ state_manager.state.house_id = house.data.id;
+ }
+
Object.assign(state_manager.state, params);
if (state_manager.state.house_id) {
state_manager.updateHouseFromState(component);
diff --git a/client/lib/databasable.js b/client/lib/databasable.js
index abe6b67..06d5fda 100644
--- a/client/lib/databasable.js
+++ b/client/lib/databasable.js
@@ -7,19 +7,21 @@ const DEFAULTS = {
var databasable = {
- accessDb: function(db_name, opts){
- var databasable = this;
- opts = Object.assign(Object.assign({
- adapter: new LokiIndexedAdapter(db_name)
- }, DEFAULTS), opts || {});
+ accessDb: function(db_name){
+ var databasable = this,
+ opts = Object.assign({
+ adapter: new LokiIndexedAdapter(db_name)
+ }, DEFAULTS, databasable.lokijs_options || {}),
+ has_adapter = !!opts.adapter;
return new Promise((fnResolve, fnReject)=>{
if (!databasable.db) {
databasable.db = new Loki(db_name, opts);
- databasable.db.loadDatabase({}, ()=>{
- fnResolve(databasable.db);
- });
+ if (has_adapter){
+ databasable.db.loadDatabase({}, ()=>{
+ fnResolve(databasable.db);
+ });
+ } else { fnResolve(databasable.db); }
} else { fnResolve(databasable.db); }
-
});
},
@@ -34,8 +36,12 @@ var databasable = {
collection: function(db_name, collection_name, options){
var databasable = this;
+ options = options || {};
return databasable.accessDb(db_name)
.then((db)=>{
+ if (!db || db !== databasable.db){
+ throw new Error('Databasable does not have db set.')
+ }
var collection = db.getCollection(collection_name)
if (!collection){
collection = db.addCollection(collection_name, options);
diff --git a/client/models/energy_datum.js b/client/models/energy_datum.js
index 5fc32a8..603b3e2 100644
--- a/client/models/energy_datum.js
+++ b/client/models/energy_datum.js
@@ -1,6 +1,9 @@
import extend from 'extend';
import moment from 'moment-timezone';
+import DateRange from './../../shared/utils/date_range';
+import EnergyDataApi from 'api/energy_data';
+
const NAME = 'EnergyDatum';
const COLLECTION_DEFAULTS = {
indices: ['day']
@@ -29,16 +32,58 @@ class EnergyDatum {
return moment.tz(energy_datum.data.day * 1000, energy_datum.house.data.timezone).format('YYYY-MM-DD');
}
- get consumption_to_s(){
+ get irradiance(){
+ var energy_datum = this;
+ return Math.round(energy_datum.data.irradiance);
+ }
+
+ get consumption(){
var energy_datum = this;
return Math.round(energy_datum.data.consumption);
}
- get production_to_s(){
+ get production(){
var energy_datum = this;
return Math.round(energy_datum.data.production);
}
+ // This method will ensure the energy data for the passed houses while:
+ // 1. Making only 1 API.
+ // 2. Only opening 1 house LokiJs DB at a time.
+ static ensureEnergyDataForHouses(houses, date_range){
+ var new_ranges = {}, params = [];
+ houses.forEach((house)=>{
+ var query_ranges = DateRange.addRange(date_range, house.data.energy_datum_ranges || []);
+ if (query_ranges.gaps_filled.length > 0) {
+ params.push({dates: query_ranges.gaps_filled, house_id: house.data.id});
+ new_ranges[house.data.id] = query_ranges.new_ranges;
+ }
+ });
+
+ // already have all the data we need.
+ if (params.length === 0) return Promise.resolve();
+
+ // get all data needed for all houses in one call.
+ return new Promise((fnResolve, fnReject)=>{
+ EnergyDataApi.index({houses: params})
+ .then((energy_data)=>{
+ energy_data = energy_data.reduce((grouped, energy_datum)=>{
+ grouped[energy_datum.house_id] = grouped[energy_datum.house_id] || [];
+ grouped[energy_datum.house_id].push(energy_datum);
+ return grouped;
+ }, {});
+ houses.reduce((promise, house)=>{
+ return promise.then(()=>{
+ if (!energy_data[house.data.id]) return Promise.resolve();
+ return house.saveEnergyData(energy_data[house.data.id], new_ranges[house.data.id])
+ }).then(()=>{
+ house.closeDb();
+ });
+ }, Promise.resolve()).then(fnResolve);
+ });
+ })
+ }
+
}
EnergyDatum.NAME = NAME;
diff --git a/client/models/house.js b/client/models/house.js
index b059f15..83cce86 100644
--- a/client/models/house.js
+++ b/client/models/house.js
@@ -13,6 +13,7 @@ import DateRange from './../../shared/utils/date_range';
import Databasable from './../lib/databasable';
const NAME = 'House';
+const MAX_POWER_RANGE = 3600 * 24 * 4; // 4 days
class House {
@@ -28,10 +29,6 @@ class House {
for (var year=house.data_from_moment.year(); year<=house.data_until_moment.year(); year+=1){
house.years.push(year);
}
- house.setMonthState({
- month: house.data_until_moment.format('MMM'),
- year: house.data_until_moment.year()
- });
}
get data_from_moment(){
@@ -53,10 +50,14 @@ class House {
else return {};
}
+ parseMoment(s, format){
+ var house = this;
+ return moment.tz(s, format, house.data.timezone);
+ }
+
availableMonths(year){
var house = this,
all_months = moment.monthsShort();
- year = year || house.state.year.toString();
if (year === house.data_from_moment.format('YYYY')){
return all_months.slice(house.data_from_moment.month(), 12);
} else if (year === house.data_until_moment.format('YYYY')){
@@ -66,95 +67,56 @@ class House {
}
}
- // this will mutate params and set house.state.
- setMonthState(params){
- var house = this;
- house.verifyMonthState(params);
- house.state.month = params.month;
- house.state.year = params.year;
-
- var month_i = moment.monthsShort().indexOf(house.state.month),
- new_month_moment = moment.tz({year: house.state.year, month: month_i, day: 1}, house.data.timezone).startOf('month');
- if (!house.state.current_month_moment || new_month_moment.unix() !== house.state.current_month_moment.unix()){
- house.state.current_month_moment = new_month_moment;
- var end_of_month = new_month_moment.clone().endOf('month')
- house.state.end_of_current_data_moment = end_of_month > house.data_until_moment ? house.data_until_moment : end_of_month
- }
-
- house.verifyPowerRange(params);
- house.state.power_range = params.power_range;
- var energy_max = Math.min(house.state.end_of_current_data_moment.clone().endOf('year').unix(), house.data.data_until);
- house.state.energy_range = [house.state.end_of_current_data_moment.clone().startOf('year').unix(), energy_max];
- }
-
// This will mutate params.
verifyMonthState(params){
var house = this;
- params.month = params.month || house.state.month;
- params.year = params.year || house.state.year;
- if (house.state.month !== params.month || house.state.year != params.year){
- var new_year = +params.year;
- if (new_year < house.data_from_moment.year() && new_year > house.data_until_moment.year()){
- if (house.state.year) params.year = house.state.year;
- else params.year = house.years[house.years.length - 1];
- }
+ params.month = params.month || house.data_until_moment.format('MMM');
+ params.year = params.year || house.data_until_moment.year();
+ var new_year = +params.year;
+ if (new_year < house.data_from_moment.year()) params.year = house.data_from_moment.year();
+ else if (new_year > house.data_until_moment.year()) params.year = house.data_until_moment.year();
- var available_months = house.availableMonths(params.year);
- if (available_months.indexOf(params.month) < 0){
- if (house.state.month) params.month = house.state.month;
- else params.month = available_months[available_months.length - 1];
- }
+ var available_months = house.availableMonths(params.year);
+ if (available_months.indexOf(params.month) < 0){
+ params.month = available_months[available_months.length - 1];
}
}
// This will mutate params
- verifyPowerRange(params){
+ verifyPowerRange(power_range, params){
var house = this,
month_i = moment.monthsShort().indexOf(params.month),
month_moment = moment.tz({year: params.year, month: month_i, day: 1}, house.data.timezone).startOf('month'),
end_of_month = month_moment.clone().endOf('month'),
- end_of_current_data_moment = end_of_month > house.data_until_moment ? house.data_until_moment : end_of_month;
+ start_moment = month_moment < house.data_from_moment ? house.data_from_moment : month_moment,
+ end_moment = end_of_month > house.data_until_moment ? house.data_until_moment : end_of_month;
- params.power_range = params.power_range || [];
-
- var current_data_range = [month_moment.unix(), end_of_current_data_moment.unix()],
- power_min = params.power_range[0],
- power_max = params.power_range[1];
- if (params.power_range.length > 0){
- if (DateRange.inRange(params.power_range[1], current_data_range)){
- power_max = params.power_range[1];
- }
- if (DateRange.inRange(params.power_range[0], current_data_range) && params.power_range[0] < power_max){
- power_min = params.power_range[0];
- }
+ var current_data_range = [start_moment.unix(), end_moment.unix()],
+ state_power_range = house.state.power_range || [],
+ power_min = state_power_range[0],
+ power_max = state_power_range[1];
+ if (power_range[1] && DateRange.inRange(power_range[1], current_data_range)){
+ power_max = power_range[1];
+ }
+ if (power_range[0] && DateRange.inRange(power_range[0], current_data_range) && power_range[0] < power_max){
+ power_min = power_range[0];
}
if (!power_max || !DateRange.inRange(power_max, current_data_range)){
- power_max = end_of_current_data_moment.unix();
+ power_max = end_moment.unix();
}
if (!power_min ||
!DateRange.inRange(power_min, current_data_range) ||
- power_max - power_min > 3600 * 24 * 4){
- power_min = power_max - 3600 * 24 * 4;
+ power_max - power_min > MAX_POWER_RANGE){
+ power_min = power_max - MAX_POWER_RANGE;
}
- params.power_range = [power_min, power_max];
- }
-
- matchesEnergyState(params){
- var house = this;
- return params.year == house.state.year;
- }
-
- matchesPowerState(params){
- var house = this;
- return params.month === house.state.month && params.year == house.state.year &&
- house.state.power_range[0] == params.power_range[0] && house.state.power_range[1] == params.power_range[1];
+ return [power_min, power_max];
}
offset_diff(unix){
var house = this,
tz = moment.tz.zone(house.data.timezone);
- return (new Date().getTimezoneOffset() - tz.offset(unix * 1000)) * 60;
+ return (new Date(unix * 1000).getTimezoneOffset() - tz.offset(unix * 1000)) * 60;
}
toDate(unix){
@@ -176,8 +138,9 @@ class House {
});
}
- setPowerData(){
+ setPowerData(power_range){
var house = this;
+ house.state.power_range = power_range;
return house.collection(house.scoped_id, PowerDatum.NAME, PowerDatum.COLLECTION_OPTIONS)
.then((power_collection)=>{
return house.ensurePowerData()
@@ -197,7 +160,6 @@ class House {
ensurePowerData(){
var house = this,
query_ranges;
-
query_ranges = DateRange.addRange(house.state.power_range, house.data.power_datum_ranges || []);
if (query_ranges.gaps_filled.length > 0){
var params = {dates: query_ranges.gaps_filled};
@@ -222,12 +184,13 @@ class House {
})
}
- setEnergyData(){
+ setEnergyData(energy_range){
var house = this;
+ house.state.energy_range = energy_range;
return house.collection(house.scoped_id, EnergyDatum.NAME, EnergyDatum.COLLECTION_OPTIONS)
.then((energy_collection)=>{
return house.ensureEnergyData()
- .then(()=>{
+ .then((res)=>{
var params = house.rangeToLokiParams('day', house.state.energy_range);
house.energy_data = energy_collection.find(params)
.sort((pd1, pd2)=>{
@@ -244,25 +207,36 @@ class House {
var house = this,
query_ranges = DateRange.addRange(house.state.energy_range, house.data.energy_datum_ranges || []);
if (query_ranges.gaps_filled.length > 0){
- return house.getEnergyData({dates: query_ranges.gaps_filled})
- .then(()=>{
- house.data.energy_datum_ranges = query_ranges.new_ranges;
- house.save();
- });
+ return new Promise((fnResolve, fnReject)=>{
+ house.getEnergyData({dates: query_ranges.gaps_filled})
+ .then((energy_data)=>{
+ house.saveEnergyData(energy_data, query_ranges.new_ranges)
+ .then(fnResolve);
+ });
+ });
} else { return Promise.resolve(); }
}
getEnergyData(params){
var house = this;
params.house_id = house.data.id;
- return house.collection(house.scoped_id, EnergyDatum.NAME, EnergyDatum.COLLECTION_OPTIONS)
- .then((energy_collection)=>{
- return EnergyDataApi.index(params)
- .then((energy_data)=>{
- energy_collection.insert(energy_data);
- house.db.save();
- });
- })
+ return EnergyDataApi.index(params);
+ }
+
+ // save new energy data to LokiJs Db, as well as
+ // the new energy data query ranges (ie house metadata).
+ saveEnergyData(energy_data, new_ranges){
+ var house = this;
+ return new Promise((fnResolve, fnReject)=>{
+ house.collection(house.scoped_id, EnergyDatum.NAME, EnergyDatum.COLLECTION_OPTIONS)
+ .then((energy_collection)=>{
+ energy_collection.insert(energy_data);
+ house.db.save();
+ house.data.energy_datum_ranges = new_ranges;
+ house.save();
+ fnResolve();
+ });
+ });
}
// removes all energy and power data from LokiJs (memory and persisted) database.
@@ -317,6 +291,7 @@ class House {
}
House.NAME = NAME;
+House.MAX_POWER_RANGE = MAX_POWER_RANGE;
Object.assign(House, Databasable);
export default House;
diff --git a/gulpfile.babel.js b/gulpfile.babel.js
index d1100e3..843b203 100644
--- a/gulpfile.babel.js
+++ b/gulpfile.babel.js
@@ -18,19 +18,6 @@ gulp.task('generate_power_csv', function(done){
});
});
-gulp.task('generate_design_data', function(){
- var exec = require('child_process').exec,
- house_ids = yargs.argv.house_ids.split(','),
- start_date = parseInt(yargs.argv.start_date),
- end_date = parseInt(yargs.argv.end_date),
- data_generator = new DesignDataGenerator(house_ids, [start_date, end_date]);
- return DB.sync()
- .then(()=>{
- return data_generator.exec();
- });
-});
-
-
gulp.task('save_power_csv', function(done){
DB.sync().then(()=>{
PowerDataSeed.saveCsv(yargs.argv, done);
@@ -43,6 +30,15 @@ gulp.task('save_house_csv', function(done){
});
});
+gulp.task('generate_design_data', function(){
+ var house_ids = yargs.argv.house_ids.split(','),
+ start_date = parseInt(yargs.argv.start_date),
+ end_date = parseInt(yargs.argv.end_date),
+ data_generator = new DesignDataGenerator(house_ids, [start_date, end_date]);
+ return DB.sync()
+ .then(()=>{ return data_generator.exec(); });
+});
+
// right now, build only available for design.
gulp.task('build', function(done) {
var config, env;
diff --git a/karma.conf.js b/karma.conf.js
index 69ab3fb..3dfb51f 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -48,6 +48,9 @@ module.exports = function (config) {
api: __dirname + '/client/api/development',
config: __dirname + '/client/config/development'
}
+ },
+ node: {
+ fs: "empty"
}
},
});
diff --git a/server/app.express.js b/server/app.express.js
index 910d64c..5132a3f 100644
--- a/server/app.express.js
+++ b/server/app.express.js
@@ -24,10 +24,10 @@ var api = express();
DB.sync().then(()=>{
- routes(api);
-
api.use(bodyParser.json());
- api.use(bodyParser.urlencoded({ extended: false }));
+ api.use(bodyParser.urlencoded({ extended: true }));
+
+ routes(api);
api.listen(API_PORT, () => {
console.log(`API is now running on http://localhost:${API_PORT}`);
diff --git a/server/controllers/energy_controller.js b/server/controllers/energy_controller.js
index 9646461..9de9164 100644
--- a/server/controllers/energy_controller.js
+++ b/server/controllers/energy_controller.js
@@ -5,9 +5,14 @@ const NAME = 'EnergyController';
class EnergyController{
static index(req, res){
- DB.EnergyDatum.exposeForHouseAtDates(req.query.house_id, req.query.dates).then((energy_data)=>{
- res.json({data: energy_data});
- });
+ console.log ('EnergyController.index');
+ console.log(JSON.stringify(req.body))
+ console.log(JSON.stringify(req.params))
+ console.log(JSON.stringify(req.query))
+ DB.EnergyDatum.exposeForHouseAtDates(req.body)
+ .then((energy_data)=>{
+ res.json({data: energy_data});
+ });
}
}
diff --git a/server/models/energy_datum.js b/server/models/energy_datum.js
index fc62db7..cbbf3a2 100644
--- a/server/models/energy_datum.js
+++ b/server/models/energy_datum.js
@@ -32,16 +32,27 @@ var EnergyDatum = DB.sequelize.define(NAME, {
associate: ()=>{
EnergyDatum.belongsTo(DB.House);
},
- exposeForHouseAtDates: (house_id, dates)=>{
- var params = {house_id: house_id};
- extend(params, ApiHelper.datesParamToSequelize(dates, 'day'));
+ exposeForHouseAtDates: (query)=>{
+ var attributes = ['id', 'production', 'irradiance', 'consumption', 'day'],
+ params = {};
+ if (query.houses){
+ attributes.push('house_id');
+ params['$or'] = []
+ query.houses.forEach((house_query)=>{
+ var house_params = {house_id: house_query.house_id};
+ extend(house_params, ApiHelper.datesParamToSequelize(house_query.dates, 'day'));
+ params['$or'].push(house_params);
+ });
+ } else {
+ params.house_id = query.house_id;
+ extend(params, ApiHelper.datesParamToSequelize(query.dates, 'day'));
+ }
+
return EnergyDatum.findAll({
where: params,
- attributes: ['id', 'production', 'consumption', 'day']
+ attributes: attributes
}).then((energy_data)=>{
- return energy_data.map((energy_datum)=>{
- return energy_datum.dataValues;
- });
+ return energy_data.map(energy_datum => energy_datum.dataValues);
});
}
}
diff --git a/shared/utils/date_range.js b/shared/utils/date_range.js
index b56b7ed..1636bb8 100644
--- a/shared/utils/date_range.js
+++ b/shared/utils/date_range.js
@@ -17,7 +17,9 @@ class DateRange {
if (end && !DateRange.eq(end, range[0]) && DateRange.lte(end, range[0])){
new_ranges.push([last_start, end]);
new_ranges.push(range);
- gaps_filled.push([last_end, end]);
+ if (!DateRange.eq(end, last_end)){
+ gaps_filled.push([last_end, end]);
+ }
covered = true;
} else if (end && !DateRange.gte(end, range[1])) {
new_ranges.push([last_start, range[1]]);
diff --git a/spec/client/dashboard/models/house.test.js b/spec/client/dashboard/models/house.test.js
deleted file mode 100644
index b44366b..0000000
--- a/spec/client/dashboard/models/house.test.js
+++ /dev/null
@@ -1,85 +0,0 @@
-"use strict";
-
-import moment from 'moment-timezone';
-import House from './../../../../client/models/house.js';
-
-describe('house#setMonthState', ()=>{
-
- var data_until = 1456589922, // Sat, 27 Feb 2016 16:18:42 +0000
- house = new House({
- id: 1,
- name: 'Johnson',
- data_from: data_until - 3600 * 24 * 365 * 3,
- data_until: data_until,
- timezone: 'America/New_York'
- });
-
- it('is updated properly on init', ()=>{
- var current_month_moment = moment.tz({year: 2016, month: 1, day: 1}, 'America/New_York'),
- energy_min = moment.tz({year: 2016, month: 0, day: 1}, 'America/New_York').unix(),
- energy_max = data_until,
- power_min = data_until - 3600 * 24 * 4,
- power_max = data_until;
-
- expect(house.state.month).toEqual('Feb');
- expect(house.state.year).toEqual(2016);
- expect(house.state.current_month_moment.unix()).toEqual(current_month_moment.unix());
- expect(house.state.energy_range).toEqual([energy_min, energy_max]);
- expect(house.state.power_range).toEqual([power_min, power_max]);
- });
-
- it('is not updated when passed no params', ()=>{
- var current_month_moment = moment.tz({year: 2016, month: 1, day: 1}, 'America/New_York'),
- energy_min = moment.tz({year: 2016, month: 0, day: 1}, 'America/New_York').unix(),
- energy_max = data_until,
- power_min = data_until - 3600 * 24 * 4,
- power_max = data_until;
-
- house.setMonthState({});
- expect(house.state.month).toEqual('Feb');
- expect(house.state.year).toEqual(2016);
- expect(house.state.current_month_moment.unix()).toEqual(current_month_moment.unix());
- expect(house.state.energy_range).toEqual([energy_min, energy_max]);
- expect(house.state.power_range).toEqual([power_min, power_max]);
- });
-
- it('is updated properly when passed power params', ()=>{
- var current_month_moment = moment.tz({year: 2015, month: 2, day: 1}, 'America/New_York'),
- energy_min = moment.tz({year: 2015, month: 0, day: 1}, 'America/New_York').unix(),
- energy_max = moment.tz({year: 2015, month: 0, day: 1}, 'America/New_York').endOf('year').unix(),
- power_max = current_month_moment.clone().endOf('month').subtract(3, 'days').unix(),
- power_min = current_month_moment.clone().endOf('month').subtract(6, 'days').unix()
-
- house.setMonthState({
- month: 'Mar',
- year: 2015,
- power_range: [ power_min, power_max ]
- });
-
- expect(house.state.month).toEqual('Mar');
- expect(house.state.year).toEqual(2015);
- expect(house.state.current_month_moment.unix()).toEqual(current_month_moment.unix());
- expect(house.state.energy_range).toEqual([energy_min, energy_max]);
- expect(house.state.power_range).toEqual([power_min, power_max]);
- });
-
- it('is updated properly when passed energy params', ()=>{
- var current_month_moment = moment.tz({year: 2014, month: 9, day: 1}, 'America/New_York'),
- energy_min = moment.tz({year: 2014, month: 0, day: 1}, 'America/New_York').unix(),
- energy_max = moment.tz({year: 2014, month: 0, day: 1}, 'America/New_York').endOf('year').unix(),
- power_max = moment.tz({year: 2014, month: 9, day: 1}, 'America/New_York').endOf('month').unix(),
- power_min = power_max - 3600 * 24 * 4;
-
- house.setMonthState({
- month: 'Oct',
- year: 2014
- });
-
- expect(house.state.month).toEqual('Oct');
- expect(house.state.year).toEqual(2014);
- expect(house.state.current_month_moment.unix()).toEqual(current_month_moment.unix());
- expect(house.state.energy_range).toEqual([energy_min, energy_max]);
- expect(house.state.power_range).toEqual([power_min, power_max]);
- });
-
-});
diff --git a/spec/client/lib/databasable.test.js b/spec/client/lib/databasable.test.js
new file mode 100644
index 0000000..12f093e
--- /dev/null
+++ b/spec/client/lib/databasable.test.js
@@ -0,0 +1,65 @@
+import Loki from 'lokijs/src/lokijs';
+
+import Databasable from './../../../client/lib/databasable';
+
+class DbClass {
+ constructor(){
+ Object.assign(this, Databasable);
+ }
+
+ get lokijs_options(){
+ return {
+ adapter: null
+ };
+ }
+
+ doSomethingWithCollection(){
+ var db_class = this;
+ return db_class.collection('yadadb', 'yada_collection')
+ .then((collection)=>{
+ db_class.collection = collection;
+ })
+ .then(()=>{
+ db_class.worked = db_class.collection instanceof Loki.Collection;
+ });
+ }
+
+}
+
+var db_class;
+
+describe('Databasable', ()=>{
+ beforeEach(()=>{
+ db_class = new DbClass();
+ });
+
+ describe('Databasable#accessDb', ()=>{
+ it('should initiate a new database', (done)=>{
+ db_class.accessDb('yadadb')
+ .then(()=>{
+ expect(db_class.db instanceof Loki).toEqual(true);
+ done();
+ });
+ });
+ });
+
+ describe('Databasable#collection', ()=>{
+ it('should initiate a new database & collection', (done)=>{
+ db_class.collection('yadadb', 'yada_collection')
+ .then((collection)=>{
+ expect(db_class.db instanceof Loki).toEqual(true);
+ expect(collection instanceof Loki.Collection).toEqual(true);
+ done();
+ });
+ });
+ it('works asynchronously', (done)=>{
+ db_class.doSomethingWithCollection()
+ .then(()=>{
+ expect(db_class.collection instanceof Loki.Collection).toEqual(true);
+ expect(db_class.worked).toEqual(true);
+ done();
+ })
+ });
+ });
+
+});
diff --git a/spec/client/models/house.test.js b/spec/client/models/house.test.js
new file mode 100644
index 0000000..f8ce51a
--- /dev/null
+++ b/spec/client/models/house.test.js
@@ -0,0 +1,76 @@
+"use strict";
+
+import moment from 'moment-timezone';
+import House from './../../../client/models/house.js';
+
+var data_until = 1456589922, // Sat, 27 Feb 2016 16:18:42 +0000
+ house = new House({
+ id: 1,
+ name: 'Johnson',
+ data_from: data_until - 3600 * 24 * 365 * 3, // 3 years before
+ data_until: data_until,
+ timezone: 'America/New_York'
+ });
+
+describe('House#state', ()=>{
+
+ it('has no state after init', ()=>{
+ expect(house.state).toEqual({});
+ });
+
+});
+
+describe('house#verifyMonthState', ()=>{
+
+ it('verifies to data_until month and year by default', ()=>{
+ var params = {};
+ house.verifyMonthState(params);
+ expect(params.month).toEqual('Feb');
+ expect(params.year).toEqual(2016);
+ });
+
+ it('verifies properly when passed valid params', ()=>{
+ var params = {
+ month: 'Mar',
+ year: 2015
+ };
+ house.verifyMonthState(params);
+ expect(params.month).toEqual('Mar');
+ expect(params.year).toEqual(2015);
+ });
+
+ it('corrects for params outside of data range', ()=>{
+ var params = {
+ month: 'Mar',
+ year: 2006
+ };
+ house.verifyMonthState(params);
+
+ expect(params.month).toEqual('Mar');
+ expect(params.year).toEqual(2013);
+ });
+
+});
+describe('House#verifyPowerRange', ()=>{
+
+ it('defaults to last four days of data', ()=>{
+ var power_max = house.data.data_until,
+ power_min = power_max - House.MAX_POWER_RANGE,
+ power_range = house.verifyPowerRange([], {month: 'Feb', year: 2016});
+
+ expect(power_range).toEqual([power_min, power_max]);
+ });
+
+ it('otherwise verifies power range to max 4 day range', ()=>{
+ var power_max = moment.tz({year: 2014, month: 9, day: 1}, 'America/New_York').endOf('month').unix(),
+ invalid_power_min = power_max - House.MAX_POWER_RANGE - 10,
+ valid_power_min = power_max - House.MAX_POWER_RANGE,
+ power_range = house.verifyPowerRange([invalid_power_min, power_max], {
+ month: 'Oct',
+ year: 2014
+ });
+
+ expect(power_range).toEqual([valid_power_min, power_max]);
+ });
+
+});
diff --git a/spec/shared/utils/date_range.test.js b/spec/shared/utils/date_range.test.js
index cbe3dea..8d71552 100644
--- a/spec/shared/utils/date_range.test.js
+++ b/spec/shared/utils/date_range.test.js
@@ -421,4 +421,14 @@ describe('DateRange.addRange', ()=>{
});
});
+ describe('overlapping in the middle', ()=>{
+ it('should not return any new ranges', ()=>{
+ var new_range = [date1, date2],
+ ranges = [[date1, date2], [date3, date4], [date5, date6]],
+ result = DateRange.addRange(new_range, ranges);
+ expect(result.gaps_filled).toEqual([]);
+ expect(result.new_ranges).toEqual([[date1, date2], [date3, date4], [date5, date6]]);
+ });
+ });
+
});
diff --git a/spec/support/jasmine.json b/spec/support/jasmine.json
deleted file mode 100644
index bcaf67b..0000000
--- a/spec/support/jasmine.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "spec_dir": "spec",
- "spec_files": [
- "**/*.test.js"
- ],
- "helpers": [
- "../node_modules/babel-core/register.js",
- "helpers/**/*.js"
- ],
- "stopSpecOnExpectationFailure": false,
- "random": false
-}