Skip to content

Commit

Permalink
Reaction Shopify connector (#2584)
Browse files Browse the repository at this point in the history
* init shopify connector

* Shopify connector package init

* [EXPERIMENTAL] [WIP] Initial shopify api methods

* [WIP] Little bit of work to start pulling Shopify options / variants apart for insertion as Reaction ProductVariants

* [WIP] Fixes missing bracket and close paren

* Iterate through tags coming from shopify - find or create them in Reaction and cache for the rest of the import

* add productType field to Product schema.

* [WIP] Import images from Shopify - rename plugin directory

* Rename plugin to reaction-connectors-shopify

* Import images for variants and options and assign correctly to RC variants/options

* Correctly set price for primary product

* Add jsdoc, cleanup methods, add loop for 3rd shopify option

* Skip revision control for products that are imported.

* Update button to provide user feedback about import status

* Add shopifyId to product schema on server for quickly checking if a product has already been imported.

* Fix typo in register.js file

* add roles helper.

* Skip revision control for media that has workflow = "published" pre set.

* Add jsdoc, skip revision control, add opts, log to debug.

* i18n and a check to make sure we don't import any products we already have.
  • Loading branch information
spencern committed Jul 28, 2017
1 parent 96fb1b5 commit f096714
Show file tree
Hide file tree
Showing 30 changed files with 963 additions and 16 deletions.
1 change: 1 addition & 0 deletions imports/plugins/core/connectors/client/index.js
@@ -0,0 +1 @@
import "./templates";
2 changes: 2 additions & 0 deletions imports/plugins/core/connectors/client/templates/index.js
@@ -0,0 +1,2 @@
import "./settings/connectors.html";
import "./settings/connectors.js";
@@ -0,0 +1,21 @@
<template name="connectorSettings">
{{#each reactionApps provides='connectorSettings'}}

<div class="panel panel-default">
<div class="panel-heading panel-heading-flex">
<div class="panel-title">
<i class="fa fa-{{name}}"></i>
<span data-i18n="{{i18nKeyLabel}}">{{label}}</span>

</div>
<div class="panel-controls">
<input class="checkbox-switch connector-settings" type="checkbox" name="enabled" data-id={{packageId}} data-key="{{settingsKey}}" {{checked enabled}}>
</div>
</div>
<div class="panel-body {{shown enabled}}">
{{> Template.dynamic template=template data=.}}
</div>
</div>
{{/each}}

</template>
@@ -0,0 +1,48 @@
import { Template } from "meteor/templating";
import { Meteor } from "meteor/meteor";
/*
* Template connector Helpers
*/
Template.connectorSettings.onCreated(function () {
});

Template.connectorSettings.helpers({
checked(enabled) {
if (enabled === true) {
return "checked";
}
return "";
},
shown(enabled) {
if (enabled !== true) {
return "hidden";
}
return "";
}
});

// toggle connector methods visibility
// also toggles connector method settings
Template.connectorSettings.events({
/**
* connectorSettings settings update enabled status for connector service on change
* @param {event} event jQuery Event
* @return {void}
*/
"change input.checkbox-switch.connector-settings[name=enabled]": (event) => {
event.preventDefault();
const settingsKey = event.target.getAttribute("data-key");
const packageId = event.target.getAttribute("data-id");
const fields = [{
property: "enabled",
value: event.target.checked
}];
// save connector registry updates
if (packageId) {
// update package registry
Meteor.call("registry/update", packageId, settingsKey, fields);
// also update connector provider status
Meteor.call("connectors/connection/toggle", packageId, settingsKey);
}
}
});
12 changes: 12 additions & 0 deletions imports/plugins/core/connectors/package.json
@@ -0,0 +1,12 @@
{
"name": "reaction-connectors",
"version": "1.0.0",
"description": "base connectors plugin for reaction commerce",
"main": "register.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Spencer Norman",
"license": "GPL-3.0"
}
29 changes: 29 additions & 0 deletions imports/plugins/core/connectors/register.js
@@ -0,0 +1,29 @@
import { Reaction } from "/server/api";

Reaction.registerPackage({
label: "Connectors",
name: "reaction-connectors",
icon: "fa fa-exchange",
autoEnable: true,
settings: {
name: "Connectors"
},
registry: [{
provides: "dashboard",
route: "/dashboard/connectors",
name: "connectors",
label: "Connectors",
description: "Connectors dashboard",
icon: "fa fa-exchange",
priority: 1,
container: "core",
workflow: "coreDashboardWorkflow"
}, {
provides: "settings",
name: "settings/connectors",
label: "Connectors",
description: "Configure connectors",
icon: "fa fa-exchange",
template: "connectorSettings"
}]
});
81 changes: 81 additions & 0 deletions imports/plugins/core/connectors/server/i18n/en.json
@@ -0,0 +1,81 @@
[{
"i18n": "en",
"ns": "reaction-shipping",
"translation": {
"reaction-shipping": {
"admin": {
"dashboard": {
"shippingLabel": "Shipping",
"shippingTitle": "Shipping",
"shippingDescription": "Provide shipping rates"
},
"settings": {
"shippingLabel": "Shipping"
}
},
"checkoutShipping": {
"selectShippingOption": "Select shipping option",
"noShippingMethods": "No shipping methods are configured.",
"contactAdmin": "Please contact the store administrator.",
"configureNow": "Configure now.",
"shipping": "Shipping"
},
"shipping": {
"addShippingProvider": "Add shipping provider",
"editShippingProvider": "Edit shipping provider",
"addShippingMethod": "Add shipping method",
"editShippingMethod": "Edit shipping method",
"deleteShippingMethod": "Delete shipping method",
"noSettingsForThisView": "No settings for this view",
"noShippingMethods": "No shipping methods are configured.",
"removeShippingMethodConfirm": "Are you sure you want to delete {{method}}?",
"removeShippingMethodTitle": "Remove Shipping Method",
"shippingMethodDeleted": "This shipping method has been deleted.",
"removeShippingProvider": "Remove Shipping Provider",
"removeShippingProviderConfirm": "Are you sure you want to delete {{provider}}?",
"shippingProviderSaved": "Shipping provider saved.",
"shippingProviderUpdated": "Shipping provider data updated.",
"shippingMethodRateAdded": "Shipping method rate added.",
"shippingMethodRateUpdated": "Shipping method rate updated.",
"name": "Name",
"label": "Label",
"group": "Group",
"cost": "Cost",
"handling": "Handling",
"rate": "Rate",
"enabled": "Enabled",
"disabled": "Disabled",
"addRate": "Add rate",
"updateRate": "Update {{name}} rate",
"addNewCondition": "Add new condition",
"deleteCondition": "Delete condition",
"provider": {
"name": "Service Code",
"label": "Public Label",
"enabled": "Enabled"
}
},
"shippingMethod": {
"name": "Method Name",
"label": "Public Label",
"group": "Group",
"cost": "Cost",
"handling": "Handling",
"rate": "Rate",
"enabled": "Enabled",
"matchingCartRanges": "Matching Cart Ranges",
"validRanges": {
"begin": "Begin",
"end": "End"
},
"matchingLocales": "Matching Locales",
"validLocales": {
"origination": "From",
"destination": "To",
"deliveryBegin": "Shipping Est.",
"deliveryEnd": "Delivery Est."
}
}
}
}
}]
10 changes: 10 additions & 0 deletions imports/plugins/core/connectors/server/i18n/index.js
@@ -0,0 +1,10 @@
import { loadTranslations } from "/server/startup/i18n";

import en from "./en.json";

//
// we want all the files in individual
// imports for easier handling by
// automated translation software
//
loadTranslations([ en ]);
2 changes: 2 additions & 0 deletions imports/plugins/core/connectors/server/index.js
@@ -0,0 +1,2 @@
import "./i18n";
import "./methods/methods";
1 change: 1 addition & 0 deletions imports/plugins/core/connectors/server/lib/roles.js
@@ -0,0 +1 @@
export const connectorsRoles = ["admin", "owner", "connectors"];
28 changes: 28 additions & 0 deletions imports/plugins/core/connectors/server/methods/methods.js
@@ -0,0 +1,28 @@
import { Meteor } from "meteor/meteor";
import { check } from "meteor/check";
import { Packages } from "/lib/collections";
import { Reaction } from "/server/api";
import { connectorsRoles } from "../lib/roles";

export const methods = {
/**
* connectors/connection/toggle
* @summary toggle enabled connection
* @param { String } packageId - packageId
* @param { String } connection - connection name
* @return { Number } update - result
*/
"connectors/connection/toggle": function (packageId, connection) {
check(packageId, String);
check(connection, String);
if (!Reaction.hasPermission(connectorsRoles)) {
throw new Meteor.Error(403, "Access Denied");
}
const pkg = Packages.findOne(packageId);
if (pkg && pkg.settings[connection]) {
// const enabled = pkg.settings[connection].enabled;
}
}
};

Meteor.methods(methods);
10 changes: 10 additions & 0 deletions imports/plugins/core/revisions/server/hooks.js
Expand Up @@ -170,6 +170,11 @@ Media.files.before.insert((userid, media) => {
if (RevisionApi.isRevisionControlEnabled() === false) {
return true;
}
if (media.metadata.workflow === "published") {
// Skip by setting metadata.workflow.status to published
return true;
}

if (media.metadata.productId) {
const revisionMetadata = Object.assign({}, media.metadata);
revisionMetadata.workflow = "published";
Expand Down Expand Up @@ -275,6 +280,11 @@ Products.before.insert((userId, product) => {
return true;
}

if (product.workflow && Array.isArray(product.workflow.workflow) && product.workflow.workflow.indexOf("imported") !== -1) {
// Mark imported products as published by default.
return true;
}

const productRevision = Revisions.findOne({
"documentId": product._id,
"workflow.status": {
Expand Down
@@ -0,0 +1 @@
import "./settings/shopify";
@@ -0,0 +1,22 @@
<template name="shopifyConnectSettings">
<div class="panel-group">
{{#autoForm collection=Collections.Packages schema=ShopifyConnectPackageConfig doc=packageData type="update" id="shopify-connect-update-form"}}
{{> afQuickField name="settings.apiKey" class='form-control'}}
{{> afQuickField name="settings.password" class='form-control'}}
{{> afQuickField name="settings.sharedSecret" class='form-control'}}
{{> afQuickField name="settings.shopName" class='form-control'}}
{{> shopSettingsSubmitButton}}
{{/autoForm}}
</div>
{{#if packageData.settings.apiKey}}
<div class="panel-group">
{{> shopifyProductImport}}
</div>
{{/if}}
</template>

<template name="shopifyProductImport">
<button class="btn btn-default" data-event-action="importProductsFromShopify">
<i class="fa fa-cloud-download"></i><span data-i18n="admin.shopifyConnectSettings.importProducts">Import Products</span>
</button>
</template>
@@ -0,0 +1,51 @@
import { Meteor } from "meteor/meteor";
import { AutoForm } from "meteor/aldeed:autoform";
import { Template } from "meteor/templating";
import { $ } from "meteor/jquery";
import { Reaction, i18next } from "/client/api";
import { Packages } from "/lib/collections";
import { ShopifyConnectPackageConfig } from "../../lib/collections/schemas";

import "./shopify.html";

Template.shopifyConnectSettings.helpers({
packageData() {
return Packages.findOne({
name: "reaction-connectors-shopify",
shopId: Reaction.getShopId()
});
},
ShopifyConnectPackageConfig() {
return ShopifyConnectPackageConfig;
}
});

Template.shopifyProductImport.events({
"click [data-event-action=importProductsFromShopify]"(event) {
event.preventDefault();
$(event.currentTarget).html(`<i class='fa fa-circle-o-notch fa-spin'></i> ${i18next.t("admin.shopifyConnectSettings.importing")}`);
event.currentTarget.disabled = true;

Meteor.call("shopifyConnect/importProducts", (err) => {
$(event.currentTarget).html(`
<i class='fa fa-cloud-download'></i> ${i18next.t("admin.shopifyConnectSettings.importProducts")}`);
event.currentTarget.disabled = false;

if (!err) {
return Alerts.toast(i18next.t("admin.shopifyConnectSettings.importSuccess"), "success");
}
return Alerts.toast(`${i18next.t("admin.shopifyConnectSettings.importFailed")}: ${err}`, "error");
});
}
});

AutoForm.hooks({
"shopify-connect-update-form": {
onSuccess: function () {
return Alerts.toast(i18next.t("admin.settings.saveSuccess"), "success");
},
onError(error) {
return Alerts.toast(`${i18next.t("admin.settings.saveFailed")} ${error}`, "error");
}
}
});
@@ -0,0 +1 @@
export * from "./shopifyConnect";
@@ -0,0 +1,27 @@
import { SimpleSchema } from "meteor/aldeed:simple-schema";
import { PackageConfig } from "/lib/collections/schemas/registry";

export const ShopifyConnectPackageConfig = new SimpleSchema([
PackageConfig, {
"settings.apiKey": {
type: String,
label: "API Key",
optional: true
},
"settings.password": {
type: String,
label: "API Password",
optional: true
},
"settings.sharedSecret": {
type: String,
label: "API Shared Secret",
optional: true
},
"settings.shopName": {
type: String,
label: "Handelized Shop Name",
optional: true
}
}
]);
12 changes: 12 additions & 0 deletions imports/plugins/included/connectors-shopify/package.json
@@ -0,0 +1,12 @@
{
"name": "reaction-connectors-shopify",
"version": "1.0.0",
"description": "Reaction plugin for connecting to shopify store",
"main": "register.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Spencer Norman",
"license": "GPL-3.0"
}

0 comments on commit f096714

Please sign in to comment.