Skip to content

Commit

Permalink
Merge pull request #740 from LiskHQ/293-refactor-price-chart-api-call…
Browse files Browse the repository at this point in the history
…-to-redux-action

Refactor Price chart API call to redux action - Closes #293
  • Loading branch information
faival committed Apr 24, 2018
2 parents 57acc6e + b52350a commit 1542095
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 46 deletions.
25 changes: 25 additions & 0 deletions src/actions/liskService.js
@@ -0,0 +1,25 @@
import actionTypes from '../constants/actions';
import liskServiceApi from '../../src/utils/api/liskService';

export const addDataToCurrencyGraph = data => ({
type: actionTypes.addDataToCurrencyGraph,
data,
});

export const addErrorToCurrencyGraph = data => ({
type: actionTypes.addErrorToCurrencyGraph,
data,
});

export const clearDataOfCurrencyGraph = () => ({
type: actionTypes.clearDataOfCurrencyGraph,
});

export const getCurrencyGraphData = step => (dispatch) => {
dispatch(clearDataOfCurrencyGraph());
liskServiceApi.getCurrencyGraphData(step).then((response) => {
dispatch(addDataToCurrencyGraph({ response, step }));
}).catch((error) => {
dispatch(addErrorToCurrencyGraph(error));
});
};
45 changes: 22 additions & 23 deletions src/components/dashboard/currencyGraph.js
@@ -1,12 +1,12 @@
import { connect } from 'react-redux';
import { Line as LineChart, Chart } from 'react-chartjs-2';
import { translate } from 'react-i18next';
import BigNumber from 'bignumber.js';
import moment from 'moment';
import React from 'react';

import EmptyState from '../emptyState';
import liskServiceApi from '../../utils/api/liskService';
import { getCurrencyGraphData } from '../../actions/liskService';

import EmptyState from '../emptyState';
import styles from './currencyGraph.css';

const bottomPadding = 15;
Expand Down Expand Up @@ -62,7 +62,8 @@ const chartOptions = step => ({
return `BTC ${tooltipItem[0].yLabel}`;
},
label(tooltipItem) {
return moment(tooltipItem.xLabel).format('DD MMM HH:mm:ss').replace(' 0', ' ');
return moment(tooltipItem.xLabel, 'MMMM DD YYYY h:mm:ss A')
.format('DD MMM HH:mm:ss').replace(' 0', ' ');
},
},
backgroundColor: 'rgba(255, 255, 255, 0.9)',
Expand Down Expand Up @@ -150,25 +151,15 @@ class CurrencyGraph extends React.Component {
this.state = {
step: steps[0],
};
this.updateData(this.state.step);
}

updateData(step) {
liskServiceApi.getCurrencyGrapData(step).then((response) => {
const { candles } = response;
const data = candles.slice(Math.max(candles.length - step.length, 1)).map(c => ({
x: new Date(c.date),
y: new BigNumber(c.high).plus(new BigNumber(c.low)).dividedBy(2),
}));
this.setState({ data });
}).catch((error) => {
this.setState({ error });
});
componentWillMount() {
this.props.getCurrencyGraphData(this.state.step);
}

setStep(step) {
this.setState({ step, data: undefined });
this.updateData(step);
this.props.getCurrencyGraphData(step);
}

render() {
Expand Down Expand Up @@ -199,13 +190,13 @@ class CurrencyGraph extends React.Component {
))}
</div>
<header><h2>{this.props.t('LSK/BTC')}</h2></header>
<div className={`${styles.chartWrapper}`} >
{this.state.data ?
<div className={`${styles.chartWrapper} chart-wrapper`} >
{this.props.liskService.prices ?
<LineChart
data={chartData.bind(null, this.state.data)}
options={chartOptions(this.state.step)}/> :
data={chartData.bind(null, this.props.liskService.prices.data)}
options={chartOptions(this.props.liskService.step)}/> :
null}
{this.state.error ?
{this.props.liskService.graphError ?
<EmptyState className={styles.errorMessage}
message={this.props.t('Price data currently not available')} /> :
null}
Expand All @@ -215,4 +206,12 @@ class CurrencyGraph extends React.Component {
}
}

export default translate()(CurrencyGraph);
const mapStateToProps = state => ({
liskService: state.liskService,
});

const mapDispatchToProps = dispatch => ({
getCurrencyGraphData: data => dispatch(getCurrencyGraphData(data)),
});

export default connect(mapStateToProps, mapDispatchToProps)(translate()(CurrencyGraph));
47 changes: 25 additions & 22 deletions src/components/dashboard/currencyGraph.test.js
@@ -1,55 +1,58 @@
import thunk from 'redux-thunk';
import { expect } from 'chai';
import { Line as LineChart } from 'react-chartjs-2';
import sinon from 'sinon';
import React from 'react';

import { mountWithContext } from './../../../test/utils/mountHelpers';
import { prepareStore } from '../../../test/utils/applicationInit';
import liskServiceApi from '../../utils/api/liskService';
import { mountWithContext } from '../../../test/utils/mountHelpers';

import liskServiceReducer from '../../store/reducers/liskService';
import CurrencyGraph from './currencyGraph';

describe('CurrencyGraph', () => {
let explorereApiMock;
let liskServiceApiMock;
let wrapper;
let store;

const prices = [
{ high: 0.003223542, date: '2018-02-01 13:00:00' },
{ high: 0.012344282, date: '2018-02-02 13:00:00' },
];

beforeEach(() => {
explorereApiMock = sinon.stub(liskServiceApi, 'getCurrencyGrapData').returnsPromise();
wrapper = mountWithContext(<CurrencyGraph/>, {});
liskServiceApiMock = sinon.stub(liskServiceApi, 'getCurrencyGraphData').returnsPromise();
store = prepareStore({
liskService: liskServiceReducer,
}, [thunk]);

wrapper = mountWithContext(<CurrencyGraph store={store}/>, {});
});

afterEach(() => {
explorereApiMock.restore();
liskServiceApiMock.restore();
});

it('shold render LineChart when explorer api resolves candle data', () => {
const candles = [
{ high: 0.003223542, date: '2018-02-01 13:00:00' },
{ high: 0.012344282, date: '2018-02-01 14:00:00' },
];

it('should render LineChart when explorer api resolves candle data', () => {
expect(wrapper.find('.chart-wrapper').first()).to.be.present();
expect(wrapper.find(LineChart)).not.to.be.present();
explorereApiMock.resolves({ candles });
liskServiceApiMock.resolves({ prices });
wrapper.update();
expect(wrapper.find(LineChart)).to.be.present();
});

it('shold show and error message when explorer api call fails', () => {
it('should show and error message when explorer api call fails', () => {
expect(wrapper.find(LineChart)).not.to.be.present();
explorereApiMock.rejects({ });
liskServiceApiMock.rejects({ });
expect(wrapper.find(LineChart)).not.to.be.present();
expect(wrapper.text()).to.contain('Price data currently not available');
});

it('should allow to change step', () => {
const candles = [
{ high: 0.003223542, date: '2018-02-01 13:00:00' },
{ high: 0.012344282, date: '2018-02-02 13:00:00' },
];

wrapper.find('.step').at(1).simulate('click');
expect(wrapper.find(LineChart)).not.to.be.present();
explorereApiMock.resolves({ candles });
liskServiceApiMock.resolves({ prices });
wrapper.update();
expect(wrapper.find(LineChart)).to.be.present();
});
});

3 changes: 3 additions & 0 deletions src/constants/actions.js
Expand Up @@ -24,6 +24,9 @@ const actionTypes = {
pendingVotesAdded: 'PENDING_VOTES_ADDED',
toastDisplayed: 'TOAST_DISPLAYED',
toastHidden: 'TOAST_HIDDEN',
addDataToCurrencyGraph: 'ADD_DATA_TO_CURRENCY_GRAPH',
addErrorToCurrencyGraph: 'ADD_ERROR_TO_CURRENCY_GRAPH',
clearDataOfCurrencyGraph: 'CLEAR_DATA_OF_CURRENCY_GRAPH',
loadingStarted: 'LOADING_STARTED',
loadingFinished: 'LOADING_FINISHED',
transactionAdded: 'TRANSACTION_ADDED',
Expand Down
1 change: 1 addition & 0 deletions src/store/reducers/index.js
Expand Up @@ -9,3 +9,4 @@ export { default as transaction } from './transaction';
export { default as savedAccounts } from './savedAccounts';
export { default as settings } from './settings';
export { default as delegate } from './delegate';
export { default as liskService } from './liskService';
45 changes: 45 additions & 0 deletions src/store/reducers/liskService.js
@@ -0,0 +1,45 @@
import BigNumber from 'bignumber.js';
import actionTypes from '../../constants/actions';

/**
* Converts candles response into x,y coordinate positions for the graph
* @param {Array} candlesResponse array of candles to show
* @param {Object} target step configuration
*/
const getPricesForGraph = ({ prices = [], step }) => ({
data: prices.slice(Math.max(prices.length - step.length, 1))
.map(c => ({
x: new Date(c.date),
y: new BigNumber(c.high).plus(new BigNumber(c.low)).dividedBy(2),
})),
});

const liskService = (state = [], action) => {
switch (action.type) {
case actionTypes.clearDataOfCurrencyGraph:
return {
...state,
prices: undefined,
graphError: undefined,
};
case actionTypes.addDataToCurrencyGraph:
return {
...state,
prices: getPricesForGraph({
prices: action.data.response.candles,
step: action.data.step,
}),
step: action.data.step,
graphError: undefined,
};
case actionTypes.addErrorToCurrencyGraph:
return {
...state,
graphError: action.data,
};
default:
return state;
}
};

export default liskService;
2 changes: 1 addition & 1 deletion src/utils/api/liskService.js
Expand Up @@ -4,7 +4,7 @@ const popsicle = require('popsicle');
const liskServiceUrl = 'https://service.lisk.io';

const liskServiceApi = {
getCurrencyGrapData: ({ span }) => new Promise((resolve, reject) => {
getCurrencyGraphData: ({ span }) => new Promise((resolve, reject) => {
popsicle.get(`${liskServiceUrl}/api/exchanges/getCandles?e=bittrex&d=${span}`)
.use(popsicle.plugins.parse('json'))
.then((response) => {
Expand Down
2 changes: 2 additions & 0 deletions test/integration/dashboard.test.js
Expand Up @@ -12,6 +12,7 @@ import accountReducer from '../../src/store/reducers/account';
import transactionReducer from '../../src/store/reducers/transactions';
import peersReducer from '../../src/store/reducers/peers';
import loadingReducer from '../../src/store/reducers/loading';
import liskServiceReducer from '../../src/store/reducers/liskService';
import loginMiddleware from '../../src/store/middlewares/login';
import accountMiddleware from '../../src/store/middlewares/account';
import peerMiddleware from '../../src/store/middlewares/peers';
Expand Down Expand Up @@ -81,6 +82,7 @@ describe('@integration: Dashboard', () => {
transactions: transactionReducer,
peers: peersReducer,
loading: loadingReducer,
liskService: liskServiceReducer,
}, [
thunk,
accountMiddleware,
Expand Down

0 comments on commit 1542095

Please sign in to comment.