diff --git a/src/networking/index.js b/src/networking/index.js index c702212..c58a742 100644 --- a/src/networking/index.js +++ b/src/networking/index.js @@ -91,6 +91,14 @@ class HttpService { return this._get(`routines/${routine.id}/log_entries`) } + getRoutineCalculations (routine) { + return this._get(`routines/${routine.id}/readings/calculations`) + } + + calculationsToCsv (routine) { + return `${process.env.REACT_APP_BASE_API_URL}/routines/${routine.id}/readings/calculations_to_csv` + } + createRoutine (routine) { return this._post('routines', { routine }) } diff --git a/src/presentation/common/sensor_chart/index.js b/src/presentation/common/sensor_chart/index.js index 1b0c3c7..a712b95 100644 --- a/src/presentation/common/sensor_chart/index.js +++ b/src/presentation/common/sensor_chart/index.js @@ -6,7 +6,7 @@ import LineChart from '../../common/chart/line' const MAGNITUDE_COLORS = { temp: '#DB9439', ph: '#8DB5B2', - observancy: '#C6625B', + product: '#C6625B', substratum: '#739E53', biomass: '#A37EA0' } diff --git a/src/presentation/common/timeline_navigation_chart/index.js b/src/presentation/common/timeline_navigation_chart/index.js index 90783bd..6b5d803 100644 --- a/src/presentation/common/timeline_navigation_chart/index.js +++ b/src/presentation/common/timeline_navigation_chart/index.js @@ -40,7 +40,7 @@ export default class TimelineNavigationChart extends Component { datasets={[ { data: this.props.timeline.temp, stroke: '#DB9439', label: 'Temperatura' }, { data: this.props.timeline.ph, stroke: '#8DB5B2', label: 'pH' }, - { data: this.props.timeline.observancy, stroke: '#C6625B', label: 'Observancia' }, + { data: this.props.timeline.product, stroke: '#C6625B', label: 'Producto' }, { data: this.props.timeline.substratum, stroke: '#739E53', label: 'Sustrato' }, { data: this.props.timeline.biomass, stroke: '#A37EA0', label: 'Biomasa' } ]} diff --git a/src/presentation/dashboard/running_routine/add_external_reading/index.js b/src/presentation/dashboard/running_routine/add_external_reading/index.js index 34fd7ed..a98a123 100644 --- a/src/presentation/dashboard/running_routine/add_external_reading/index.js +++ b/src/presentation/dashboard/running_routine/add_external_reading/index.js @@ -18,7 +18,7 @@ class AddExternalRoutine extends Component { super(props) this.state = { magnitudes: { - observancy: null, + product: null, substratum: null, biomass: null }, diff --git a/src/presentation/dashboard/running_routine/add_external_reading/presenter.js b/src/presentation/dashboard/running_routine/add_external_reading/presenter.js index d35de69..9097dc8 100644 --- a/src/presentation/dashboard/running_routine/add_external_reading/presenter.js +++ b/src/presentation/dashboard/running_routine/add_external_reading/presenter.js @@ -18,13 +18,13 @@ const AddExternalRoutinePresenter = ({ magnitudes, error, onUpdateMagnitudes, on
onUpdateMagnitudes({ ...magnitudes, observancy })} + onChange={product => onUpdateMagnitudes({ ...magnitudes, product })} /> ['observancy', 'biomass', 'substratum'].includes(magnitude) +const isExternalMagnitude = magnitude => ['product', 'biomass', 'substratum'].includes(magnitude) const ExpandedMagnitudeModal = ({ title, magnitude, timeline, onClose, onAddReadings }) => { return ( diff --git a/src/presentation/dashboard/running_routine/presenter.js b/src/presentation/dashboard/running_routine/presenter.js index f80144e..53b94ea 100644 --- a/src/presentation/dashboard/running_routine/presenter.js +++ b/src/presentation/dashboard/running_routine/presenter.js @@ -14,7 +14,7 @@ const magnitudeTitle = magnitude => { switch (magnitude) { case 'temp': return 'Temperatura' case 'ph': return 'pH' - case 'observancy': return 'Observancia' + case 'product': return 'Producto' case 'substratum': return 'Sustrato' case 'biomass': return 'Biomasa' } @@ -89,10 +89,10 @@ export default class SensorsDashboardPresenter extends Component { /> this.onClickMagnitude('observancy')} + currentValue={this.props.currentValue.product} + onClick={() => this.onClickMagnitude('product')} /> diff --git a/src/presentation/experiment/details/presenter.js b/src/presentation/experiment/details/presenter.js index 9041f9a..d02e80e 100644 --- a/src/presentation/experiment/details/presenter.js +++ b/src/presentation/experiment/details/presenter.js @@ -2,14 +2,14 @@ import React from 'react' import './styles.css' import { Grid } from 'semantic-ui-react' import moment from 'moment' +import { Link } from 'react-router-dom' import Screen from '../../common/screen' import Container from '../../common/container' import SensorChart from '../../common/sensor_chart' import Button from '../../common/button' import ButtonLink from '../../common/button/link' - -import Network from '../../../networking' +import ButtonIcon from '../../common/button/icon' import TagsList from './tags' @@ -28,6 +28,9 @@ const ExperimentPresenter = ({ routine, timeline, fetching, error, onAnalyzeData { !routine.started && } + { routine.startedDate && + Reporte + } @@ -61,16 +64,8 @@ const ExperimentPresenter = ({ routine, timeline, fetching, error, onAnalyzeData - +

Ejecución

- { routine.startedDate && - - Analizar datos - - - - - }
@@ -124,9 +119,15 @@ const ExperimentPresenter = ({ routine, timeline, fetching, error, onAnalyzeData + + + + + + { routine.readings.length > 0 && } diff --git a/src/presentation/experiment/details/styles.css b/src/presentation/experiment/details/styles.css index a5429cd..2fda705 100644 --- a/src/presentation/experiment/details/styles.css +++ b/src/presentation/experiment/details/styles.css @@ -1,3 +1,29 @@ .routineDetails .title { margin-top: 30px; -} \ No newline at end of file +} + +/* .routineDetails .chart .overlay { + display: none; +} */ + +.routineDetails .chart { + position: relative; +} + +.routineDetails .chart .overlay { + position: absolute; + background: rgba(65, 132, 202, 0.4); + border-radius: 10px; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + display: flex; +} + +.routineDetails .chart:hover .overlay { + display: flex; +} diff --git a/src/presentation/experiment/analysis/index.js b/src/presentation/experiment/execution/index.js similarity index 88% rename from src/presentation/experiment/analysis/index.js rename to src/presentation/experiment/execution/index.js index 97e6360..9b28249 100644 --- a/src/presentation/experiment/analysis/index.js +++ b/src/presentation/experiment/execution/index.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' import { connect } from 'react-redux' -import ExperimentAnalysisPresenter from './presenter' +import ExperimentExecutionPresenter from './presenter' import { selectRoutineFetchingStatus, selectSelectedRoutine @@ -17,14 +17,14 @@ import { fetchRequest } from '../../../redux/routine/actions' -class ExperimentAnalysis extends Component { +class ExperimentExecution extends Component { componentWillMount () { this.props.requestRoutine(this.props.match.params) } render () { return ( - { } } -export default connect(mapStateToProps, mapDispatchToProps)(ExperimentAnalysis) +export default connect(mapStateToProps, mapDispatchToProps)(ExperimentExecution) diff --git a/src/presentation/experiment/analysis/log_entry/index.js b/src/presentation/experiment/execution/log_entry/index.js similarity index 100% rename from src/presentation/experiment/analysis/log_entry/index.js rename to src/presentation/experiment/execution/log_entry/index.js diff --git a/src/presentation/experiment/analysis/log_entry/styles.css b/src/presentation/experiment/execution/log_entry/styles.css similarity index 100% rename from src/presentation/experiment/analysis/log_entry/styles.css rename to src/presentation/experiment/execution/log_entry/styles.css diff --git a/src/presentation/experiment/analysis/navigation_chart.js b/src/presentation/experiment/execution/navigation_chart.js similarity index 100% rename from src/presentation/experiment/analysis/navigation_chart.js rename to src/presentation/experiment/execution/navigation_chart.js diff --git a/src/presentation/experiment/analysis/presenter.js b/src/presentation/experiment/execution/presenter.js similarity index 76% rename from src/presentation/experiment/analysis/presenter.js rename to src/presentation/experiment/execution/presenter.js index e3e0f06..b6e04c7 100644 --- a/src/presentation/experiment/analysis/presenter.js +++ b/src/presentation/experiment/execution/presenter.js @@ -1,5 +1,6 @@ import React from 'react' import './styles.css' +import Network from '../../../networking' import Screen from '../../common/screen' import Container from '../../common/container' @@ -9,16 +10,23 @@ import Button from '../../common/button' import NavigationChart from './navigation_chart' import LogEntry from './log_entry' -const ExperimentAnalysisPresenter = ({ routine = {}, timeline, logEntries, fetching, error }) => { +const ExperimentExecutionPresenter = ({ routine = {}, timeline, logEntries, fetching, error }) => { return (
+

{routine.title}

- + + + + + +
+

Temperatura

-

Observancia

+

Producto

@@ -76,4 +84,4 @@ const ExperimentAnalysisPresenter = ({ routine = {}, timeline, logEntries, fetch ) } -export default ExperimentAnalysisPresenter +export default ExperimentExecutionPresenter diff --git a/src/presentation/experiment/analysis/styles.css b/src/presentation/experiment/execution/styles.css similarity index 100% rename from src/presentation/experiment/analysis/styles.css rename to src/presentation/experiment/execution/styles.css diff --git a/src/presentation/experiment/index.js b/src/presentation/experiment/index.js index e5a797e..161aae5 100644 --- a/src/presentation/experiment/index.js +++ b/src/presentation/experiment/index.js @@ -5,7 +5,8 @@ import Toolbar from '../common/toolbar' import Experiments from './list' import Experiment from './details' -import ExperimentAnalysis from './analysis' +import ExperimentExecution from './execution' +import ExperimentReport from './report' import ExperimentCreation from './upsert' const ROUTES = match => [{ @@ -21,10 +22,15 @@ const ROUTES = match => [{ component: Experiment, title: 'Experimento' }, { - path: match.url + '/:id/analysis', - component: ExperimentAnalysis, + path: match.url + '/:id/execution', + component: ExperimentExecution, itemIndex: 2, - title: 'Análisis' + title: 'Ejecución' +}, { + path: match.url + '/:id/report', + component: ExperimentReport, + itemIndex: 2, + title: 'Reporte' }] const ExperimentNavigation = ({ match }) => { diff --git a/src/presentation/experiment/report/calculation/chart/index.js b/src/presentation/experiment/report/calculation/chart/index.js new file mode 100644 index 0000000..d9a5003 --- /dev/null +++ b/src/presentation/experiment/report/calculation/chart/index.js @@ -0,0 +1,19 @@ +import React from 'react' + +import LineChart from '../../../../common/chart/line' + +const ReportChart = ({ calculation, color }) => { + return ( + y), + label: calculation.map(({ x, y }) => x), + stroke: color + }]} + height={60} + labels={calculation.map(({ x, y }) => x)} + /> + ) +} + +export default ReportChart diff --git a/src/presentation/experiment/report/calculation/index.js b/src/presentation/experiment/report/calculation/index.js new file mode 100644 index 0000000..a1853c0 --- /dev/null +++ b/src/presentation/experiment/report/calculation/index.js @@ -0,0 +1,54 @@ +import React from 'react' +import './styles.css' + +import Chart from './chart' +import Container from '../../../common/container' + +const MAGNITUDE_COLORS = { + temp: '#DB9439', + ph: '#8DB5B2', + product: '#C6625B', + substratum: '#739E53', + biomass: '#A37EA0' +} + +const CALCULATION_NAMES = { + specificProductVelocity: 'Velocidad Específica de Producto', + productVolumetricPerformance: 'Rendimiento volumétrico de Producto', + productPerformance: 'Rendimiento de Producto', + maxProductVolumetricPerformance: 'Máximo rendmiento de Producto volumétrico', + maxProductVelocity: 'Máxima velocidad de producto', + + productBiomassPerformance: 'Rendimiento de Producto Biomasa', + + specificBiomassVelocity: 'Velocidad Específica de Biomasa', + biomassVolumetricPerformance: 'Rendimiento volumétrico de Biomasa', + biomassPerformance: 'Rendimiento de Biomasa', + maxBiomassVolumetricPerformance: 'Máxima rendimiento de Biomasa volumétrica', + maxBiomassVelocity: 'Máxima velocidad de Biomasa', + + specificPhVelocity: 'Velocidad Específica de pH', + maxPhVelocity: 'Máxima velocidad de pH' +} + +const getMagnitudeName = calculationName => + Object.keys(MAGNITUDE_COLORS).find(m => calculationName.toLowerCase().includes(m)) + +const Calculation = ({ routine, calculation, max }) => { + return ( + + +

{CALCULATION_NAMES[calculation]}

+ { routine.calculations[max] && +

Máximo {routine.calculations[max].y}

+ } +
+ +
+ ) +} + +export default Calculation diff --git a/src/presentation/experiment/report/calculation/styles.css b/src/presentation/experiment/report/calculation/styles.css new file mode 100644 index 0000000..44c0bb4 --- /dev/null +++ b/src/presentation/experiment/report/calculation/styles.css @@ -0,0 +1,3 @@ +.maximum { + margin-top: 0; +} \ No newline at end of file diff --git a/src/presentation/experiment/report/index.js b/src/presentation/experiment/report/index.js new file mode 100644 index 0000000..b71a26e --- /dev/null +++ b/src/presentation/experiment/report/index.js @@ -0,0 +1,43 @@ +import React, { Component } from 'react' +import { connect } from 'react-redux' + +import ExperimentReportPresenter from './presenter' + +import { + selectRoutineFetchingStatus, selectSelectedRoutine +} from '../../../redux/routine/selector' + +import { + fetchRequest +} from '../../../redux/routine/actions' + +class ExperimentReport extends Component { + componentWillMount () { + this.props.requestRoutine(this.props.match.params) + } + + render () { + return ( + + ) + } +} + +const mapStateToProps = state => { + return { + ...selectRoutineFetchingStatus(state), + routine: selectSelectedRoutine(state) + } +} + +const mapDispatchToProps = dispatch => { + return { + requestRoutine: routine => dispatch(fetchRequest(routine)) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(ExperimentReport) diff --git a/src/presentation/experiment/report/presenter.js b/src/presentation/experiment/report/presenter.js new file mode 100644 index 0000000..e59e5a0 --- /dev/null +++ b/src/presentation/experiment/report/presenter.js @@ -0,0 +1,85 @@ +import React from 'react' +import './styles.css' +import { isEmpty } from 'lodash' +import Network from '../../../networking' +import Button from '../../common/button' + +import Screen from '../../common/screen' +import Container from '../../common/container' +import Calculation from './calculation' + +const ExperimentReportPresenter = ({ routine = {}, fetching, error }) => { + return ( + + + +

{routine.title}

+ + + + + + +
+ + { !isEmpty(routine.calculations) && +
+
+ + +

Biomasa

+
+ + + +
+ +
+ +

Producto

+
+ + + +
+ +
+ +

pH

+
+ +
+ +
+ } +
+ ) +} + +export default ExperimentReportPresenter diff --git a/src/presentation/experiment/report/styles.css b/src/presentation/experiment/report/styles.css new file mode 100644 index 0000000..8b1eaa0 --- /dev/null +++ b/src/presentation/experiment/report/styles.css @@ -0,0 +1,12 @@ +@value border from '../../constants/border.css'; + + +.experimentReport .magnitudeCalculations:not(:last-child) { + border-bottom: border; + padding-bottom: 15px; + margin-bottom: 25px; +} + +.experimentReport .magnitudeCalculations:last-child { + margin-bottom: 25px; +} diff --git a/src/redux/reading/actions.js b/src/redux/reading/actions.js index 311cdd7..4423ae0 100644 --- a/src/redux/reading/actions.js +++ b/src/redux/reading/actions.js @@ -11,7 +11,7 @@ import { export const addReading = reading => ({ type: ADD_READING, reading }) -export const createExternalReadingRequest = ({ routine, observancy, substratum, biomass }) => ({ type: CREATE_EXTERNAL_READING_REQUEST, routine, reading: { observancy, substratum, biomass } }) +export const createExternalReadingRequest = ({ routine, product, substratum, biomass }) => ({ type: CREATE_EXTERNAL_READING_REQUEST, routine, reading: { product, substratum, biomass } }) export const createExternalReadingFailure = error => ({ type: CREATE_EXTERNAL_READING_FAILURE, error }) export const createExternalReadingSuccess = reading => ({ type: CREATE_EXTERNAL_READING_SUCCESS, reading }) diff --git a/src/redux/reading/helper.js b/src/redux/reading/helper.js index 101738f..e8d154f 100644 --- a/src/redux/reading/helper.js +++ b/src/redux/reading/helper.js @@ -21,7 +21,7 @@ export const groupReadingsByDateFormat = (readings, dateFormat) => { readingsAmount = 0, totalTemp = 0, totalPH = 0, - totalObservancy = 0, + totalProduct = 0, totalSubstratum = 0, totalBiomass = 0, totalDate = 0, @@ -33,20 +33,20 @@ export const groupReadingsByDateFormat = (readings, dateFormat) => { readingsAmount: readingsAmount + 1, totalTemp: totalTemp + reading.temp, totalPH: totalPH + reading.ph, - totalObservancy: totalObservancy + reading.observancy, + totalProduct: totalProduct + reading.product, totalSubstratum: totalSubstratum + reading.substratum, totalBiomass: totalBiomass + reading.biomass, totalDate: totalDate + moment(reading.insertedAt).valueOf() } return readingsGroup }, {}) - return Object.entries(readingsGroupedByDate).map(([date, { readingsIds, readingsAmount, totalTemp, totalPH, totalObservancy, totalSubstratum, totalBiomass, routineId, totalDate }], index) => ({ + return Object.entries(readingsGroupedByDate).map(([date, { readingsIds, readingsAmount, totalTemp, totalPH, totalProduct, totalSubstratum, totalBiomass, routineId, totalDate }], index) => ({ routineId, readingsIds, id: index, temp: totalTemp / readingsAmount, ph: totalPH / readingsAmount, - observancy: totalObservancy / readingsAmount, + product: totalProduct / readingsAmount, substratum: totalSubstratum / readingsAmount, biomass: totalBiomass / readingsAmount, insertedAt: date, diff --git a/src/redux/reading/redux/entity.js b/src/redux/reading/redux/entity.js index 17eed27..a925f50 100644 --- a/src/redux/reading/redux/entity.js +++ b/src/redux/reading/redux/entity.js @@ -32,7 +32,7 @@ const addReading = (state, { reading }) => insertedAt: reading.insertedAt, temp: reading.temp || 0, ph: reading.ph || 0, - observancy: reading.observancy || 0, + product: reading.product || 0, biomass: reading.biomass || 0, substratum: reading.substratum || 0 }) @@ -68,7 +68,7 @@ const replaceRoutineReadings = (state, { routine, readings }) => insertedAt: reading.insertedAt, temp: reading.temp, ph: reading.ph, - observancy: reading.observancy, + product: reading.product, biomass: reading.biomass, substratum: reading.substratum }))), diff --git a/src/redux/reading/selector.js b/src/redux/reading/selector.js index eeba931..c4f4855 100644 --- a/src/redux/reading/selector.js +++ b/src/redux/reading/selector.js @@ -77,7 +77,7 @@ const getTimeline = readings => { labels: normalizedReadings.map(({ insertedAt }) => insertedAt), temp: normalizedReadings.map(({ temp }) => temp), ph: normalizedReadings.map(({ ph }) => ph), - observancy: normalizedReadings.map(({ observancy }) => observancy), + product: normalizedReadings.map(({ product }) => product), biomass: normalizedReadings.map(({ biomass }) => biomass), substratum: normalizedReadings.map(({ substratum }) => substratum) } diff --git a/src/redux/routine/action_types.js b/src/redux/routine/action_types.js index e76fedd..5803440 100644 --- a/src/redux/routine/action_types.js +++ b/src/redux/routine/action_types.js @@ -10,6 +10,10 @@ export const FETCH_REQUEST = 'ROUTINES.FETCH_REQUEST' export const FETCH_FAILURE = 'ROUTINES.FETCH_FAILURE' export const FETCH_SUCCESS = 'ROUTINES.FETCH_SUCCESS' +export const FETCH_ROUTINE_CALCULATIONS_REQUEST = 'ROUTINES.FETCH_ROUTINE_CALCULATIONS_REQUEST' +export const FETCH_ROUTINE_CALCULATIONS_FAILURE = 'ROUTINES.FETCH_ROUTINE_CALCULATIONS_FAILURE' +export const FETCH_ROUTINE_CALCULATIONS_SUCCESS = 'ROUTINES.FETCH_ROUTINE_CALCULATIONS_SUCCESS' + export const FETCH_ROUTINES_REQUEST = 'ROUTINES.FETCH_ROUTINES_REQUEST' export const FETCH_ROUTINES_FAILURE = 'ROUTINES.FETCH_ROUTINES_FAILURE' export const FETCH_ROUTINES_SUCCESS = 'ROUTINES.FETCH_ROUTINES_SUCCESS' diff --git a/src/redux/routine/actions.js b/src/redux/routine/actions.js index f07ddb2..19ec048 100644 --- a/src/redux/routine/actions.js +++ b/src/redux/routine/actions.js @@ -48,7 +48,11 @@ import { UPSERT_START_CREATION, UPSERT_START_EDITION, - UPSERT_SUBMIT + UPSERT_SUBMIT, + + FETCH_ROUTINE_CALCULATIONS_REQUEST, + FETCH_ROUTINE_CALCULATIONS_FAILURE, + FETCH_ROUTINE_CALCULATIONS_SUCCESS } from './action_types' export const stopRunningRoutineRequest = () => ({ type: STOP_ROUTINE_REQUEST }) @@ -67,6 +71,10 @@ export const fetchRequest = routine => ({ type: FETCH_REQUEST, routine }) export const fetchFailure = error => ({ type: FETCH_FAILURE, error }) export const fetchSuccess = routine => ({ type: FETCH_SUCCESS, routine }) +export const fetchRoutineCalculationsRequest = routine => ({ type: FETCH_ROUTINE_CALCULATIONS_REQUEST, routine }) +export const fetchRoutineCalculationsFailure = error => ({ type: FETCH_ROUTINE_CALCULATIONS_FAILURE, error }) +export const fetchRoutineCalculationsSuccess = (routine, calculations) => ({ type: FETCH_ROUTINE_CALCULATIONS_SUCCESS, routine, calculations }) + export const createRoutineRequest = routine => ({ type: CREATE_ROUTINE_REQUEST, routine }) export const createRoutineFailure = error => ({ type: CREATE_ROUTINE_FAILURE, error }) export const createRoutineSuccess = routine => ({ type: CREATE_ROUTINE_SUCCESS, routine }) diff --git a/src/redux/routine/redux/action_status.js b/src/redux/routine/redux/action_status.js index 83a0ed4..15d327b 100644 --- a/src/redux/routine/redux/action_status.js +++ b/src/redux/routine/redux/action_status.js @@ -33,7 +33,8 @@ const requestReducer = buildActionStatusReducer({ 'ROUTINES.START_ROUTINE', 'ROUTINES.STOP_ROUTINE', 'ROUTINES.SEARCH', - 'ROUTINES.SET_SELECTED_ROUTINE' + 'ROUTINES.SET_SELECTED_ROUTINE', + 'ROUTINES.FETCH_ROUTINE_CALCULATIONS' ] }) diff --git a/src/redux/routine/redux/entity.js b/src/redux/routine/redux/entity.js index 45b1999..78b01d9 100644 --- a/src/redux/routine/redux/entity.js +++ b/src/redux/routine/redux/entity.js @@ -6,7 +6,8 @@ import { START_ROUTINE_SUCCESS, CREATE_ROUTINE_SUCCESS, UPDATE_ROUTINE_SUCCESS, - DESTROY_ROUTINE_SUCCESS + DESTROY_ROUTINE_SUCCESS, + FETCH_ROUTINE_CALCULATIONS_SUCCESS } from '../action_types' import * as readingActionTypes from '../../reading/action_types' import * as routineLogEntryActionTypes from '../../routine_log_entry/action_types' @@ -30,6 +31,7 @@ const routinesById = (state = INITIAL_STATE_BY_ID, action) => { case FETCH_ROUTINES_SUCCESS: return replaceRoutines(state, action) case CREATE_ROUTINE_SUCCESS: return addRoutine(state, action) case UPDATE_ROUTINE_SUCCESS: return updateRoutine(state, action) + case FETCH_ROUTINE_CALCULATIONS_SUCCESS: return addRoutineCalculations(state, action) case FETCH_SUCCESS: return updateRoutine(state, action) case START_ROUTINE_SUCCESS: return startRoutine(state, action) case DESTROY_ROUTINE_SUCCESS: return removeRoutine(state, action) @@ -45,6 +47,7 @@ const routinesById = (state = INITIAL_STATE_BY_ID, action) => { const replaceRoutines = (state, { routines }) => replaceByIdEntries(state, routines.reverse().map(routine => ({ ...routine, + calculations: {}, readings: [], logEntries: [] }))) @@ -52,6 +55,7 @@ const replaceRoutines = (state, { routines }) => const addRoutine = (state, { routine }) => addByIdEntry(state, { ...routine, + calculations: {}, readings: [], logEntries: [] }) @@ -59,10 +63,17 @@ const addRoutine = (state, { routine }) => const updateRoutine = (state, { routine }) => updateByIdEntry(state, { ...routine, + calculations: (state[routine.id] || {}).calculations || {}, readings: (state[routine.id] || {}).readings || [], logEntries: (state[routine.id] || {}).logEntries || [] }) +const addRoutineCalculations = (state, { routine, calculations }) => + updateByIdEntry(state, { + ...routine, + calculations + }) + const startRoutine = (state, { routine }) => updateByIdEntry(state, { ...routine, diff --git a/src/redux/routine/sagas/index.js b/src/redux/routine/sagas/index.js index 2f5ef24..a5b2553 100644 --- a/src/redux/routine/sagas/index.js +++ b/src/redux/routine/sagas/index.js @@ -8,11 +8,13 @@ import { FETCH_ROUTINES_REQUEST, SET_SELECTED_ROUTINE, FETCH_REQUEST, + FETCH_SUCCESS, CREATE_ROUTINE_REQUEST, UPDATE_ROUTINE_REQUEST, DESTROY_ROUTINE_REQUEST, UPSERT_SUBMIT, - SEARCH_REQUEST + SEARCH_REQUEST, + FETCH_ROUTINE_CALCULATIONS_REQUEST } from '../action_types' import { performStopRoutine, @@ -24,7 +26,8 @@ import { performFetchRoutine, performSubmitUpsert, performSearchRoutines, - performResumeRunningRoutine + performResumeRunningRoutine, + performFetchRoutineCalculations } from './perform' import performRoutineChannelConnection from './perform_routine_channel_connection' @@ -33,6 +36,8 @@ export default [ takeEvery('BOOTED', performResumeRunningRoutine, httpService), takeEvery(STOP_ROUTINE_REQUEST, performStopRoutine, httpService), takeEvery(UPSERT_SUBMIT, performSubmitUpsert), + takeEvery(FETCH_ROUTINE_CALCULATIONS_REQUEST, performFetchRoutineCalculations, httpService), + takeEvery(FETCH_SUCCESS, performFetchRoutineCalculations, httpService), takeEvery(START_ROUTINE_REQUEST, performStartRoutine, httpService), takeEvery(FETCH_ROUTINES_REQUEST, performFetchRoutines, httpService), takeEvery(FETCH_REQUEST, performFetchRoutine, httpService), diff --git a/src/redux/routine/sagas/perform.js b/src/redux/routine/sagas/perform.js index 981443a..f1d325c 100644 --- a/src/redux/routine/sagas/perform.js +++ b/src/redux/routine/sagas/perform.js @@ -1,5 +1,6 @@ import { call, put, select } from 'redux-saga/effects' import { delay } from 'redux-saga' +import { sortBy, mapValues } from 'lodash' import { fetchRoutinesFailure, fetchRoutinesSuccess, @@ -18,7 +19,9 @@ import { stopRunningRoutineFailure, stopRunningRoutineSuccess, searchFailure, - searchSuccess + searchSuccess, + fetchRoutineCalculationsSuccess, + fetchRoutineCalculationsFailure } from '../actions' import { @@ -65,6 +68,21 @@ export function * performFetchRoutine (httpService, { routine }) { } } +export function * performFetchRoutineCalculations (httpService, { routine }) { + try { + const response = yield call([httpService, 'getRoutineCalculations'], routine) + const calculations = mapValues(response.data.data, (value, key) => { + if (key.toLowerCase().includes('max')) { + return value + } + return sortBy(value, 'x') + }) + yield put(fetchRoutineCalculationsSuccess(routine, calculations)) + } catch (error) { + yield put(fetchRoutineCalculationsFailure(error)) + } +} + export function * performSubmitUpsert () { const { operation, routine, tempRanges } = yield select(selectUpsertActionStatus) if (operation === 'creation') { diff --git a/src/test/networking_mock.js b/src/test/networking_mock.js index 9173673..131b30f 100644 --- a/src/test/networking_mock.js +++ b/src/test/networking_mock.js @@ -62,6 +62,16 @@ class HttpServiceMock { } } + getRoutineCalculations (routine) { + return { + data: { + data: { + biomassPerformance: [] + } + } + } + } + getRoutineLogEntries (routine) { return { data: { diff --git a/src/test/routine/fetch_routine_calculations.test.js b/src/test/routine/fetch_routine_calculations.test.js new file mode 100644 index 0000000..1996b57 --- /dev/null +++ b/src/test/routine/fetch_routine_calculations.test.js @@ -0,0 +1,134 @@ +/* eslint-env jest */ + +import { call, put } from 'redux-saga/effects' +import { + FETCH_ROUTINE_CALCULATIONS_REQUEST, + FETCH_ROUTINE_CALCULATIONS_FAILURE, + FETCH_ROUTINE_CALCULATIONS_SUCCESS +} from '../../redux/routine/action_types' +import { + fetchRoutineCalculationsRequest, + fetchRoutineCalculationsFailure, + fetchRoutineCalculationsSuccess +} from '../../redux/routine/actions' +import reducer from '../../redux/routine/redux' +import { performFetchRoutineCalculations } from '../../redux/routine/sagas/perform' +import httpServiceMock from '../networking_mock' +import { merge } from '../../redux/helper' + +describe('actions', () => { + it('should create an action to request routine calculations', () => { + const routine = { id: 4, title: 'a title', strain: 30, medium: 'a medium', targetTemp: 1, targetPh: 4, estimatedTimeSeconds: 100, extraNotes: 'some notes' } + const expectedAction = { + type: FETCH_ROUTINE_CALCULATIONS_REQUEST, + routine + } + expect(fetchRoutineCalculationsRequest(routine)).toEqual(expectedAction) + }) + + it('should create an action for routine calculations fetching failure', () => { + const error = 'an error' + const expectedAction = { + type: FETCH_ROUTINE_CALCULATIONS_FAILURE, + error + } + expect(fetchRoutineCalculationsFailure(error)).toEqual(expectedAction) + }) + + it('should create an action for routine calculations fetching success', () => { + const routine = { id: 4, title: 'a title', strain: 30, medium: 'a medium', targetTemp: 1, targetPh: 4, estimatedTimeSeconds: 100, extraNotes: 'some notes' } + const calculations = { biomassPerformance: [] } + const expectedAction = { + type: FETCH_ROUTINE_CALCULATIONS_SUCCESS, + routine, + calculations + } + expect(fetchRoutineCalculationsSuccess(routine, calculations)).toEqual(expectedAction) + }) +}) + +describe('action status reducer', () => { + const INITIAL_STATE = reducer.actionStatus(undefined, {}) + const DIRTY_STATE = merge(INITIAL_STATE, { fetching: false, error: 'error' }) + + it('should handle FETCH_ROUTINE_CALCULATIONS_REQUEST', () => { + expect( + reducer.actionStatus(DIRTY_STATE, { + type: FETCH_ROUTINE_CALCULATIONS_REQUEST + }) + ).toEqual(merge(INITIAL_STATE, { + fetching: true, + error: null + })) + }) + + it('should handle FETCH_ROUTINE_CALCULATIONS_FAILURE', () => { + expect( + reducer.actionStatus(DIRTY_STATE, { + type: FETCH_ROUTINE_CALCULATIONS_FAILURE, + error: 'an error' + }) + ).toEqual(merge(INITIAL_STATE, { + fetching: false, + error: 'an error' + })) + }) + + it('should handle FETCH_ROUTINE_CALCULATIONS_SUCCESS', () => { + expect( + reducer.actionStatus(DIRTY_STATE, { + type: FETCH_ROUTINE_CALCULATIONS_SUCCESS + }) + ).toEqual(merge(INITIAL_STATE, { + fetching: false, + error: null + })) + }) +}) + +describe('entity reducer', () => { + const INITIAL_STATE = { + byId: { + 4: { id: 4, title: '3', strain: 60, medium: 'other medium', targetTemp: 1, targetPh: 4, estimatedTimeSeconds: 100, extraNotes: 'some notes', readings: [5] } + }, + allIds: [4] + } + + it('should handle FETCH_ROUTINE_CALCULATIONS_SUCCESS', () => { + const routine = { id: 4, title: 'a title', strain: 30, medium: 'a medium', targetTemp: 1, targetPh: 4, estimatedTimeSeconds: 100, extraNotes: 'some notes' } + const calculations = { biomassPerformance: [] } + expect( + reducer.entity(INITIAL_STATE, { + type: FETCH_ROUTINE_CALCULATIONS_SUCCESS, + routine, + calculations + }) + ).toEqual({ + byId: { + 4: { + ...routine, + calculations, + readings: [5] + } + }, + allIds: [4] + }) + }) +}) + +describe('sagas', () => { + it('perfom fetch routine calculations success', () => { + const routine = { title: 'a title', strain: 30, medium: 'a medium', targetTemp: 1, targetPh: 4, estimatedTimeSeconds: 100, extraNotes: 'some notes' } + const iterator = performFetchRoutineCalculations(httpServiceMock, { routine }) + const response = httpServiceMock.getRoutineCalculations(routine) + expect(iterator.next().value).toEqual(call([httpServiceMock, 'getRoutineCalculations'], routine)) + expect(iterator.next(response).value).toEqual(put(fetchRoutineCalculationsSuccess(routine, response.data.data))) + }) + + it('perfom fetch routine calculations failure', () => { + const routine = { title: 'a title', strain: 30, medium: 'a medium', targetTemp: 1, targetPh: 4, estimatedTimeSeconds: 100, extraNotes: 'some notes' } + const iterator = performFetchRoutineCalculations(httpServiceMock, { routine }) + expect(iterator.next().value).toEqual(call([httpServiceMock, 'getRoutineCalculations'], routine)) + expect(iterator.throw('an error').value).toEqual(put(fetchRoutineCalculationsFailure('an error'))) + }) +})