Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: orders performance #5096

Merged
merged 10 commits into from
Apr 8, 2019
110 changes: 18 additions & 92 deletions imports/plugins/core/core/server/publications/collections/orders.js
Original file line number Diff line number Diff line change
@@ -1,112 +1,38 @@
import { Meteor } from "meteor/meteor";
import { check, Match } from "meteor/check";
import { Roles } from "meteor/alanning:roles";
import { ReactiveAggregate } from "./reactiveAggregate";
import { Accounts, MediaRecords, Orders, Shops } from "/lib/collections";
import { Counts } from "meteor/tmeasday:publish-counts";
import Reaction from "/imports/plugins/core/core/server/Reaction";

/**
* @summary A shared way of creating a projection
* @param {String} shopId - shopId to filter records by
* @param {Object} sort - An object containing a sort
* @param {Number} limit - An optional limit of how many records to return
* @param {Object} query - An optional query to match
* @param {Number} skip - An optional limit number of records to skip
* @returns {Array} An array of projection operators
* @private
*/
function createAggregate(shopId, sort = { createdAt: -1 }, limit = 0, query = {}, skip = 0) {
const aggregate = [{ $match: { "shipping.shopId": shopId, ...query } }];

if (sort) aggregate.push({ $sort: sort });
if (skip > 0) aggregate.push({ $skip: skip });
if (limit > 0) aggregate.push({ $limit: limit });

// NOTE: in Mongo 3.4 using the $in operator will be supported for projection filters
aggregate.push({
$project: {
shipping: {
$filter: {
input: "$shipping",
as: "shipping",
cond: { $eq: ["$$shipping.shopId", shopId] }
}
},
accountId: 1,
cartId: 1,
createdAt: 1,
email: 1,
payments: 1,
referenceId: 1,
shopId: 1,
workflow: 1 // workflow is still stored at the top level and used to showing status
}
});

return aggregate;
}

Meteor.publish("Orders", function () {
if (this.userId === null) {
return this.ready();
}
const shopId = Reaction.getShopId();
if (!shopId) {
return this.ready();
}

// return any order for which the shopId is attached to an item
const aggregateOptions = {
observeSelector: {
"shipping.shopId": shopId
}
};
const aggregate = createAggregate(shopId);

if (Roles.userIsInRole(this.userId, ["admin", "owner", "orders"], shopId)) {
ReactiveAggregate(this, Orders, aggregate, aggregateOptions);
} else {
const account = Accounts.findOne({ userId: this.userId });
return Orders.find({
accountId: account._id,
shopId
});
}
});


Meteor.publish("PaginatedOrders", function (query, options) {
check(query, Match.Optional(Object));
check(options, Match.Optional(Object));

if (this.userId === null) {
return this.ready();
}
const shopId = Reaction.getUserShopId(this.userId) || Reaction.getShopId();
if (!shopId) {
return this.ready();
}

// return any order for which the shopId is attached to an item
const aggregateOptions = {
observeSelector: {
"shipping.shopId": shopId
}
};
const limit = (options && options.limit) ? options.limit : 0;
const skip = (options && options.skip) ? options.skip : 0;
const aggregate = createAggregate(shopId, { createdAt: -1 }, limit, query, skip);
if (Roles.userIsInRole(this.userId, ["admin", "owner", "orders"], shopId)) {
Counts.publish(this, "orders-count", Orders.find(), { noReady: true });
ReactiveAggregate(this, Orders, aggregate, aggregateOptions);
} else {
const account = Accounts.findOne({ userId: this.userId });
return Orders.find({
accountId: account._id,
shopId
});
if (Roles.userIsInRole(this.userId, ["admin", "owner", "orders"], shopId) === false) {
return this.ready();
}

const limit = (options && options.limit) ? options.limit : 0;
const page = (options && options.page) ? options.page : 0;

Counts.publish(this, "orders-count", Orders.find({ shopId, ...query }), { noReady: true });

return Orders.find({
shopId,
...query
}, {
sort: {
createdAt: -1
},
limit,
skip: page * limit
});
});

/**
Expand Down
141 changes: 54 additions & 87 deletions imports/plugins/core/orders/client/components/orderDashboard.js
Original file line number Diff line number Diff line change
@@ -1,105 +1,72 @@
import React, { Component } from "react";
import React from "react";
import PropTypes from "prop-types";
import { Components } from "@reactioncommerce/reaction-components";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import OrderTable from "../containers/orderTableContainer";
import OrderFilter from "./orderFilter";

class OrderDashboard extends Component {
static propTypes = {
clearFilter: PropTypes.func,
currentPage: PropTypes.number,
filterDates: PropTypes.func,
filterShippingStatus: PropTypes.func,
filterWorkflowStatus: PropTypes.func,
handleBulkPaymentCapture: PropTypes.func,
handleClick: PropTypes.func,
handleSelect: PropTypes.func,
isLoading: PropTypes.object,
multipleSelect: PropTypes.bool, // eslint-disable-line react/boolean-prop-naming
onPageChange: PropTypes.func,
onPageSizeChange: PropTypes.func,
orders: PropTypes.array,
pages: PropTypes.number,
query: PropTypes.object,
renderFlowList: PropTypes.bool, // eslint-disable-line react/boolean-prop-naming
selectAllOrders: PropTypes.func,
selectedItems: PropTypes.array,
setShippingStatus: PropTypes.func,
shipping: PropTypes.object,
toggleShippingFlowList: PropTypes.func
}
/**
* Order Dashboard component
* @param {Object} props Component props
* @returns {React.Component} React component
*/
function OrderDashboard(props) {
const {
clearFilter,
filterDates,
filterShippingStatus,
filterWorkflowStatus,
orders,
onPageChange,
onPageSizeChange,
page,
pageSize,
totalOrderCount
} = props;

state = {
detailClassName: "",
listClassName: "order-icon-toggle",
openList: true,
orders: this.props.orders
}

handleListToggle = () => {
this.setState({
detailClassName: "",
listClassName: "order-icon-toggle",
openList: true
});
}

handleDetailToggle = () => {
this.setState({
detailClassName: "order-icon-toggle",
listClassName: "",
openList: false
});
}

render() {
return (
<div className="order-dashboard-container">
return (
<Card>
<CardContent>
<OrderFilter
clearFilter={this.props.clearFilter}
filterDates={this.props.filterDates}
filterShippingStatus={this.props.filterShippingStatus}
filterWorkflowStatus={this.props.filterWorkflowStatus}
clearFilter={clearFilter}
filterDates={filterDates}
filterShippingStatus={filterShippingStatus}
filterWorkflowStatus={filterWorkflowStatus}
/>
{this.props.orders.length ?
<div className="container-fluid-sm order-details-list-container">
<div className="order-toggle-buttons-container">
<div className="order-toggle-buttons">
<button
className={`order-toggle-btn ${this.state.detailClassName}`}
onClick={this.handleDetailToggle}
>
<i className="fa fa-th-list" />
</button>

<button
className={`order-toggle-btn ${this.state.listClassName}`}
onClick={this.handleListToggle}
>
<i className="fa fa-list" />
</button>
</div>
</div>

<OrderTable
orders={this.props.orders}
isOpen={this.state.openList}
onPageChange={this.props.onPageChange}
onPageSizeChange={this.props.onPageSizeChange}
pages={this.props.pages}
page={this.props.currentPage}
/>
</div> :
{orders.length ?
<OrderTable
orders={orders}
onPageChange={onPageChange}
onPageSizeChange={onPageSizeChange}
page={page}
pageSize={pageSize}
totalOrderCount={totalOrderCount}
/> :
<div className="container-fluid-sm order-details-list-container">
<div className="empty-view-message">
<Components.Icon icon="fa fa-sun-o" />
<Components.Translation defaultValue={"No orders found"} i18nKey={"order.ordersNotFound"} />
</div>
</div>
}
</div>
);
}
</CardContent>
</Card>
);
}

OrderDashboard.propTypes = {
clearFilter: PropTypes.func,
currentPage: PropTypes.number,
filterDates: PropTypes.func,
filterShippingStatus: PropTypes.func,
filterWorkflowStatus: PropTypes.func,
onPageChange: PropTypes.func,
onPageSizeChange: PropTypes.func,
orders: PropTypes.array,
page: PropTypes.number,
pageSize: PropTypes.number,
totalOrderCount: PropTypes.number
};

export default OrderDashboard;
4 changes: 2 additions & 2 deletions imports/plugins/core/orders/client/components/orderFilter.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,8 @@ class OrderFilter extends Component {
menuClassName="status-dropdown"
className="order-menu-item-dropdown"
onChange={this.handleShippingChange}
attachment={`bottom ${attachmentDirection}`}
targetAttachment={`top ${attachmentDirection}`}
attachment={`top ${attachmentDirection}`}
targetAttachment={`bottom ${attachmentDirection}`}
>
{Constants.shippingStatus.map((status, index) => (
<Components.MenuItem
Expand Down
Loading