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

[WIP] Refactor catalog module #2890

Merged
merged 90 commits into from
Jul 1, 2019
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
5d2827c
prepare module draft
patzick May 8, 2019
3889092
add default values
patzick May 9, 2019
9d2462a
init module actions
patzick May 9, 2019
16ca33e
move helper from old module
patzick May 9, 2019
0207857
getting available filters
patzick May 9, 2019
087d45e
invoke search with current filters
patzick May 9, 2019
e834b21
current category from getter
patzick May 9, 2019
d372696
default value as string
patzick May 9, 2019
59f2130
add todo for multiple filters
patzick May 9, 2019
8a9d28d
switching search filters
patzick May 9, 2019
d2b4664
switching filters query
patzick May 9, 2019
23e70d7
multiple dilters draft and reseting filters
patzick May 9, 2019
c60508d
migrate filter components
patzick May 9, 2019
54a231b
migrate category page
patzick May 9, 2019
f45fe5b
filters and currentCategory as getters
patzick May 10, 2019
d027a4c
active filters
patzick May 10, 2019
8e71fdf
recomputing getters
patzick May 10, 2019
8f9e795
ui active filters
patzick May 10, 2019
f5cdd82
add sorty by property
patzick May 10, 2019
0a9f7d5
system filters not visible on search filters
patzick May 13, 2019
f979051
get turned off by default
patzick May 13, 2019
fece9d5
add category breadcrumbs
patzick May 13, 2019
fb380e7
stop using of sidebar mixin
patzick May 13, 2019
bd32c8c
add multiple filters support
patzick May 15, 2019
9084808
merge develop
patzick May 15, 2019
487a847
moved filter logic to mixin
patzick May 15, 2019
a819f6f
refactor calculate getters
patzick May 15, 2019
25eabd7
move logic to getters
patzick May 16, 2019
652afe0
move logic to directory
patzick May 16, 2019
767aa6d
change loadCategory invocation
patzick May 16, 2019
4ee0279
change router as separate action
patzick May 19, 2019
97e0981
add category service
patzick May 19, 2019
adc60ad
loading categories
patzick May 19, 2019
d97b2a1
change filter method with tests
patzick May 19, 2019
eab2243
extract getFiltersFromQuery with tests
patzick May 19, 2019
8fd4f42
add support for multiple prices with tests
patzick May 20, 2019
af8db3a
Merge branch 'develop' into magento-catalog
patzick May 20, 2019
98034ef
Merge branch 'magento-catalog' of https://github.com/patzick/vue-stor…
patzick May 20, 2019
35293e3
add support for filters as query and path params
patzick May 20, 2019
b47b34b
filters per category
patzick May 20, 2019
7d4633b
refactor -magento to -next
patzick May 22, 2019
e7bdcf2
merge develop
patzick May 29, 2019
93d2588
support finding configurable child with multiple filters
patzick May 30, 2019
771d734
configure fetched products
patzick May 30, 2019
fa52a84
load category filters before picking filters
patzick May 30, 2019
f0a7259
Merge branch 'develop' into magento-catalog
patzick May 30, 2019
738ddce
test caching on server
patzick Jun 3, 2019
6208010
improve service worker catalog cache
patzick Jun 3, 2019
4a687c5
use filters on product page
patzick Jun 4, 2019
abfa197
cleaning code and dependencies
patzick Jun 7, 2019
ed6492f
Merge branch 'magento-catalog' of https://github.com/patzick/vue-stor…
patzick Jun 7, 2019
0549431
merge develop
patzick Jun 11, 2019
447130b
Merge branch 'develop' into magento-catalog
patzick Jun 13, 2019
6867930
linter errors
patzick Jun 14, 2019
c648c3c
correct getter
patzick Jun 14, 2019
e4665e0
default argument
patzick Jun 17, 2019
2e9e140
filter event
patzick Jun 17, 2019
541add0
Merge branch 'develop' into magento-catalog
patzick Jun 19, 2019
c908fdb
cleanup and add types to category service
patzick Jun 22, 2019
02f542a
linter and category interface
patzick Jun 22, 2019
07a52d1
categories as map
patzick Jun 22, 2019
9d6909e
add missing translation
patzick Jun 24, 2019
bce5a2d
cleanup
patzick Jun 24, 2019
d1ca817
recurrent breadcrumbs
patzick Jun 24, 2019
67087ea
mock configuration for configureProductAsync
patzick Jun 24, 2019
867f582
additional test to check buildProductsQuery
patzick Jun 24, 2019
37aca5e
categories hierarchy map and loading categories to breadcrumbs by one…
patzick Jun 24, 2019
64aeff6
Change caegoryFilters to object
patzick Jun 25, 2019
0fc7b6d
breadcrumbs from categories hierarchy map
patzick Jun 25, 2019
ba9f93b
do no t duplicate optionLabel helper
patzick Jun 25, 2019
94e5d23
composition function for proper SSR/CSR caching
patzick Jun 25, 2019
3da38c0
code cleanup
patzick Jun 25, 2019
35638d3
add default breadcrumb value
patzick Jun 25, 2019
7329923
improve filters reactivity on product page
patzick Jun 25, 2019
097eebb
fix problem with available options
patzick Jun 25, 2019
cfafe84
merge develop
patzick Jun 25, 2019
b868176
sorting filters
patzick Jun 25, 2019
dee2ead
lnter
patzick Jun 25, 2019
e345a04
Merge branch 'develop' into magento-catalog
patzick Jun 26, 2019
a0e993d
add infinite scroll pagination
patzick Jun 26, 2019
9659e6f
categoryHierarchyMap
patzick Jun 26, 2019
0f61390
Merge branch 'develop' into magento-catalog
patzick Jun 26, 2019
aa41c09
remove hierarchyMap - build from specific category
patzick Jun 27, 2019
8545d57
Fix problem with products caching on SSR
patzick Jun 27, 2019
52e8854
refactor category page getters names
patzick Jun 27, 2019
61ae322
Merge branch 'develop' into magento-catalog
patzick Jun 28, 2019
6153d69
remove unused hook
patzick Jun 28, 2019
779ce67
fix for product option values incomplete
patzick Jul 1, 2019
f31ac67
prefetch stock for cached products
patzick Jul 1, 2019
ddc3cde
Merge branch 'develop' into magento-catalog
patzick Jul 1, 2019
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
4 changes: 2 additions & 2 deletions core/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ export function productThumbnailPath (product, ignoreConfig = false) {
return thumbnail
}

export function buildFilterProductsQuery (currentCategory, chosenFilters, defaultFilters = null) {
let filterQr = baseFilterProductsQuery(currentCategory, defaultFilters == null ? rootStore.state.config.products.defaultFilters : defaultFilters)
export function buildFilterProductsQuery (currentCategory, chosenFilters = {}, defaultFilters = null) {
let filterQr = baseFilterProductsQuery(currentCategory, !defaultFilters ? rootStore.state.config.products.defaultFilters : defaultFilters)

// add choosedn filters
for (let code of Object.keys(chosenFilters)) {
Expand Down
2 changes: 1 addition & 1 deletion core/lib/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function isOnline () : boolean {
* @param {Int} size page size
* @return {Promise}
*/
export const quickSearchByQuery = async ({ query, start = 0, size = 50, entityType = 'product', sort = '', storeCode = null, excludeFields = null, includeFields = null }): Promise<SearchResponse> => {
export const quickSearchByQuery = async ({ query = {}, start = 0, size = 50, entityType = 'product', sort = '', storeCode = null, excludeFields = null, includeFields = null } = {}): Promise<SearchResponse> => {
const searchAdapter = await getSearchAdapter()
if (size <= 0) size = 50
if (start < 0) start = 0
Expand Down
39 changes: 39 additions & 0 deletions core/modules/catalog-magento/helpers/optionLabel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Helper method for getting attribute name - TODO: to be moved to external/shared helper
*
* @param {String} attributeCode
* @param {String} optionId - value to get label for
*/
import toString from 'lodash-es/toString'

export function optionLabel (state, { attributeKey, searchBy = 'code', optionId }) {
let attrCache = state.labels[attributeKey]

if (attrCache) {
let label = attrCache[optionId]

if (label) {
return label
}
}
let attr = state['list_by_' + searchBy][attributeKey]
if (attr) {
let opt = attr.options.find((op) => { // TODO: cache it in memory
if (toString(op.value) === toString(optionId)) {
return op
}
}) // TODO: i18n support with multi-website attribute names

if (opt) {
if (!state.labels[attributeKey]) {
state.labels[attributeKey] = {}
}
state.labels[attributeKey][optionId] = opt.label
return opt ? opt.label : optionId
} else {
return optionId
}
} else {
return optionId
}
}
15 changes: 15 additions & 0 deletions core/modules/catalog-magento/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// import { productModule } from './store/product'
// import { attributeModule } from './store/attribute'
// import { stockModule } from './store/stock'
// import { taxModule } from './store/tax'
import { categoryModule } from './store/category'
import { createModule } from '@vue-storefront/core/lib/module'
// import { beforeRegistration } from './hooks/beforeRegistration'

export const KEY = 'catalog-magento'
export default createModule({
key: KEY,
store: { modules: [
{ key: 'category-magento', module: categoryModule }
] }
})
16 changes: 16 additions & 0 deletions core/modules/catalog-magento/store/category/CategoryState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export default interface CategoryState {
categories: any,
availableFilters: any,
products: any
// list: any,
// current: any,
// filters: {
// available: any,
// chosen: any
// },
// breadcrumbs: {
// routes: any
// },
// current_product_query: any,
// current_path: any
}
132 changes: 132 additions & 0 deletions core/modules/catalog-magento/store/category/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// import Vue from 'vue'
import { ActionTree } from 'vuex'
import * as types from './mutation-types'
import RootState from '@vue-storefront/core/types/RootState'
import CategoryState from './CategoryState'
import { quickSearchByQuery } from '@vue-storefront/core/lib/search'
import { buildFilterProductsQuery } from '@vue-storefront/core/helpers'
import config from 'config'
import trim from 'lodash-es/trim'
import toString from 'lodash-es/toString'
import { currentStoreView } from '@vue-storefront/core/lib/multistore'
import { optionLabel } from '../../helpers/optionLabel'
import { router } from '@vue-storefront/core/app'
import FilterVariant from '../../types/FilterVariant';

const actions: ActionTree<CategoryState, RootState> = {
/**
* Initialise category module.
* - gets available categories
* - gets available filters for current category
*/
async initCategoryModule ({ getters, dispatch }) {
if (!getters.getCategories.length) {
await dispatch('getCategories')
await dispatch('getAvailableFilters')
patzick marked this conversation as resolved.
Show resolved Hide resolved
}
},
async searchProducts ({ commit, getters, dispatch }, { filters, route } = {}) {
await dispatch('initCategoryModule')
const searchQuery = getters.calculateFilters(filters)
patzick marked this conversation as resolved.
Show resolved Hide resolved
const searchCategory = getters.calculateCurrentCategory(route)
let filterQr = buildFilterProductsQuery(searchCategory, searchQuery.filters)
const searchResult = await quickSearchByQuery({ query: filterQr, sort: searchQuery.sort })
commit(types.CATEGORY_SET_PRODUCTS, searchResult.items)
patzick marked this conversation as resolved.
Show resolved Hide resolved

return searchResult.items
},
async getCurrentCategory ({ dispatch, rootState }) {
return await dispatch('category/single', { key: config.products.useMagentoUrlKeys ? 'url_key' : 'slug', value: rootState.route.params.slug }, {root: true})
},
async fetchCategories ({ dispatch }) {
const res = await dispatch('category/list', {}, {root: true})
return res.items
},
async getCategories ({ commit, dispatch }) {
const categories = await dispatch('fetchCategories')
commit(types.CATEGORY_SET_CATEGORIES, categories)
return categories
},
patzick marked this conversation as resolved.
Show resolved Hide resolved
/**
* Fetch and process filters from current category and sets them in available filters.
*/
async getAvailableFilters ({ commit, getters, rootState }) {
const filters = {}
const searchCategory = getters.getCurrentCategory
let filterQr = buildFilterProductsQuery(searchCategory)
const searchResult = await quickSearchByQuery({ query: filterQr })
if (searchResult && searchResult.aggregations) { // populate filter aggregates
for (let attrToFilter of config.products.defaultFilters) { // fill out the filter options
let filterOptions:Array<FilterVariant> = []

let uniqueFilterValues = new Set<string>()
if (attrToFilter !== 'price') {
if (searchResult.aggregations['agg_terms_' + attrToFilter]) {
let buckets = searchResult.aggregations['agg_terms_' + attrToFilter].buckets
if (searchResult.aggregations['agg_terms_' + attrToFilter + '_options']) {
buckets = buckets.concat(searchResult.aggregations['agg_terms_' + attrToFilter + '_options'].buckets)
}

for (let option of buckets) {
uniqueFilterValues.add(toString(option.key))
}
}

uniqueFilterValues.forEach(key => {
const label = optionLabel(rootState.attribute, { attributeKey: attrToFilter, optionId: key })
if (trim(label) !== '') { // is there any situation when label could be empty and we should still support it?
filterOptions.push({
id: key,
label: label,
type: attrToFilter
})
}
});
} else { // special case is range filter for prices
const storeView = currentStoreView()
const currencySign = storeView.i18n.currencySign
if (searchResult.aggregations['agg_range_' + attrToFilter]) {
let index = 0
let count = searchResult.aggregations['agg_range_' + attrToFilter].buckets.length
for (let option of searchResult.aggregations['agg_range_' + attrToFilter].buckets) {
filterOptions.push({
id: option.key,
type: attrToFilter,
from: option.from,
to: option.to,
label: (index === 0 || (index === count - 1)) ? (option.to ? '< ' + currencySign + option.to : '> ' + currencySign + option.from) : currencySign + option.from + (option.to ? ' - ' + option.to : '')// TODO: add better way for formatting, extract currency sign
})
index++
}
}
}
filters[attrToFilter] = filterOptions
}
// Add sort to available filters
let variants = []
Object.keys(config.products.sortByAttributes).map(label => {
variants.push({
label: label,
id: config.products.sortByAttributes[label],
type: 'sort'
})
})
filters['sort'] = variants
}
commit(types.CATEGORY_SET_AVAILABLE_FILTERS, filters)
},
async switchSearchFilter({ dispatch }, filterVariant:FilterVariant) {
patzick marked this conversation as resolved.
Show resolved Hide resolved
const query = Object.assign({}, router.currentRoute.query) // await dispatch('getCurrentFilters')
if(query[filterVariant.type] && query[filterVariant.type] === filterVariant.id) {
delete query[filterVariant.type]
} else {
query[filterVariant.type] = filterVariant.id
}
router.push({query})
},
async resetFilters() {
router.push({query: {}})
}
}

export default actions
44 changes: 44 additions & 0 deletions core/modules/catalog-magento/store/category/getters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { GetterTree } from 'vuex'
import RootState from '@vue-storefront/core/types/RootState'
import CategoryState from './CategoryState'

const specialFields = ['sort']

const getters: GetterTree<CategoryState, RootState> = {
getCategories: (state) => state.categories,
getCategoryProducts: (state) => state.products,
getAvailableFilters: state => state.availableFilters,
calculateCurrentCategory: (state, getters, rootState) => (route) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming, calculateCutrentCategory is totaly misleading - no clue what this getter is doing. You can calculate tax, price, any other number but find me a calculator where You can calculate category :D Makes no sense from the naming point of view. Moreover. Getters should be more obvious/self explaining

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moreover - again, with getters You can't do any async. Which makes the computations You have in it limited. I belive it should be moved to Vuex action instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, it was first what came up to my mind, we can at later stage have some discussions about naming parametrized getters. I used calculate instead get for now to have obvious info when can i just use getter and when i need to provide parameter od just use brackets :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say get is easier to get :D

const currentRoute = route ? route : rootState.route
return getters.getCategories.find(category => currentRoute.path.includes(category.url_path)) || {}
},
getCurrentCategory: (state, getters, rootState) => getters.calculateCurrentCategory(rootState.route),
calculateFilters: (state, getters, rootState) => (filters) => {
const currentQuery = filters ? filters : rootState.route.query
const searchQuery = {
filters: {}
}
Object.keys(currentQuery).forEach(filterKey => {
const filter = getters.getAvailableFilters[filterKey]
const queryValue = currentQuery[filterKey]
if (!filter) return
if (specialFields.includes(filterKey)) {
searchQuery[filterKey] = queryValue
} else if (Array.isArray(queryValue)) {
queryValue.map(singleValue => {
const variant = filter.find(filterVariant => filterVariant.id === singleValue)
if (!searchQuery.filters[filterKey] || !Array.isArray(searchQuery.filters[filterKey])) searchQuery.filters[filterKey] = []
searchQuery.filters[filterKey].push({...variant, attribute_code: filterKey})
})
} else {
const variant = filter.find(filterVariant => filterVariant.id === queryValue)
searchQuery.filters[filterKey] = {...variant, attribute_code: filterKey}
}
})
return searchQuery
},
patzick marked this conversation as resolved.
Show resolved Hide resolved
getCurrentFilters: (state, getters, rootState) => getters.calculateFilters(rootState.route.query).filters,
hasActiveFilters: (state, getters) => !!Object.keys(getters.getCurrentFilters).length
}

export default getters
18 changes: 18 additions & 0 deletions core/modules/catalog-magento/store/category/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Module } from 'vuex'
import actions from './actions'
import getters from './getters'
import mutations from './mutations'
import RootState from '@vue-storefront/core/types/RootState'
import CategoryState from './CategoryState'

export const categoryModule: Module<CategoryState, RootState> = {
namespaced: true,
state: {
categories: [],
availableFilters: {},
products: []
},
getters,
actions,
mutations
}
4 changes: 4 additions & 0 deletions core/modules/catalog-magento/store/category/mutation-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const SN_CATEGORY = 'category'
export const CATEGORY_SET_PRODUCTS = `${SN_CATEGORY}/SET_PRODUCTS`
export const CATEGORY_SET_CATEGORIES = `${SN_CATEGORY}/SET_CATEGORIES`
export const CATEGORY_SET_AVAILABLE_FILTERS = `${SN_CATEGORY}/SET_AVAILABLE_FILTERS`
18 changes: 18 additions & 0 deletions core/modules/catalog-magento/store/category/mutations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// import Vue from 'vue'
import { MutationTree } from 'vuex'
import * as types from './mutation-types'
import CategoryState from './CategoryState'

const mutations: MutationTree<CategoryState> = {
[types.CATEGORY_SET_PRODUCTS] (state, products = []) {
state.products = products
},
[types.CATEGORY_SET_CATEGORIES] (state, categories = []) {
state.categories = categories
},
[types.CATEGORY_SET_AVAILABLE_FILTERS] (state, availableFilters = {}) {
state.availableFilters = availableFilters
}
}

export default mutations
7 changes: 7 additions & 0 deletions core/modules/catalog-magento/types/FilterVariant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default interface FilterVariant {
id: string,
label: string,
type: string,
from?: string,
to?: string
}
14 changes: 13 additions & 1 deletion core/modules/catalog/components/CategorySort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,24 @@ export const CategorySort = {
methods: {
// emit to category, todo: move all logic inside
sort () {
this.$bus.$emit('list-change-sort', { attribute: this.sortby })
this.$emit('sortChange', this.sortby)
patzick marked this conversation as resolved.
Show resolved Hide resolved
// this.$bus.$emit('list-change-sort', { attribute: this.sortby })
}
},
computed: {
sortingOptions () {
return this.$store.state.config.products.sortByAttributes
},
sortingVariants () {
let variants = []
Object.keys(this.sortingOptions).map(label => {
variants.push({
label: label,
id: this.sortingOptions[label],
type: 'sort'
})
})
return variants
}
}
}
2 changes: 1 addition & 1 deletion core/modules/catalog/components/ProductCustomOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const ProductCustomOption = {
label: {
type: String,
required: false,
default: () => false
default: ''
},
id: {
type: null,
Expand Down
2 changes: 2 additions & 0 deletions src/modules/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// import { extendModule } from '@vue-storefront/core/lib/module'
import { VueStorefrontModule } from '@vue-storefront/core/lib/module'
import { Catalog } from "@vue-storefront/core/modules/catalog"
import CatalogMagento from "@vue-storefront/core/modules/catalog-magento"
import { Cart } from '@vue-storefront/core/modules/cart'
import { Checkout } from '@vue-storefront/core/modules/checkout'
import { Compare } from '@vue-storefront/core/modules/compare'
Expand Down Expand Up @@ -77,5 +78,6 @@ export const registerModules: VueStorefrontModule[] = [
AmpRenderer,
InstantCheckout,
Url,
CatalogMagento
// Example
]
Loading