create irradiance composite graph

This commit is contained in:
Eric Hulburd
2016-03-11 17:38:03 -06:00
parent 2dd9389694
commit 782f5cbf91
32 changed files with 824 additions and 379 deletions

View File

@@ -6,8 +6,10 @@ class EnergyDataApi {
static index(params){
return jQuery.ajax({
url: ENDPOINT + '?' + jQuery.param(params),
type: 'GET',
url: ENDPOINT,
data: JSON.stringify(params),
contentType: 'application/json',
type: 'POST',
dataType: 'json'
}).then((res)=>{
return res.data;

View File

@@ -7,6 +7,9 @@ import layoutRt from './../../dashboard/layout/layout.rt';
import energyRt from './../../dashboard/energy/energy.rt';
import energyGraphRt from './../../dashboard/energy/graph/graph.rt';
import energyTableRt from './../../dashboard/energy/table/table.rt';
import irradianceRt from './../../dashboard/irradiance/irradiance.rt';
import irradianceGraphRt from './../../dashboard/irradiance/graph/graph.rt';
import irradianceTableRt from './../../dashboard/irradiance/table/table.rt';
import powerRt from './../../dashboard/power/power.rt';
import powerGraphRt from './../../dashboard/power/graph/graph.rt';
import powerTableRt from './../../dashboard/power/table/table.rt';
@@ -16,6 +19,9 @@ const TEMPLATES = {
energy: energyRt,
energy_graph: energyGraphRt,
energy_table: energyTableRt,
irradiance: irradianceRt,
irradiance_graph: irradianceGraphRt,
irradiance_table: irradianceTableRt,
power: powerRt,
power_graph: powerGraphRt,
power_table: powerTableRt

View File

@@ -1,9 +1,6 @@
<rt-require dependency="./graph/graph.component" as="EnergyGraph"/>
<rt-require dependency="./table/table.component" as="EnergyTable"/>
<div id="energy_view">
<div class="alert alert-warning" rt-if="this.loading_energy_data">
Retrieving energy data...
</div>
<div rt-if="this.props.view === 'graph'">
<h4>Select Data</h4>
<div class="btn-group" role="group">

View File

@@ -1,4 +1,5 @@
import React from 'react';
import c3 from 'c3';
import Templates from 'config/templates';
import CalendarGridChart from './../../../d3/grid/calendar_grid';

View File

@@ -4,6 +4,7 @@
<th></th>
<th>Day</th>
<th>Consumption (kWh)</th>
<th>Daily Mean Irradiance (W/m<sup>2</sup>)</th>
<th>Production (kWh)</th>
</tr>
</thead>
@@ -11,8 +12,9 @@
<tr rt-repeat="energy_datum in this.house.energy_data" key="{energy_datum.scoped_id}">
<td></td>
<td>{energy_datum.day_to_s}</td>
<td>{energy_datum.consumption_to_s}</td>
<td>{energy_datum.production_to_s}</td>
<td>{energy_datum.consumption}</td>
<td>{energy_datum.irradiance}</td>
<td>{energy_datum.production}</td>
</tr>
</tbody>
</table>

View File

@@ -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;

View File

@@ -0,0 +1 @@
<div id="irradiance_graph"></div>

View File

@@ -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;

View File

@@ -0,0 +1,13 @@
<rt-require dependency="./graph/graph.component" as="IrradianceGraph"/>
<rt-require dependency="./table/table.component" as="IrradianceTable"/>
<div id="irradiance_view">
<h4>Irradiance</h4>
<IrradianceGraph
rt-if="this.props.view === 'graph'"
state_manager="{this.state_manager}"
date_interval="{this.props.date_interval}" />
<IrradianceTable
rt-if="this.props.view === 'table'"
state_manager="{this.state_manager}"
date_interval="{this.props.date_interval}" />
</div>

View File

@@ -0,0 +1,3 @@
#irradiance_component {
}

View File

@@ -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;

View File

@@ -0,0 +1,18 @@
<table id="irradiance_table" class="table">
<thead>
<tr>
<th>Day</th>
<th>House</th>
<th>Daily Mean Irradiance (W/m<sup>2</sup>)</th>
<th>Production (kWh)</th>
</tr>
</thead>
<tbody rt-repeat="day in Object.keys(this.state_manager.irradiance_data)" key="irradiance-date-{day}">
<tr rt-repeat="energy_datum in this.state_manager.irradiance_data[day]" key="{energy_datum.scoped_id}">
<td>{energy_datum.day_to_s}</td>
<td>{energy_datum.house.data.name}</td>
<td>{energy_datum.irradiance}</td>
<td>{energy_datum.production}</td>
</tr>
</tbody>
</table>

View File

@@ -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,

View File

@@ -1,4 +1,5 @@
<rt-require dependency="./../energy/energy.component" as="Energy"/>
<rt-require dependency="./../irradiance/irradiance.component" as="Irradiance"/>
<rt-require dependency="./../power/power.component" as="Power"/>
<div id="layout">
<div rt-if="!this.house" id="about">
@@ -9,7 +10,7 @@
<ul>
<li>React</li>
<li>React Templates</li>
<li>React Router</li>
<li>ReactJs History</li>
<li>LokiJs - persisting API calls to indexedDb</li>
<li>Webpack - hot mode developing and app bundling</li>
<li>Babel - ES6 transpiler</li>
@@ -22,15 +23,23 @@
<div class="alert alert-warning" rt-if="this.state.loading_houses">Retrieving houses...</div>
<h4>Select household:</h4>
<select id="houses_select" rt-if="this.state.houses && this.state_manager" class="form-control" onChange="{this.setHouse.bind(this)}" value="{this.house_id}">
<option rt-repeat="house in this.state.houses" value="{house.data.id}" key="{house.scoped_id}">{house.data.name}</option>
</select>
<div rt-if="this.dataset !== 'irradiance'">
<h4>Select household:</h4>
<select id="houses_select" rt-if="this.state.houses && this.state_manager" class="form-control" onChange="{this.setHouse.bind(this)}" value="{this.house_id}">
<option rt-repeat="house in this.state.houses" value="{house.data.id}" key="{house.scoped_id}">{house.data.name}</option>
</select>
</div>
<button rt-if="this.house" onClick="{this.refreshData.bind(this)}" class="btn btn-xs btn-default">Refresh House Data</button>
<div rt-if="this.house">
<div>
<h4>Select dataset:</h4>
<div class="btn-group" role="group">
<button
data-param="dataset"
data-value="power"
rt-class="{active: this.state.dataset === 'power'}"
onClick="{this.setParam.bind(this)}"
type="button" class="btn btn-primary">15-minute Power Statistics</button>
<button
data-param="dataset"
data-value="energy"
@@ -39,10 +48,10 @@
type="button" class="btn btn-primary">Daily Energy Statistics</button>
<button
data-param="dataset"
data-value="power"
rt-class="{active: this.state.dataset === 'power'}"
data-value="irradiance"
rt-class="{active: this.state.dataset === 'irradiance'}"
onClick="{this.setParam.bind(this)}"
type="button" class="btn btn-primary">15-minute Power Statistics</button>
type="button" class="btn btn-primary">Daily Mean Irradiance</button>
</div>
<h4>View as:</h4>
@@ -72,26 +81,44 @@
class="btn-info btn btn-sm"
rt-class="{active: year == this.state.year}"
onClick="{this.setParam.bind(this)}">{year}</button>
</div><br/>
<div class="btn-group" rt-if="this.state.dataset === 'power' || this.state.dataset === 'irradiance'">
<button
rt-repeat="month in this.house.availableMonths(this.state.year)"
data-param="month"
data-value="{month}"
key="data-month-{month}"
class="btn-warning btn btn-sm"
rt-class="{active: month === this.state.month}"
onClick="{this.setParam.bind(this)}">{month}</button>
</div><br/>
<div id="date_interval"></div>
<div class="alert alert-warning" rt-if="this.state.loading_data">
Retrieving {this.state.loading_data} data...
</div>
</div><br/>
<Energy
rt-if="this.should_show_energy_data"
house="{this.state.house}"
loading_energy_data="{this.state.loading_energy_data}"
state_manager="{this.state_manager}"
view="{this.state.view}"
graph_attr="{this.state.graph_attr}"
year="{this.state.year}" />
<Irradiance
rt-if="this.state.dataset === 'irradiance'"
view="{this.state.view}"
date_interval="{this.state.date_interval}"
state_manager="{this.state_manager}" />
<Power
rt-if="this.should_show_power_data"
house="{this.state.house}"
loading_power_data="{this.state.loading_power_data}"
state_manager="{this.state_manager}"
view="{this.state.view}"
month="{this.state.month}"
year="{this.state.year}"
power_range="{this.state.power_range}" />
date_interval="{this.state.date_interval}" />
</div>
</div>

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -1,33 +1,18 @@
<rt-require dependency="./graph/graph.component" as="PowerGraph"/>
<rt-require dependency="./table/table.component" as="PowerTable"/>
<div id="power_view">
<div class="btn-group">
<button
rt-if="this.house"
rt-repeat="month in this.house.availableMonths()"
data-param="month"
data-value="{month}"
key="data-month-{month}"
class="btn-warning btn btn-sm"
rt-class="{active: month === this.house.state.month}"
onClick="{this.setParam.bind(this)}">{month}</button>
</div>
<div class="alert alert-warning" rt-if="this.loading_power_data">
Retrieving power data...
</div>
<div id="power_date_setter"></div>
<PowerGraph
rt-if="this.props.view === 'graph'"
state_manager="{this.props.state_manager}"
house="{this.props.house}"
month="{this.props.month}"
year="{this.props.year}"
power_range="{this.props.power_range}" ></PowerGraph>
date_interval="{this.props.date_interval}" ></PowerGraph>
<PowerTable
rt-if="this.props.view === 'table'"
state_manager="{this.props.state_manager}"
house="{this.props.house}"
month="{this.props.month}"
year="{this.props.year}"
power_range="{this.props.power_range}" ></PowerTable>
date_interval="{this.props.date_interval}" ></PowerTable>
</div>

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -48,6 +48,9 @@ module.exports = function (config) {
api: __dirname + '/client/api/development',
config: __dirname + '/client/config/development'
}
},
node: {
fs: "empty"
}
},
});

View File

@@ -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}`);

View File

@@ -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});
});
}
}

View File

@@ -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);
});
}
}

View File

@@ -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]]);

View File

@@ -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]);
});
});

View File

@@ -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();
})
});
});
});

View File

@@ -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]);
});
});

View File

@@ -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]]);
});
});
});

View File

@@ -1,12 +0,0 @@
{
"spec_dir": "spec",
"spec_files": [
"**/*.test.js"
],
"helpers": [
"../node_modules/babel-core/register.js",
"helpers/**/*.js"
],
"stopSpecOnExpectationFailure": false,
"random": false
}