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

refactor: remove catalog/publish/products meteor method, use publishProductsToCatalog GQL Mutation instead #5541

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 1 addition & 71 deletions imports/meteor-app-tests/accounts.app-test.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
/* eslint-disable require-jsdoc */
/* eslint dot-notation: 0 */
/* eslint prefer-arrow-callback:0 */
import Logger from "@reactioncommerce/logger";
import Random from "@reactioncommerce/random";
import { Meteor } from "meteor/meteor";
import { Factory } from "meteor/dburles:factory";
import { check, Match } from "meteor/check";
import { Accounts as MeteorAccounts } from "meteor/accounts-base";
import { expect } from "meteor/practicalmeteor:chai";
import { sinon } from "meteor/practicalmeteor:sinon";
import ReactionError from "@reactioncommerce/reaction-error";
import { Accounts, Groups, Packages, Orders, Products, Shops, Cart } from "/lib/collections";
import { Accounts, Packages, Orders, Products, Shops, Cart } from "/lib/collections";
import Reaction from "/imports/plugins/core/core/server/Reaction";
import { getShop } from "/imports/plugins/core/core/server/fixtures/shops";
import Fixtures from "/imports/plugins/core/core/server/fixtures";

describe("Account Meteor method ", function () {
let shopId;
let fakeUser;
let fakeAccount;
const originals = {};
let sandbox;

Expand Down Expand Up @@ -49,7 +43,6 @@ describe("Account Meteor method ", function () {
fakeUser = Factory.create("user");
const userId = fakeUser._id;
// set the _id... some code requires that Account#_id === Account#userId
fakeAccount = Factory.create("account", { _id: userId, userId, shopId });
sandbox.stub(Meteor, "user", () => fakeUser);
sandbox.stub(Meteor.users, "findOne", () => fakeUser);
sandbox.stub(Reaction, "getUserId", () => userId);
Expand All @@ -69,67 +62,4 @@ describe("Account Meteor method ", function () {
return originals[method].apply(this, args);
});
}

describe("accounts/inviteShopOwner", function () {
let createUserSpy;
let groupId;
let group;

function callDescribed(accountAttributes = {}, shopData) {
const options = Object.assign({
email: fakeUser.emails[0].address,
name: fakeAccount.profile.addressBook[0].fullName
}, accountAttributes);

return Meteor.call("accounts/inviteShopOwner", options, shopData);
}

function stubPermissioning(settings) {
const { hasPermission } = settings;

sandbox
.stub(Reaction, "hasPermission", () => hasPermission)
.withArgs("admin", fakeAccount.userId, sinon.match.string);
}

beforeEach(function () {
// fakeAccount = Factory.create("account");
createUserSpy = sandbox.spy(MeteorAccounts, "createUser");

// resolves issues with the onCreateUser event handler
groupId = Random.id();
group = Factory.create("group");
sandbox
.stub(Groups, "findOne", () => group)
.withArgs({ _id: groupId, shopId: sinon.match.string });

// since we expect a note to be written, let's ignore it to keep the output clean
sandbox.stub(Logger, "info").withArgs(sinon.match(/Created shop/));
});

it("requires admin permission", function () {
stubPermissioning({ hasPermission: false });

expect(callDescribed).to.throw(ReactionError, /Access denied/);
expect(createUserSpy).to.not.have.been.called;
});

it("creates a shop with the data provided", function () {
const primaryShop = getShop();
const name = Random.id();
const shopData = { name };
const email = `${Random.id()}@example.com`;

stubPermissioning({ hasPermission: true });
sandbox.stub(Reaction, "getPrimaryShop", () => primaryShop);

sandbox.stub(Accounts, "findOne", () => fakeAccount)
.withArgs({ id: fakeUser._id });

callDescribed({ email }, shopData);

const newShopCount = Shops.find({ name }).count();
expect(newShopCount).to.equal(1);
});
});
});
49 changes: 40 additions & 9 deletions imports/plugins/core/catalog/client/components/publishControls.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,34 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import gql from "graphql-tag";
import { Mutation } from "react-apollo";
import Button from "@material-ui/core/Button";
import Typography from "@material-ui/core/Typography";
import withStyles from "@material-ui/core/styles/withStyles";
import { i18next } from "/client/api";
import PrimaryAppBar from "/imports/client/ui/components/PrimaryAppBar/PrimaryAppBar";

const publishProductsToCatalog = gql`
mutation ($productIds: [ID]!) {
publishProductsToCatalog(productIds: $productIds) {
product {
productId
title
isDeleted
supportedFulfillmentTypes
variants {
_id
title
options {
_id
title
}
}
}
}
}
`;

const styles = (theme) => ({
label: {
marginRight: theme.spacing(2)
Expand Down Expand Up @@ -36,21 +59,29 @@ class PublishControls extends Component {
return null;
}

renderOnCompletedAlert = () => Alerts.toast(i18next.t("admin.catalogProductPublishSuccess"), "success");

renderOnErrorAlert = (error) => Alerts.toast(error.message, "error");

render() {
const { documentIds, onPublishClick } = this.props;

return (
<PrimaryAppBar>
{this.renderChangesNotification()}
<Button
color="primary"
variant="contained"
disabled={Array.isArray(documentIds) && documentIds.length === 0}
label="Publish"
onClick={onPublishClick}
>
{i18next.t("productDetailEdit.publish")}
</Button>
<Mutation mutation={publishProductsToCatalog} onCompleted={() => this.renderOnCompletedAlert()} onError={(error) => this.renderOnErrorAlert(error)}>
{(mutationFunc) => (
<Button
color="primary"
variant="contained"
disabled={Array.isArray(documentIds) && documentIds.length === 0}
label="Publish"
onClick={() => onPublishClick(mutationFunc)}
>
{i18next.t("productDetailEdit.publish")}
</Button>
)}
</Mutation>
</PrimaryAppBar>
);
}
Expand Down
23 changes: 13 additions & 10 deletions imports/plugins/core/catalog/client/containers/publishContainer.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,34 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Meteor } from "meteor/meteor";
import { composeWithTracker } from "@reactioncommerce/reaction-components";
import { i18next } from "/client/api";
import getOpaqueIds from "/imports/plugins/core/core/client/util/getOpaqueIds";
import TranslationProvider from "/imports/plugins/core/ui/client/providers/translationProvider";
import PublishControls from "../components/publishControls";

/*
* PublishContainer is a container component connected to Meteor data source.
*/
class PublishContainer extends Component {
publishToCatalog(collection, documentIds) {
Meteor.call(`catalog/publish/${collection}`, documentIds, (error, result) => {
if (result) {
Alerts.toast(i18next.t("admin.catalogProductPublishSuccess", { defaultValue: "Product published to catalog" }), "success");
} else if (error) {
Alerts.toast(error.message, "error");
async publishToCatalog(productIds, mutation) {
// we need to encode the productIds here to pass them to GraphQL
const productIdObjects = productIds.map((productId) => (
{ namespace: "Product", id: productId }
));
const opaqueProductIds = await getOpaqueIds(productIdObjects);

await mutation({
variables: {
productIds: opaqueProductIds
}
});
}

handlePublishClick = () => {
handlePublishClick = (mutation) => {
const productIds = this.props.documents
.filter((doc) => doc.type === "simple")
.map((doc) => doc._id);

this.publishToCatalog("products", productIds);
this.publishToCatalog(productIds, mutation);
}

handlePublishActions = (event, action) => {
Expand Down
1 change: 0 additions & 1 deletion imports/plugins/core/catalog/server/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import "./i18n";
import "./methods/catalog";
import "./methods/publishProducts";

/**
* Query functions that do not import or use any Meteor packages or globals. These can be used both
Expand Down
12 changes: 0 additions & 12 deletions imports/plugins/core/catalog/server/methods/publishProducts.js

This file was deleted.

90 changes: 55 additions & 35 deletions imports/plugins/included/product-variant/components/productGrid.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* eslint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */
import React, { Component } from "react";
import React, { Component, Fragment } from "react";
import PropTypes from "prop-types";
import gql from "graphql-tag";
import { Mutation } from "react-apollo";
import { Components } from "@reactioncommerce/reaction-components";
import { Session } from "meteor/session";
import { i18next } from "/client/api";
Expand All @@ -24,6 +26,27 @@ import Typography from "@material-ui/core/Typography";
import Chip from "@reactioncommerce/catalyst/Chip";
import withStyles from "@material-ui/core/styles/withStyles";

const publishProductsToCatalog = gql`
mutation ($productIds: [ID]!) {
publishProductsToCatalog(productIds: $productIds) {
product {
productId
title
isDeleted
supportedFulfillmentTypes
variants {
_id
title
options {
_id
title
}
}
}
}
}
`;

const styles = (theme) => ({
leftChip: {
marginBottom: theme.spacing(2),
Expand Down Expand Up @@ -84,7 +107,6 @@ const styles = (theme) => ({
}
});

// TODO: refactor to function
class ProductGrid extends Component {
static propTypes = {
classes: PropTypes.object,
Expand All @@ -94,7 +116,6 @@ class ProductGrid extends Component {
onArchiveProducts: PropTypes.func,
onChangePage: PropTypes.func,
onChangeRowsPerPage: PropTypes.func,
onDisplayTagSelector: PropTypes.func,
onDuplicateProducts: PropTypes.func,
onPublishProducts: PropTypes.func,
onSelectAllProducts: PropTypes.func,
Expand Down Expand Up @@ -210,14 +231,9 @@ class ProductGrid extends Component {
);
}

handleDisplayTagSelector = () => {
this.handleCloseBulkActions();
this.props.onDisplayTagSelector(true);
}

handleShowFilterByFile = () => {
this.handleCloseBulkActions();
this.props.onShowFilterByFile(true);
this.props.onShowFilterByFile();
}

handleShowBulkActions = (event) => {
Expand Down Expand Up @@ -248,8 +264,8 @@ class ProductGrid extends Component {
this.handleCloseBulkActions();
}

handleBulkActionPublish = () => {
this.props.onPublishProducts(this.props.selectedProductIds);
handleBulkActionPublish = (mutation) => {
this.props.onPublishProducts(this.props.selectedProductIds, mutation);
this.handleCloseBulkActions();
}

Expand Down Expand Up @@ -281,7 +297,6 @@ class ProductGrid extends Component {
const { bulkActionMenuAnchorEl } = this.state;
const count = selectedProductIds.length;
const isEnabled = Array.isArray(selectedProductIds) && selectedProductIds.length;

return (
<Toolbar disableGutters={true} className={classes.toolbar}>
<Button
Expand Down Expand Up @@ -310,15 +325,6 @@ class ProductGrid extends Component {
Actions
</MenuItem>

<MenuItem
onClick={this.handleDisplayTagSelector}
variant="default"
disabled={!isEnabled}
className={classes.actionDropdownMenuItem}
>
{i18next.t("admin.productTable.bulkActions.addRemoveTags")}
</MenuItem>

<MenuItem
onClick={this.handleShowFilterByFile}
variant="default"
Expand All @@ -327,21 +333,35 @@ class ProductGrid extends Component {
{i18next.t("admin.productTable.bulkActions.filterByFile")}
</MenuItem>

<ConfirmDialog
title={i18next.t("admin.productTable.bulkActions.publishTitle", { count })}
message={i18next.t("admin.productTable.bulkActions.publishMessage")}
onConfirm={this.handleBulkActionPublish}
>
{({ openDialog }) => (
<MenuItem
className={classes.actionDropdownMenuItem}
onClick={openDialog}
disabled={!isEnabled}
>
{i18next.t("admin.productTable.bulkActions.publish")}
</MenuItem>
<Mutation mutation={publishProductsToCatalog}>
{(mutationFunc, { data, error }) => (
<Fragment>
<ConfirmDialog
title={i18next.t("admin.productTable.bulkActions.publishTitle", { count })}
message={i18next.t("admin.productTable.bulkActions.publishMessage")}
onConfirm={() => this.handleBulkActionPublish(mutationFunc)}
>
{({ openDialog }) => (
<MenuItem
className={classes.actionDropdownMenuItem}
onClick={openDialog}
disabled={!isEnabled}
>
{i18next.t("admin.productTable.bulkActions.publish")}
</MenuItem>
)}
</ConfirmDialog>
<span>
{ error &&
Alerts.toast(error.message, "error")
}
{ data &&
Alerts.toast(i18next.t("admin.catalogProductPublishSuccess", { defaultValue: "Product published to catalog" }), "success")
}
</span>
</Fragment>
)}
</ConfirmDialog>
</Mutation>

<ConfirmDialog
title={i18next.t("admin.productTable.bulkActions.makeVisibleTitle", { count })}
Expand Down
Loading