From 7356d0fe77e87d597dd163d81d4d9d8bd6cc20b7 Mon Sep 17 00:00:00 2001 From: Gaetan Riou Date: Fri, 7 Aug 2020 18:10:51 +1000 Subject: [PATCH] move url filter functionality to service ProductFiltersService --- .../admin/bulk_product_update.js.coffee | 63 ++++++++--------- .../directives/select2_min_search.js.coffee | 4 +- .../admin/services/product_filters.js.coffee | 23 +++++++ .../admin/products/index/_filters.html.haml | 8 +-- .../admin/bulk_product_update_spec.js.coffee | 49 ++++++------- .../services/product_filters_spec.js.coffee | 68 +++++++++++++++++++ 6 files changed, 151 insertions(+), 64 deletions(-) create mode 100644 app/assets/javascripts/admin/services/product_filters.js.coffee create mode 100644 spec/javascripts/unit/admin/services/product_filters_spec.js.coffee diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index 7571aaaaea..5c011de5bb 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -1,4 +1,4 @@ -angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $filter, $http, $window, $location, $httpParamSerializer, BulkProducts, DisplayProperties, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, Columns, tax_categories, RequestMonitor, SortOptions, ErrorsParser) -> +angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $filter, $http, $window, $location, BulkProducts, DisplayProperties, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, Columns, tax_categories, RequestMonitor, SortOptions, ErrorsParser, ProductFiltersService) -> $scope.StatusMessage = StatusMessage $scope.columns = Columns.columns @@ -14,11 +14,13 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout ] productFilters = ['producerFilter', 'categoryFilter', 'query', 'sorting', 'importDateFilter'] - $scope.producerFilter = "" - $scope.categoryFilter = "" - $scope.importDateFilter = "" - $scope.query = "" - $scope.sorting = "" + $scope.q = { + producerFilter: "" + categoryFilter: "" + importDateFilter: "" + query: "" + sorting: "" + } $scope.producers = producers $scope.taxons = Taxons.all @@ -30,13 +32,8 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout $scope.sortOptions = SortOptions - loadFilterFromUrl = -> - filters = $location.search() - for filter in productFilters - $scope[filter] = if filters[filter] then filters[filter] else "" - $scope.initialise = -> - loadFilterFromUrl() + $scope.q = ProductFiltersService.loadFromUrl($location.search()) $scope.fetchProducts() $scope.$watchCollection '[query, producerFilter, categoryFilter, importDateFilter, per_page]', -> @@ -50,33 +47,33 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout filters = {} for filter in productFilters filters[filter] = $scope[filter] if $scope[filter] - + filters $scope.fetchProducts = -> removeClearedValues() params = { - 'q[name_cont]': $scope.query, - 'q[supplier_id_eq]': $scope.producerFilter, - 'q[primary_taxon_id_eq]': $scope.categoryFilter, - 'q[s]': $scope.sorting, - import_date: $scope.importDateFilter, + 'q[name_cont]': $scope.q.query, + 'q[supplier_id_eq]': $scope.q.producerFilter, + 'q[primary_taxon_id_eq]': $scope.q.categoryFilter, + 'q[s]': $scope.q.sorting, + import_date: $scope.q.importDateFilter, page: $scope.page, per_page: $scope.per_page } RequestMonitor.load(BulkProducts.fetch(params).$promise).then -> # update url with the filters used - $location.search(generateFilter()) + $location.search(ProductFiltersService.generate($scope.q)) $scope.resetProducts() removeClearedValues = -> - delete $scope.producerFilter if $scope.producerFilter == "0" - delete $scope.categoryFilter if $scope.categoryFilter == "0" - delete $scope.importDateFilter if $scope.importDateFilter == "0" + delete $scope.q.producerFilter if $scope.q.producerFilter == "0" + delete $scope.q.categoryFilter if $scope.q.categoryFilter == "0" + delete $scope.q.importDateFilter if $scope.q.importDateFilter == "0" $timeout -> if $scope.showLatestImport - $scope.importDateFilter = $scope.importDates[1].id + $scope.q.importDateFilter = $scope.importDates[1].id $scope.resetProducts = -> DirtyProducts.clear() @@ -106,10 +103,10 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout $scope.visibleTab = tab $scope.resetSelectFilters = -> - $scope.query = "" - $scope.producerFilter = "0" - $scope.categoryFilter = "0" - $scope.importDateFilter = "0" + $scope.q.query = "" + $scope.q.producerFilter = "0" + $scope.q.categoryFilter = "0" + $scope.q.importDateFilter = "0" $scope.fetchProducts() $scope.$watch 'sortOptions', (sort) -> @@ -127,9 +124,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout $scope.editWarn = (product, variant) -> if confirm_unsaved_changes() - filterUrl = $httpParamSerializer(generateFilter()) - filterUrl = "?#{filterUrl}" if filterUrl isnt "" - $window.location.href = "#{editProductUrl(product, variant)}#{filterUrl}" + $window.location.href = ProductFiltersService.buildUrl(editProductUrl(product, variant), $scope.q) $scope.toggleShowAllVariants = -> showVariants = !DisplayProperties.showVariants 0 @@ -226,10 +221,10 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout data: products: productsToSubmit filters: - 'q[name_cont]': $scope.query - 'q[supplier_id_eq]': $scope.producerFilter - 'q[primary_taxon_id_eq]': $scope.categoryFilter - import_date: $scope.importDateFilter + 'q[name_cont]': $scope.q.query + 'q[supplier_id_eq]': $scope.q.producerFilter + 'q[primary_taxon_id_eq]': $scope.q.categoryFilter + import_date: $scope.q.importDateFilter page: $scope.page per_page: $scope.per_page ).success((data) -> diff --git a/app/assets/javascripts/admin/directives/select2_min_search.js.coffee b/app/assets/javascripts/admin/directives/select2_min_search.js.coffee index 0d9e9faa75..dac4aa1b7f 100644 --- a/app/assets/javascripts/admin/directives/select2_min_search.js.coffee +++ b/app/assets/javascripts/admin/directives/select2_min_search.js.coffee @@ -5,8 +5,8 @@ angular.module("ofn.admin").directive "ofnSelect2MinSearch", -> minimumResultsForSearch: attrs.ofnSelect2MinSearch ngModel.$formatters.push (value) -> - # select2 populate options with a value like "number:3" or "string:category" but - # select2('val', value) doesn't do the translation for us as one would expect + # select2 populates options with a value like "number:3" or "string:category" but + # select2('val', value) doesn't do the type conversion for us as one would expect if isNaN(value) element.select2('val', "string:#{value}") else diff --git a/app/assets/javascripts/admin/services/product_filters.js.coffee b/app/assets/javascripts/admin/services/product_filters.js.coffee new file mode 100644 index 0000000000..c6d3574df0 --- /dev/null +++ b/app/assets/javascripts/admin/services/product_filters.js.coffee @@ -0,0 +1,23 @@ +angular.module("ofn.admin").factory "ProductFiltersService", ($httpParamSerializer) -> + new class ProductFiltersService + productFilters: ['producerFilter', 'categoryFilter', 'query', 'sorting', 'importDateFilter'] + + loadFromUrl: (filters) -> + loadedFilters = {} + for filter in @productFilters + loadedFilters[filter] = if filters[filter] then filters[filter] else "" + + loadedFilters + + generate: (ctrlFilters) -> + filters = {} + for filter in @productFilters + filters[filter] = ctrlFilters[filter] if ctrlFilters[filter] + + filters + + buildUrl: (baseUrl, ctrlFilters) -> + filterUrl = $httpParamSerializer(@generate(ctrlFilters)) + filterUrl = "?#{filterUrl}" if filterUrl isnt "" + + "#{baseUrl}#{filterUrl}" diff --git a/app/views/spree/admin/products/index/_filters.html.haml b/app/views/spree/admin/products/index/_filters.html.haml index 4493e95258..8c08b3af1b 100644 --- a/app/views/spree/admin/products/index/_filters.html.haml +++ b/app/views/spree/admin/products/index/_filters.html.haml @@ -5,20 +5,20 @@ .quick_search.three.columns.alpha %label{ for: 'quick_filter' } %br - %input.quick-search.fullwidth{ ng: {model: 'query'}, name: "quick_filter", type: 'text', placeholder: t('admin.quick_search'), "ng-keypress" => "$event.keyCode === 13 && fetchProducts()" } + %input.quick-search.fullwidth{ ng: {model: 'q.query'}, name: "quick_filter", type: 'text', placeholder: t('admin.quick_search'), "ng-keypress" => "$event.keyCode === 13 && fetchProducts()" } .one.columns   .filter_select.three.columns %label{ for: 'producer_filter' }= t 'producer' %br - %select.fullwidth{ id: 'producer_filter', 'ofn-select2-min-search' => 5, ng: {model: 'producerFilter', options: 'producer.id as producer.name for producer in producers'} } + %select.fullwidth{ id: 'producer_filter', 'ofn-select2-min-search' => 5, ng: {model: 'q.producerFilter', options: 'producer.id as producer.name for producer in producers'} } .filter_select.three.columns %label{ for: 'category_filter' }= t 'category' %br - %select.fullwidth{ id: 'category_filter', 'ofn-select2-min-search' => 5, ng: {model: 'categoryFilter', options: 'taxon.id as taxon.name for taxon in taxons'} } + %select.fullwidth{ id: 'category_filter', 'ofn-select2-min-search' => 5, ng: {model: 'q.categoryFilter', options: 'taxon.id as taxon.name for taxon in taxons'} } .filter_select.three.columns %label{ for: 'import_filter' } Import Date %br - %select.fullwidth{ id: 'import_date_filter', 'ofn-select2-min-search' => 5, ng: {model: 'importDateFilter', init: "importDates = #{@import_dates}; showLatestImport = #{@show_latest_import}", options: 'date.id as date.name for date in importDates'} } + %select.fullwidth{ id: 'import_date_filter', 'ofn-select2-min-search' => 5, ng: {model: 'q.importDateFilter', init: "importDates = #{@import_dates}; showLatestImport = #{@show_latest_import}", options: 'date.id as date.name for date in importDates'} } .filter_clear.three.columns.omega %label{ for: 'clear_all_filters' } diff --git a/spec/javascripts/unit/admin/bulk_product_update_spec.js.coffee b/spec/javascripts/unit/admin/bulk_product_update_spec.js.coffee index d6f7456856..b6dc92e051 100644 --- a/spec/javascripts/unit/admin/bulk_product_update_spec.js.coffee +++ b/spec/javascripts/unit/admin/bulk_product_update_spec.js.coffee @@ -246,7 +246,7 @@ describe "filtering products for submission to database", -> ] describe "AdminProductEditCtrl", -> - $ctrl = $scope = $timeout = $httpBackend = BulkProducts = DirtyProducts = DisplayProperties = windowStub = null + $ctrl = $scope = $timeout = $httpBackend = BulkProducts = DirtyProducts = DisplayProperties = ProductFiltersService = windowStub = null beforeEach -> module "ofn.admin" @@ -258,7 +258,7 @@ describe "AdminProductEditCtrl", -> $provide.value 'columns', [] null - beforeEach inject((_$controller_, _$timeout_, $rootScope, _$httpBackend_, _BulkProducts_, _DirtyProducts_, _DisplayProperties_) -> + beforeEach inject((_$controller_, _$timeout_, $rootScope, _$httpBackend_, _BulkProducts_, _DirtyProducts_, _DisplayProperties_, _ProductFiltersService_) -> $scope = $rootScope.$new() $ctrl = _$controller_ $timeout = _$timeout_ @@ -266,8 +266,9 @@ describe "AdminProductEditCtrl", -> BulkProducts = _BulkProducts_ DirtyProducts = _DirtyProducts_ DisplayProperties = _DisplayProperties_ + ProductFiltersService = _ProductFiltersService_ - # Stub the window object so we don't get redirected when href is updated + # Stub the window object so we don't get redirected when href is updated windowStub = {navigator: {userAgent: 'foo'}, location: {href: ''}} $ctrl "AdminProductEditCtrl", {$scope: $scope, $timeout: $timeout, $window: windowStub} @@ -282,7 +283,7 @@ describe "AdminProductEditCtrl", -> expect($scope.fetchProducts.calls.count()).toBe 1 it "gets a list of products applying filters from the url", inject ($location) -> - query = 'lala' + query = 'lala' producerFilter = 2 categoryFilter = 5 sorting = 'name desc' @@ -291,11 +292,11 @@ describe "AdminProductEditCtrl", -> $scope.initialise() - expect($scope.query).toBe query - expect($scope.producerFilter).toBe producerFilter - expect($scope.categoryFilter).toBe categoryFilter - expect($scope.sorting).toBe sorting - expect($scope.importDateFilter).toBe importDateFilter + expect($scope.q.query).toBe query + expect($scope.q.producerFilter).toBe producerFilter + expect($scope.q.categoryFilter).toBe categoryFilter + expect($scope.q.sorting).toBe sorting + expect($scope.q.importDateFilter).toBe importDateFilter describe "fetching products", -> $q = null @@ -316,18 +317,18 @@ describe "AdminProductEditCtrl", -> $scope.$digest() expect($scope.resetProducts).toHaveBeenCalled() - it "updates url wihth filter after data has been received", inject ($location, $window) -> + it "updates url with filter after data has been received", inject ($location, $window) -> query = 'lala' producerFilter = 2 categoryFilter = 5 sorting = 'name desc' importDateFilter = '2020-06-08' - $scope.query = query - $scope.producerFilter = producerFilter - $scope.categoryFilter = categoryFilter - $scope.sorting = sorting - $scope.importDateFilter = importDateFilter + $scope.q.query = query + $scope.q.producerFilter = producerFilter + $scope.q.categoryFilter = categoryFilter + $scope.q.sorting = sorting + $scope.q.importDateFilter = importDateFilter $scope.fetchProducts() $scope.$digest() @@ -995,14 +996,14 @@ describe "AdminProductEditCtrl", -> it 'should load edit product page including the selected filters', inject ($httpParamSerializer) -> query = 'lala' category = 3 - $scope.query = query - $scope.categoryFilter = category + $scope.q.query = query + $scope.q.categoryFilter = category # use $httpParamSerializer as it will sort parameters alphabetically expectedFilter = $httpParamSerializer({ query: query, categoryFilter: category }) $scope.editWarn(testProduct, null) - + expect(windowStub.location.href).toBe( "/admin/products/#{testProduct.permalink_live}/edit?#{expectedFilter}" ) @@ -1010,13 +1011,13 @@ describe "AdminProductEditCtrl", -> describe "filtering products", -> describe "clearing filters", -> it "resets filter variables", -> - $scope.query = "lala" - $scope.producerFilter = "5" - $scope.categoryFilter = "6" + $scope.q.query = "lala" + $scope.q.producerFilter = "5" + $scope.q.categoryFilter = "6" $scope.resetSelectFilters() - expect($scope.query).toBe "" - expect($scope.producerFilter).toBeUndefined - expect($scope.categoryFilter).toBeUndefined + expect($scope.q.query).toBe "" + expect($scope.q.producerFilter).toBeUndefined + expect($scope.q.categoryFilter).toBeUndefined describe "converting arrays of objects with ids to an object with ids as keys", -> diff --git a/spec/javascripts/unit/admin/services/product_filters_spec.js.coffee b/spec/javascripts/unit/admin/services/product_filters_spec.js.coffee new file mode 100644 index 0000000000..3ffd895f45 --- /dev/null +++ b/spec/javascripts/unit/admin/services/product_filters_spec.js.coffee @@ -0,0 +1,68 @@ +describe "ProductFiltersService service", -> + ProductFiltersService = null + + beforeEach -> + module "ofn.admin" + + beforeEach inject (_ProductFiltersService_) -> + ProductFiltersService = _ProductFiltersService_ + + describe "loadFromUrl", -> + it "should return a hash with value populated for filters existing in parameter", -> + producerFilter = 2 + query = 'fruit' + + filters = ProductFiltersService.loadFromUrl(producerFilter: producerFilter, query: query) + + expect(filters.producerFilter).toBe producerFilter + expect(filters.query).toBe query + + it "should return a hash with empty value for filters missing from parameter", -> + filters = ProductFiltersService.loadFromUrl({}) + + expect(filters.producerFilter).toBe "" + expect(filters.query).toBe "" + expect(filters.categoryFilter).toBe "" + expect(filters.sorting).toBe "" + expect(filters.importDateFilter).toBe "" + + describe "generate", -> + it 'should filter given hash with productFilters', -> + producerFilter = 2 + query = 'fruit' + + filters = ProductFiltersService.generate( + producerFilter: producerFilter, query: query, otherParam: 'otherParam' + ) + + expect(filters.producerFilter).toBe producerFilter + expect(filters.query).toBe query + expect(filters.otherParam).toBe undefined + + describe "buildUrl", -> + it 'should return a url adding filters to the baseUrl', inject ($httpParamSerializer) -> + query = 'lala' + producerFilter = 2 + categoryFilter = 5 + sorting = 'name desc' + importDateFilter = '2020-06-08' + filters = { + producerFilter: producerFilter + categoryFilter: categoryFilter + query: query + sorting: sorting + importDateFilter: importDateFilter + } + baseUrl = "openfoodnetwork.org.au" + + url = ProductFiltersService.buildUrl(baseUrl, filters) + + expectedFilters = $httpParamSerializer(filters) + expect(url).toBe("#{baseUrl}?#{expectedFilters}") + + it 'should return baseUrl if filters are empty', -> + baseUrl = "openfoodnetwork.org.au" + + url = ProductFiltersService.buildUrl(baseUrl, {}) + expect(url).toBe baseUrl +