From 6bdc5d4438939a670a6b6406dc97252bdb9908ef Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Mon, 6 May 2019 01:26:15 +0100 Subject: [PATCH 1/3] Fix maps performance --- .../services/enterprise_modal.js.coffee | 9 +- app/controllers/api/enterprises_controller.rb | 6 + app/helpers/injection_helper.rb | 8 ++ .../enterprise_shopfront_list_serializer.rb | 52 +++++++ .../api/enterprise_shopfront_serializer.rb | 133 ++++++++++++++++++ .../api/enterprise_thin_serializer.rb | 21 +++ app/views/map/index.html.haml | 2 +- config/routes.rb | 4 + 8 files changed, 231 insertions(+), 4 deletions(-) create mode 100644 app/serializers/api/enterprise_shopfront_list_serializer.rb create mode 100644 app/serializers/api/enterprise_shopfront_serializer.rb create mode 100644 app/serializers/api/enterprise_thin_serializer.rb diff --git a/app/assets/javascripts/darkswarm/services/enterprise_modal.js.coffee b/app/assets/javascripts/darkswarm/services/enterprise_modal.js.coffee index ae43c85090..19361fe508 100644 --- a/app/assets/javascripts/darkswarm/services/enterprise_modal.js.coffee +++ b/app/assets/javascripts/darkswarm/services/enterprise_modal.js.coffee @@ -1,9 +1,12 @@ -Darkswarm.factory "EnterpriseModal", ($modal, $rootScope)-> +Darkswarm.factory "EnterpriseModal", ($modal, $rootScope, $http)-> # Build a modal popup for an enterprise. new class EnterpriseModal open: (enterprise)-> scope = $rootScope.$new(true) # Spawn an isolate to contain the enterprise scope.embedded_layout = window.location.search.indexOf("embedded_shopfront=true") != -1 - scope.enterprise = enterprise - $modal.open(templateUrl: "enterprise_modal.html", scope: scope) + $http.get("/api/enterprises/" + enterprise.id + "/shopfront").success (data) -> + scope.enterprise = data + $modal.open(templateUrl: "enterprise_modal.html", scope: scope) + .error (data) -> + console.error(data) diff --git a/app/controllers/api/enterprises_controller.rb b/app/controllers/api/enterprises_controller.rb index c1f6fb3ce7..ca828f707a 100644 --- a/app/controllers/api/enterprises_controller.rb +++ b/app/controllers/api/enterprises_controller.rb @@ -47,6 +47,12 @@ module Api end end + def shopfront + enterprise = Enterprise.find_by_id(params[:id]) + + render text: Api::EnterpriseShopfrontSerializer.new(enterprise).to_json, status: :ok + end + private def override_owner diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index aaec90f0b8..60fc2bbde5 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -10,6 +10,14 @@ module InjectionHelper ) end + def inject_enterprise_shopfront_list + inject_json_ams( + 'enterprises', + Enterprise.activated.includes(address: :state).all, + Api::EnterpriseShopfrontListSerializer + ) + end + def inject_enterprise_and_relatives inject_json_ams "enterprises", current_distributor.relatives_including_self.activated.includes(address: :state).all, Api::EnterpriseSerializer, enterprise_injection_data end diff --git a/app/serializers/api/enterprise_shopfront_list_serializer.rb b/app/serializers/api/enterprise_shopfront_list_serializer.rb new file mode 100644 index 0000000000..30b5ed3590 --- /dev/null +++ b/app/serializers/api/enterprise_shopfront_list_serializer.rb @@ -0,0 +1,52 @@ +# Represents the minimum details of an Enterprise when all shopfronts are being listed +module Api + class EnterpriseShopfrontListSerializer < ActiveModel::Serializer + attributes :name, :id, :latitude, :longitude, :is_primary_producer, :is_distributor, + :visible, :path, :icon, :icon_font, :producer_icon_font + + has_one :address, serializer: Api::AddressSerializer + + def path + enterprise_shop_path(enterprise) + end + + def icon + icons = { + hub: "/assets/map_005-hub.svg", + hub_profile: "/assets/map_006-hub-profile.svg", + producer_hub: "/assets/map_005-hub.svg", + producer_shop: "/assets/map_003-producer-shop.svg", + producer: "/assets/map_001-producer-only.svg", + } + icons[enterprise.category] + end + + def icon_font + icon_fonts = { + hub: "ofn-i_063-hub", + hub_profile: "ofn-i_064-hub-reversed", + producer_hub: "ofn-i_063-hub", + producer_shop: "ofn-i_059-producer", + producer: "ofn-i_059-producer", + } + icon_fonts[enterprise.category] + end + + def producer_icon_font + icon_fonts = { + hub: "", + hub_profile: "", + producer_hub: "ofn-i_059-producer", + producer_shop: "ofn-i_059-producer", + producer: "ofn-i_059-producer", + } + icon_fonts[enterprise.category] + end + + private + + def enterprise + object + end + end +end diff --git a/app/serializers/api/enterprise_shopfront_serializer.rb b/app/serializers/api/enterprise_shopfront_serializer.rb new file mode 100644 index 0000000000..6b800ffcd7 --- /dev/null +++ b/app/serializers/api/enterprise_shopfront_serializer.rb @@ -0,0 +1,133 @@ +# Represents the properties of an Enterprise when viewing the details of listed shopfronts +module Api + class EnterpriseShopfrontSerializer < ActiveModel::Serializer + include SerializerHelper + + attributes :name, :id, :description, :latitude, :longitude, :long_description, :website, + :instagram, :linkedin, :twitter, :facebook, :is_primary_producer, :is_distributor, + :phone, :visible, :email_address, :hash, :logo, :promo_image, :path, :category, + :active, :producers, :orders_close_at, :hubs, :taxons, :supplied_taxons, :pickup, + :delivery + + has_one :address, serializer: Api::AddressSerializer + has_many :supplied_properties, serializer: Api::PropertySerializer + has_many :distributed_properties, serializer: Api::PropertySerializer + + def orders_close_at + OrderCycle.with_distributor(enterprise).soonest_closing.first.andand.orders_close_at + end + + def active + enterprise.ready_for_checkout? && OrderCycle.active.with_distributor(enterprise).exists? + end + + def pickup + shipping_types? :pickup + end + + def delivery + shipping_types? :delivery + end + + def email_address + enterprise.email_address.to_s.reverse + end + + def hash + enterprise.to_param + end + + def logo + enterprise.logo(:medium) if enterprise.logo? + end + + def promo_image + enterprise.promo_image(:large) if enterprise.promo_image? + end + + def path + enterprise_shop_path(enterprise) + end + + def producers + ActiveModel::ArraySerializer.new( + enterprise.suppliers, each_serializer: Api::EnterpriseThinSerializer + ) + end + + def hubs + ActiveModel::ArraySerializer.new( + enterprise.distributors, each_serializer: Api::EnterpriseThinSerializer + ) + end + + def taxons + ActiveModel::ArraySerializer.new( + enterprise.distributed_taxons, each_serializer: Api::TaxonSerializer + ) + end + + def supplied_taxons + ActiveModel::ArraySerializer.new( + enterprise.supplied_taxons, each_serializer: Api::TaxonSerializer + ) + end + + def supplied_properties + (product_properties + producer_properties).uniq do |property_object| + property_object.property.presentation + end + end + + def distributed_properties + (distributed_product_properties + distributed_producer_properties).uniq do |property_object| + property_object.property.presentation + end + end + + def distributed_product_properties + properties = Spree::Property.joins(products: { variants: { exchanges: :order_cycle } }) + .merge(Exchange.outgoing) + .merge(Exchange.to_enterprise(enterprise)) + .select('DISTINCT spree_properties.*') + + return properties.merge(OrderCycle.active) if active + properties + end + + def distributed_producer_properties + properties = Spree::Property.joins( + producer_properties: { + producer: { supplied_products: { variants: { exchanges: :order_cycle } } } + } + ) + .merge(Exchange.outgoing).merge(Exchange.to_enterprise(enterprise)) + .select('DISTINCT spree_properties.*') + + return properties.merge(OrderCycle.active) if active + properties + end + + private + + def product_properties + enterprise.supplied_products.flat_map(&:properties) + end + + def producer_properties + enterprise.properties + end + + def enterprise + object + end + + def shipping_types?(type) + require_shipping = type == :delivery ? 't' : 'f' + Spree::ShippingMethod. + joins(:distributor_shipping_methods). + where('distributors_shipping_methods.distributor_id = ?', enterprise.id). + where("spree_shipping_methods.require_ship_address = '#{require_shipping}'").exists? + end + end +end diff --git a/app/serializers/api/enterprise_thin_serializer.rb b/app/serializers/api/enterprise_thin_serializer.rb new file mode 100644 index 0000000000..cdc4d6b9eb --- /dev/null +++ b/app/serializers/api/enterprise_thin_serializer.rb @@ -0,0 +1,21 @@ +module Api + class EnterpriseThinSerializer < ActiveModel::Serializer + attributes :name, :id, :active, :path + + has_one :address, serializer: Api::AddressSerializer + + def active + enterprise.ready_for_checkout? && OrderCycle.active.with_distributor(enterprise).exists? + end + + def path + enterprise_shop_path(enterprise) + end + + private + + def enterprise + object + end + end +end diff --git a/app/views/map/index.html.haml b/app/views/map/index.html.haml index 75bb99f9d7..b66df8429f 100644 --- a/app/views/map/index.html.haml +++ b/app/views/map/index.html.haml @@ -1,7 +1,7 @@ - content_for(:title) do = t :label_map -= inject_enterprises += inject_enterprise_shopfront_list .map-container{"fill-vertical" => true} %map{"ng-controller" => "MapCtrl"} diff --git a/config/routes.rb b/config/routes.rb index 34d681fe11..f264d87144 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -96,6 +96,10 @@ Openfoodnetwork::Application.routes.draw do resource :logo, only: [:destroy] resource :promo_image, only: [:destroy] + + member do + get :shopfront + end end resources :order_cycles do From 6a8a67560b26afc06f9fd00bf2b364f04b9f9804 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Mon, 6 May 2019 17:31:08 +0100 Subject: [PATCH 2/3] Fix API authentication --- app/controllers/api/enterprises_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/enterprises_controller.rb b/app/controllers/api/enterprises_controller.rb index ca828f707a..666eb0b034 100644 --- a/app/controllers/api/enterprises_controller.rb +++ b/app/controllers/api/enterprises_controller.rb @@ -1,5 +1,5 @@ module Api - class EnterprisesController < Spree::Api::BaseController + class EnterprisesController < BaseController before_filter :override_owner, only: [:create, :update] before_filter :check_type, only: :update From fc8b220b4cc46cdfbd9db9dfe17ddb3bd6ea0d45 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Mon, 6 May 2019 22:42:51 +0100 Subject: [PATCH 3/3] Add specs for endpoint and serializers --- .../api/enterprises_controller_spec.rb | 22 ++++++++ ...terprise_shopfront_list_serializer_spec.rb | 20 ++++++++ .../enterprise_shopfront_serializer_spec.rb | 50 +++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 spec/serializers/api/enterprise_shopfront_list_serializer_spec.rb create mode 100644 spec/serializers/api/enterprise_shopfront_serializer_spec.rb diff --git a/spec/controllers/api/enterprises_controller_spec.rb b/spec/controllers/api/enterprises_controller_spec.rb index a16bfcbac2..7cedd8f526 100644 --- a/spec/controllers/api/enterprises_controller_spec.rb +++ b/spec/controllers/api/enterprises_controller_spec.rb @@ -81,5 +81,27 @@ module Api end end end + + describe "fetching shopfronts data" do + let!(:hub) { + create(:distributor_enterprise, with_payment_and_shipping: true, name: 'Shopfront Test Hub') + } + let!(:producer) { create(:supplier_enterprise, name: 'Shopfront Test Producer') } + let!(:category) { create(:taxon, name: 'Fruit') } + let!(:product) { create(:product, supplier: producer, primary_taxon: category ) } + let!(:relationship) { create(:enterprise_relationship, parent: hub, child: producer) } + + before do + allow(controller).to receive(:spree_current_user) { Spree::User.anonymous! } + end + + it "returns data for an enterprise" do + spree_get :shopfront, id: producer.id, format: :json + + expect(json_response['name']).to eq 'Shopfront Test Producer' + expect(json_response['hubs'][0]['name']).to eq 'Shopfront Test Hub' + expect(json_response['supplied_taxons'][0]['name']).to eq 'Fruit' + end + end end end diff --git a/spec/serializers/api/enterprise_shopfront_list_serializer_spec.rb b/spec/serializers/api/enterprise_shopfront_list_serializer_spec.rb new file mode 100644 index 0000000000..8a36282e43 --- /dev/null +++ b/spec/serializers/api/enterprise_shopfront_list_serializer_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Api::EnterpriseShopfrontListSerializer do + let(:enterprise) { create(:distributor_enterprise) } + let(:serializer) { + Api::EnterpriseShopfrontListSerializer.new enterprise + } + + it "serializes enterprise attributes" do + expect(serializer.to_json).to match enterprise.name + end + + it "serializes shopfront path" do + expect(serializer.to_json).to match enterprise_shop_path(enterprise) + end + + it "serializes icons" do + expect(serializer.to_json).to match "map_005-hub.svg" + end +end diff --git a/spec/serializers/api/enterprise_shopfront_serializer_spec.rb b/spec/serializers/api/enterprise_shopfront_serializer_spec.rb new file mode 100644 index 0000000000..eb303cb49d --- /dev/null +++ b/spec/serializers/api/enterprise_shopfront_serializer_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +describe Api::EnterpriseShopfrontSerializer do + let!(:hub) { create(:distributor_enterprise, with_payment_and_shipping: true) } + let!(:producer) { create(:supplier_enterprise) } + let!(:relationship) { create(:enterprise_relationship, parent: hub, child: producer) } + + let!(:taxon1) { create(:taxon, name: 'Meat') } + let!(:taxon2) { create(:taxon, name: 'Veg') } + let!(:product) { create(:product, supplier: producer, primary_taxon: taxon1, taxons: [taxon1, taxon2] ) } + + let(:close_time) { 2.days.from_now } + let!(:oc) { create(:simple_order_cycle, orders_close_at: close_time, distributors: [hub]) } + + let!(:ex) { + create(:exchange, order_cycle: oc, incoming: false, + sender: producer, receiver: hub) + } + + let(:serializer) { Api::EnterpriseShopfrontSerializer.new hub } + + before do + ex.variants << product.variants.first + end + + it "serializes next order cycle close time" do + expect(serializer.serializable_hash[:orders_close_at]).to match oc.orders_close_at + end + + it "serializes shipping method types" do + expect(serializer.serializable_hash[:pickup]).to eq false + expect(serializer.serializable_hash[:delivery]).to eq true + end + + it "serialises an array of hubs" do + expect(serializer.serializable_hash[:hubs]).to be_a ActiveModel::ArraySerializer + expect(serializer.serializable_hash[:hubs].to_json).to match hub.name + end + + it "serialises an array of producers" do + expect(serializer.serializable_hash[:producers]).to be_a ActiveModel::ArraySerializer + expect(serializer.serializable_hash[:producers].to_json).to match producer.name + end + + it "serialises taxons" do + expect(serializer.serializable_hash[:taxons]).to be_a ActiveModel::ArraySerializer + expect(serializer.serializable_hash[:taxons].to_json).to match 'Meat' + expect(serializer.serializable_hash[:taxons].to_json).to match 'Veg' + end +end