Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/develop' into feature/3249-ref…
Browse files Browse the repository at this point in the history
…actoring-vuex-cart
  • Loading branch information
andrzejewsky committed Aug 13, 2019
2 parents cdb8de1 + bd2d02c commit 9bef021
Show file tree
Hide file tree
Showing 13 changed files with 273 additions and 87 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Refactored the vuex user module - @andrzejewsky (#3095)
- Brazilian Portuguese (pt_BR) translation improved - @pxfm (#3288)
- Moved store/lib to /lib - @pxfm (#3253)
- Improved some of the german translations in spelling and wording - @MariaKern (#3297)
- Corrected usage of "configurableChildrenStockPrefetchStatic" setting, refactored logic to tested helper - @philippsander (#859)
- Improved some of the german translations in spelling and wording - @MariaKern (#3297)

## [1.10.0] - 2019.08.10

Expand Down
7 changes: 2 additions & 5 deletions core/lib/multistore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,8 @@ export function storeCodeFromRoute (matchedRouteOrUrl: LocalizedRoute | RawLocat
return storeCode
}
}

return ''
} else {
return ''
}
}
return ''
}

export function removeStoreCodeFromRoute (matchedRouteOrUrl: LocalizedRoute | string): LocalizedRoute | string {
Expand Down
24 changes: 24 additions & 0 deletions core/modules/catalog-next/helpers/cacheProductsHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { products } from 'config'

export const prefetchStockItems = (cachedProductsResponse, cache = {}) => {
const skus = []
let prefetchIndex = 0
cachedProductsResponse.items.map(i => {
if (products.configurableChildrenStockPrefetchStatic &&
products.configurableChildrenStockPrefetchStaticPrefetchCount > 0) {
if (prefetchIndex > products.configurableChildrenStockPrefetchStaticPrefetchCount) return
}
skus.push(i.sku) // main product sku to be checked anyway
if (i.type_id === 'configurable' && i.configurable_children && i.configurable_children.length > 0) {
for (const confChild of i.configurable_children) {
const cachedItem = cache[confChild.id]
if (typeof cachedItem === 'undefined' || cachedItem === null) {
skus.push(confChild.sku)
}
}
prefetchIndex++
}
})

return skus
}
22 changes: 4 additions & 18 deletions core/modules/catalog-next/store/category/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { configureProductAsync } from '@vue-storefront/core/modules/catalog/help
import { DataResolver } from 'core/data-resolver/types/DataResolver';
import { Category } from '../../types/Category';
import { _prepareCategoryPathIds } from '../../helpers/categoryHelpers';
import { prefetchStockItems } from '../../helpers/cacheProductsHelper';
import chunk from 'lodash-es/chunk'

const actions: ActionTree<CategoryState, RootState> = {
Expand Down Expand Up @@ -75,24 +76,9 @@ const actions: ActionTree<CategoryState, RootState> = {
sort: searchQuery.sort,
updateState: false // not update the product listing - this request is only for caching
}, { root: true })
if (products.filterUnavailableVariants && products.configurableChildrenStockPrefetchStatic) { // prefetch the stock items
const skus = []
let prefetchIndex = 0
cachedProductsResponse.items.map(i => {
if (products.configurableChildrenStockPrefetchStaticPrefetchCount > 0) {
if (prefetchIndex > products.configurableChildrenStockPrefetchStaticPrefetchCount) return
}
skus.push(i.sku) // main product sku to be checked anyway
if (i.type_id === 'configurable' && i.configurable_children && i.configurable_children.length > 0) {
for (const confChild of i.configurable_children) {
const cachedItem = rootState.stock.cache[confChild.id]
if (typeof cachedItem === 'undefined' || cachedItem === null) {
skus.push(confChild.sku)
}
}
prefetchIndex++
}
})
if (products.filterUnavailableVariants) { // prefetch the stock items
const skus = prefetchStockItems(cachedProductsResponse, rootState.stock.cache)

for (const chunkItem of chunk(skus, 15)) {
dispatch('stock/list', { skus: chunkItem }, { root: true }) // store it in the cache
}
Expand Down
71 changes: 71 additions & 0 deletions core/modules/catalog-next/test/unit/prefetchStockItems.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {prefetchStockItems} from '../../helpers/cacheProductsHelper';
import config from 'config';

describe('prefetchStockItems method', () => {
describe('default configurableChildrenStockPrefetchStaticPrefetchCount', () => {
beforeEach(() => {
jest.clearAllMocks();
jest.mock('config', () => ({}));
})

it('returns an empty array when no items are provided', () => {
const cachedProductsResponse = {
items: []
}
const result = prefetchStockItems(cachedProductsResponse)
expect(result).toEqual([]);
})

it('returns the skus of the children of a configurable', () => {
const cachedProductsResponse = {
items: [
{sku: 'foo'},
{
sku: 'bar',
type_id: 'configurable',
configurable_children: [
{sku: 'bar.foo'},
{sku: 'bar.bar'},
{sku: 'bar.baz'}
]
},
{sku: 'baz'}
]
}
const result = prefetchStockItems(cachedProductsResponse)
expect(result).toEqual(['foo', 'bar', 'bar.foo', 'bar.bar', 'bar.baz', 'baz']);
})

it('returns the same skus of the provided simple products', () => {
const cachedProductsResponse = {
items: [
{sku: 'foo'},
{sku: 'bar'},
{sku: 'baz'}
]
}
const result = prefetchStockItems(cachedProductsResponse)
expect(result).toEqual(['foo', 'bar', 'baz']);
})

it('ignores the pre-cached skus of children of a configurable', () => {
const cachedProductsResponse = {
items: [
{sku: 'foo'},
{
sku: 'bar',
type_id: 'configurable',
configurable_children: [
{sku: 'bar.foo', id: 1337},
{sku: 'bar.bar'},
{sku: 'bar.baz', id: 4711}
]
},
{sku: 'baz'}
]
}
const result = prefetchStockItems(cachedProductsResponse, {1337: {}, 4711: {}})
expect(result).toEqual(['foo', 'bar', 'bar.bar', 'baz']);
})
})
})
38 changes: 38 additions & 0 deletions core/modules/catalog/helpers/areAttributesAlreadyLoaded.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import config from 'config'

const areAttributesAlreadyLoaded = ({
filterValues,
filterField,
blacklist,
idsList,
codesList
}: {
filterValues: string[],
filterField: string,
blacklist: string[],
idsList: any,
codesList: any
}): boolean => {
return filterValues.filter(fv => {
if (config.entities.product.standardSystemFields.indexOf(fv) >= 0) {
return false
}

if (fv.indexOf('.') >= 0) {
return false
}

if (blacklist !== null && blacklist.includes(fv)) {
return false
}

if (filterField === 'attribute_id') {
return (typeof idsList[fv] === 'undefined' || idsList[fv] === null)
}
if (filterField === 'attribute_code') {
return (typeof codesList[fv] === 'undefined' || codesList[fv] === null)
}
}).length === 0
}

export default areAttributesAlreadyLoaded
29 changes: 29 additions & 0 deletions core/modules/catalog/helpers/createAttributesListQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'

const createAttributesListQuery = ({
filterValues,
filterField,
onlyDefinedByUser,
onlyVisible
}: {
filterValues: string[],
filterField: string,
onlyDefinedByUser: boolean,
onlyVisible: boolean
}): SearchQuery => {
let searchQuery = new SearchQuery()

if (filterValues) {
searchQuery = searchQuery.applyFilter({key: filterField, value: {'in': filterValues}})
}
if (onlyDefinedByUser) {
searchQuery = searchQuery.applyFilter({key: 'is_user_defined', value: {'in': [true]}})
}
if (onlyVisible) {
searchQuery = searchQuery.applyFilter({key: 'is_visible', value: {'in': [true]}})
}

return searchQuery
}

export default createAttributesListQuery
26 changes: 26 additions & 0 deletions core/modules/catalog/helpers/reduceAttributesLists.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Attribute from '@vue-storefront/core/modules/catalog/types/Attribute'

const reduceAttributes = (prev, curr) => {
if (curr) {
prev.attrHashByCode[curr.attribute_code] = curr
prev.attrHashById[curr.attribute_id] = curr
}

return prev
}

const reduceAttributesLists = ({
codesList,
idsList,
attributes
}: {
codesList: any,
idsList: any,
attributes: Attribute[]
}) => {
return attributes.reduce(
reduceAttributes, { attrHashByCode: codesList, attrHashById: idsList }
)
}

export default reduceAttributesLists
103 changes: 66 additions & 37 deletions core/modules/catalog/store/attribute/actions.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,86 @@
import * as types from './mutation-types'
import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
import { quickSearchByQuery } from '@vue-storefront/core/lib/search'
import { StorageManager } from '@vue-storefront/core/lib/storage-manager'
import AttributeState from '../../types/AttributeState'
import RootState from '@vue-storefront/core/types/RootState'
import { ActionTree } from 'vuex'
import config from 'config'
import { Logger } from '@vue-storefront/core/lib/logger'
import { entityKeyName } from '@vue-storefront/core/lib/store/entities'
import { prefetchCachedAttributes } from '../../helpers/prefetchCachedAttributes'
import areAttributesAlreadyLoaded from './../../helpers/areAttributesAlreadyLoaded'
import createAttributesListQuery from './../../helpers/createAttributesListQuery'
import reduceAttributesLists from './../../helpers/reduceAttributesLists'

const actions: ActionTree<AttributeState, RootState> = {
async updateAttributes ({ commit, getters }, { attributes }) {
const idsList = getters.attributeListById
const codesList = getters.attributeListByCode

for (let attr of attributes) {
if (attr && !config.attributes.disablePersistentAttributesCache) {
const attrCollection = StorageManager.get('attributes')

try {
await attrCollection.setItem(entityKeyName('attribute_code', attr.attribute_code.toLowerCase()), attr)
await attrCollection.setItem(entityKeyName('attribute_id', attr.attribute_id.toString()), attr)
} catch (e) {
Logger.error(e, 'mutations')()
}
}
}

commit(types.ATTRIBUTE_UPD_ATTRIBUTES, reduceAttributesLists({ codesList, idsList, attributes }))
},
async loadCachedAttributes ({ dispatch }, { filterField, filterValues }) {
if (!filterValues) {
return
}

const attributes = await prefetchCachedAttributes(filterField, filterValues)

if (attributes) {
await dispatch('updateAttributes', { attributes })
}
},
updateBlacklist ({ commit, getters }, { filterValues, filterField, attributes }) {
if (attributes && filterValues.length > 0) {
const foundValues = attributes.map(attr => attr[filterField])
const toBlackList = filterValues.filter(ofv => !foundValues.includes(ofv) && !getters.getBlacklist.includes(ofv))
commit(types.ATTRIBUTE_UPD_BLACKLIST, toBlackList)
}
},
/**
* Load attributes with specific codes
* @param {Object} context
* @param {Array} attrCodes attribute codes to load
*/
async list (context, { filterValues = null, filterField = 'attribute_code', only_user_defined = false, only_visible = false, size = 150, start = 0, includeFields = config.entities.optimize ? config.entities.attribute.includeFields : null }) {
const commit = context.commit
let searchQuery = new SearchQuery()
const orgFilterValues = filterValues ? [...filterValues] : []
if (filterValues) {
const cachedAttributes = await prefetchCachedAttributes(filterField, filterValues)
if (cachedAttributes) context.commit(types.ATTRIBUTE_UPD_ATTRIBUTES, { items: cachedAttributes })
filterValues = filterValues.filter(fv => { // check the already loaded
if (config.entities.product.standardSystemFields.indexOf(fv) >= 0) return false // skip standard system fields
if (fv.indexOf('.') >= 0) return false // skip multipart field names
if (context.state.blacklist !== null && context.state.blacklist.includes(fv)) return false // return that this attribute is not on our blacklist
if (filterField === 'attribute_id') return (typeof context.state.list_by_id[fv] === 'undefined' || context.state.list_by_id[fv] === null)
if (filterField === 'attribute_code') return (typeof context.state.list_by_code[fv] === 'undefined' || context.state.list_by_code[fv] === null)
})
if (!filterValues || filterValues.length === 0) {
Logger.info('Skipping attribute load - attributes already loaded', 'attr', { orgFilterValues, filterField })()
return Promise.resolve({
items: Object.values(context.state.list_by_code)
})
}
searchQuery = searchQuery.applyFilter({key: filterField, value: {'in': filterValues}})
}
if (only_user_defined) {
searchQuery = searchQuery.applyFilter({key: 'is_user_defined', value: {'in': [true]}})
}
if (only_visible) {
searchQuery = searchQuery.applyFilter({key: 'is_visible', value: {'in': [true]}})
async list ({ getters, dispatch }, { filterValues = null, filterField = 'attribute_code', only_user_defined = false, only_visible = false, size = 150, start = 0, includeFields = config.entities.optimize ? config.entities.attribute.includeFields : null }) {
const blacklist = getters.getBlacklist
const idsList = getters.attributeListById
const codesList = getters.attributeListByCode
const orgFilterValues = filterValues || []

await dispatch('loadCachedAttributes', { filterField, filterValues })

if (areAttributesAlreadyLoaded({ filterValues, filterField, blacklist, idsList, codesList })) {
Logger.info('Skipping attribute load - attributes already loaded', 'attr', { orgFilterValues, filterField })()
return { items: Object.values(codesList) }
}
return quickSearchByQuery({ entityType: 'attribute', query: searchQuery, includeFields: includeFields }).then((resp) => {
if (resp && Array.isArray(orgFilterValues) && orgFilterValues.length > 0) {
const foundValues = resp.items.map(attr => attr[filterField])
const toBlackList = filterValues.filter(ofv => !foundValues.includes(ofv))
toBlackList.map(tbl => {
if (!context.state.blacklist.includes(tbl)) context.state.blacklist.push(tbl)
}) // extend the black list of not-found atrbiutes
}
commit(types.ATTRIBUTE_UPD_ATTRIBUTES, resp)

const query = createAttributesListQuery({
filterValues,
filterField,
onlyDefinedByUser: only_user_defined,
onlyVisible: only_visible
})
const resp = await quickSearchByQuery({ entityType: 'attribute', query, includeFields })
const attributes = resp && orgFilterValues.length > 0 ? resp.items : null

dispatch('updateBlacklist', { filterValues, filterField, attributes })
await dispatch('updateAttributes', { attributes })

return resp
}
}

Expand Down
3 changes: 2 additions & 1 deletion core/modules/catalog/store/attribute/getters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import RootState from '@vue-storefront/core/types/RootState'

const getters: GetterTree<AttributeState, RootState> = {
attributeListByCode: (state) => state.list_by_code,
attributeListById: (state) => state.list_by_id
attributeListById: (state) => state.list_by_id,
getBlacklist: (state) => state.blacklist
}

export default getters
1 change: 1 addition & 0 deletions core/modules/catalog/store/attribute/mutation-types.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export const SN_ATTRIBUTE = 'attribute'
export const ATTRIBUTE_UPD_ATTRIBUTES = SN_ATTRIBUTE + '/UPD_ATTRIBUTES'
export const ATTRIBUTE_UPD_BLACKLIST = SN_ATTRIBUTE + '/UPD_BLACKLIST_ATTRIBUTES'
Loading

0 comments on commit 9bef021

Please sign in to comment.