Skip to content

Commit

Permalink
Merge pull request #2899 from marmelab/dashboard-custom-action
Browse files Browse the repository at this point in the history
[RFR] Add withDataProvider HOC and Query/Mutation components to ease custom queries
  • Loading branch information
Kmaschta authored Feb 28, 2019
2 parents 5cabddc + 9238f43 commit aab31fe
Show file tree
Hide file tree
Showing 14 changed files with 1,036 additions and 334 deletions.
462 changes: 285 additions & 177 deletions docs/Actions.md

Large diffs are not rendered by default.

24 changes: 18 additions & 6 deletions docs/_layouts/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -692,31 +692,43 @@

<li class="chapter {% if page.path == 'Actions.md' %}active{% endif %}">
<a href="./Actions.html">
<b>14.</b> Writing Actions
<b>14.</b> Querying the API
</a>
<ul class="articles" {% if page.path !='Actions.md' %}style="display:none" {% endif %}>
<li class="chapter">
<a href="#the-simple-way">The Simple Way</a>
<a href="#the-basic-way-using-fetch">The Basic Way: Using <code>fetch</code></a>
</li>
<li class="chapter">
<a href="#using-a-data-provider-instead-of-fetch">Using a Data Provider</a>
<a href="#using-the-data-provider-instead-of-fetch">Using the <code>dataProvider</code></a>
</li>
<li class="chapter">
<a href="#using-a-custom-action-creator">Using a Custom Action Creator</a>
<a href="#using-the-withdataprovider-decorator">Using <code>withDataProvider</code></a>
</li>
<li class="chapter">
<a href="#handling-side-effects">Handling Side Effects</a>
</li>
<li class="chapter">
<a href="#success-and-failure-side-effects">Success and Failure Side Effects</a>
<a href="#optimistic-rendering-and-undo">Optimistic Rendering and Undo</a>
</li>
<li class="chapter">
<a href="#optimistic-rendering-and-undo">Optimistic Rendering and Undo</a>
<a href="#query-and-mutation-components"><code>&lt;Query&gt;</code> and <code>&lt;Mutation&gt;</code></a>
</li>
<li class="chapter">
<a href="#using-a-custom-action-creator">Using a Custom Action Creator</a>
</li>
<li class="chapter">
<a href="#adding-side-effects-to-actions">Adding Side Effects to Actions</a>
</li>
<li class="chapter">
<a href="#making-an-action-undoable">Undoable Action</a>
</li>
<li class="chapter">
<a href="#altering-the-form-values-before-submitting">Altering the Form Values before
Submitting</a>
</li>
<li class="chapter">
<a href="#custom-side-effects">Custom Side Effects</a>
</li>
<li class="chapter">
<a href="#custom-sagas">Custom Sagas</a>
</li>
Expand Down
224 changes: 115 additions & 109 deletions examples/demo/src/dashboard/Dashboard.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React, { Component } from 'react';
import { GET_LIST, GET_MANY, Responsive } from 'react-admin';
import { GET_LIST, GET_MANY, Responsive, withDataProvider } from 'react-admin';
import compose from 'recompose/compose';
import { connect } from 'react-redux';

import Welcome from './Welcome';
import MonthlyRevenue from './MonthlyRevenue';
import NbNewOrders from './NbNewOrders';
import PendingOrders from './PendingOrders';
import PendingReviews from './PendingReviews';
import NewCustomers from './NewCustomers';
import dataProviderFactory from '../dataProvider';

const styles = {
flex: { display: 'flex' },
Expand All @@ -21,118 +22,116 @@ class Dashboard extends Component {
state = {};

componentDidMount() {
this.fetchData();
}

componentDidUpdate(prevProps) {
// handle refresh
if (this.props.version !== prevProps.version) {
this.fetchData();
}
}

fetchData() {
this.fetchOrders();
this.fetchReviews();
this.fetchCustomers();
}

async fetchOrders() {
const { dataProvider } = this.props;
const aMonthAgo = new Date();
aMonthAgo.setDate(aMonthAgo.getDate() - 30);
const { data: recentOrders } = await dataProvider(
GET_LIST,
'commands',
{
filter: { date_gte: aMonthAgo.toISOString() },
sort: { field: 'date', order: 'DESC' },
pagination: { page: 1, perPage: 50 },
}
);
const aggregations = recentOrders
.filter(order => order.status !== 'cancelled')
.reduce(
(stats, order) => {
if (order.status !== 'cancelled') {
stats.revenue += order.total;
stats.nbNewOrders++;
}
if (order.status === 'ordered') {
stats.pendingOrders.push(order);
}
return stats;
},
{
revenue: 0,
nbNewOrders: 0,
pendingOrders: [],
}
);
this.setState({
revenue: aggregations.revenue.toLocaleString(undefined, {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}),
nbNewOrders: aggregations.nbNewOrders,
pendingOrders: aggregations.pendingOrders,
});
const { data: customers } = await dataProvider(GET_MANY, 'customers', {
ids: aggregations.pendingOrders.map(order => order.customer_id),
});
this.setState({
pendingOrdersCustomers: customers.reduce((prev, customer) => {
prev[customer.id] = customer; // eslint-disable-line no-param-reassign
return prev;
}, {}),
});
}

dataProviderFactory(process.env.REACT_APP_DATA_PROVIDER).then(
dataProvider => {
dataProvider(GET_LIST, 'commands', {
filter: { date_gte: aMonthAgo.toISOString() },
sort: { field: 'date', order: 'DESC' },
pagination: { page: 1, perPage: 50 },
})
.then(response =>
response.data
.filter(order => order.status !== 'cancelled')
.reduce(
(stats, order) => {
if (order.status !== 'cancelled') {
stats.revenue += order.total;
stats.nbNewOrders++;
}
if (order.status === 'ordered') {
stats.pendingOrders.push(order);
}
return stats;
},
{
revenue: 0,
nbNewOrders: 0,
pendingOrders: [],
}
)
)
.then(({ revenue, nbNewOrders, pendingOrders }) => {
this.setState({
revenue: revenue.toLocaleString(undefined, {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}),
nbNewOrders,
pendingOrders,
});
return pendingOrders;
})
.then(pendingOrders =>
pendingOrders.map(order => order.customer_id)
)
.then(customerIds =>
dataProvider(GET_MANY, 'customers', {
ids: customerIds,
})
)
.then(response => response.data)
.then(customers =>
customers.reduce((prev, customer) => {
prev[customer.id] = customer; // eslint-disable-line no-param-reassign
return prev;
}, {})
)
.then(customers =>
this.setState({ pendingOrdersCustomers: customers })
);

dataProvider(GET_LIST, 'reviews', {
filter: { status: 'pending' },
sort: { field: 'date', order: 'DESC' },
pagination: { page: 1, perPage: 100 },
})
.then(response => response.data)
.then(reviews => {
const nbPendingReviews = reviews.reduce(nb => ++nb, 0);
const pendingReviews = reviews.slice(
0,
Math.min(10, reviews.length)
);
this.setState({ pendingReviews, nbPendingReviews });
return pendingReviews;
})
.then(reviews => reviews.map(review => review.customer_id))
.then(customerIds =>
dataProvider(GET_MANY, 'customers', {
ids: customerIds,
})
)
.then(response => response.data)
.then(customers =>
customers.reduce((prev, customer) => {
prev[customer.id] = customer; // eslint-disable-line no-param-reassign
return prev;
}, {})
)
.then(customers =>
this.setState({ pendingReviewsCustomers: customers })
);
async fetchReviews() {
const { dataProvider } = this.props;
const { data: reviews } = await dataProvider(GET_LIST, 'reviews', {
filter: { status: 'pending' },
sort: { field: 'date', order: 'DESC' },
pagination: { page: 1, perPage: 100 },
});
const nbPendingReviews = reviews.reduce(nb => ++nb, 0);
const pendingReviews = reviews.slice(0, Math.min(10, reviews.length));
this.setState({ pendingReviews, nbPendingReviews });
const { data: customers } = await dataProvider(GET_MANY, 'customers', {
ids: pendingReviews.map(review => review.customer_id),
});
this.setState({
pendingReviewsCustomers: customers.reduce((prev, customer) => {
prev[customer.id] = customer; // eslint-disable-line no-param-reassign
return prev;
}, {}),
});
}

dataProvider(GET_LIST, 'customers', {
filter: {
has_ordered: true,
first_seen_gte: aMonthAgo.toISOString(),
},
sort: { field: 'first_seen', order: 'DESC' },
pagination: { page: 1, perPage: 100 },
})
.then(response => response.data)
.then(newCustomers => {
this.setState({ newCustomers });
this.setState({
nbNewCustomers: newCustomers.reduce(nb => ++nb, 0),
});
});
async fetchCustomers() {
const { dataProvider } = this.props;
const aMonthAgo = new Date();
aMonthAgo.setDate(aMonthAgo.getDate() - 30);
const { data: newCustomers } = await dataProvider(
GET_LIST,
'customers',
{
filter: {
has_ordered: true,
first_seen_gte: aMonthAgo.toISOString(),
},
sort: { field: 'first_seen', order: 'DESC' },
pagination: { page: 1, perPage: 100 },
}
);
this.setState({
newCustomers,
nbNewCustomers: newCustomers.reduce(nb => ++nb, 0),
});
}

render() {
Expand Down Expand Up @@ -222,4 +221,11 @@ class Dashboard extends Component {
}
}

export default Dashboard;
const mapStateToProps = state => ({
version: state.admin.ui.viewVersion,
});

export default compose(
connect(mapStateToProps),
withDataProvider
)(Dashboard);
Loading

0 comments on commit aab31fe

Please sign in to comment.