Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Product Page Schema implementation as JSON-LD - @Michal-Dziedzinski (#3704)
- Add `/cache-version.json` route to get current cache version
- Built-in module for detecting device type based on UserAgent with SSR support - @Fifciu
- Update to `storefront-query-builder` version `1.0.0` - @cewald (#4234)
- Move generating files from webpack config to script @gibkigonzo (#4236)

### Fixed
Expand Down
5 changes: 5 additions & 0 deletions core/lib/search/adapter/api-search-query/searchAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ export class SearchAdapter {
if (Request.hasOwnProperty('groupToken') && Request.groupToken !== null) {
rawQueryObject['groupToken'] = Request.groupToken
}
if (Request.sort) {
const [ field, options ] = Request.sort.split(':')
rawQueryObject.applySort({ field, options })
delete Request.sort
}
const storeView = (Request.store === null) ? currentStoreView() : await prepareStoreView(Request.store)
Request.index = storeView.elasticsearch.index

Expand Down
2 changes: 1 addition & 1 deletion core/modules/cart/test/unit/store/productActions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('Cart productActions', () => {

(contextMock.dispatch as jest.Mock).mockImplementationOnce(() => ({ items: [serverItem] }));
const result = await (cartActions as any).findProductOption(contextMock, { serverItem });
expect(contextMock.dispatch).toBeCalledWith('product/list', { query: { _appliedFilters: [{ attribute: 'configurable_children.sku', options: Object, scope: 'default', value: { eq: 1 } }], _availableFilters: [], _searchText: '' }, size: 1, start: 0, updateState: false }, { root: true })
expect(contextMock.dispatch).toBeCalledWith('product/list', { query: { _appliedFilters: [{ attribute: 'configurable_children.sku', options: Object, scope: 'default', value: { eq: 1 } }], _availableFilters: [], _appliedSort: [], _searchText: '' }, size: 1, start: 0, updateState: false }, { root: true })
expect(result).toEqual({ childSku: 1, sku: 1 })
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ describe('createHierarchyLoadQuery', () => {
const filter = { id: null }
let hierarchyLoadQuery = createHierarchyLoadQuery(filter)

expect(hierarchyLoadQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _searchText: '' })
expect(hierarchyLoadQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _appliedSort: [], _searchText: '' })
})
})
2 changes: 1 addition & 1 deletion core/modules/cms/test/unit/createLoadingBlockQuery.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ describe('createLoadingBlockQuery', () => {
const filter = { filterField: 'test', filterValues: undefined }
let loadingBlockQuery = createLoadingBlockQuery(filter)

expect(loadingBlockQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _searchText: '' })
expect(loadingBlockQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _appliedSort: [], _searchText: '' })
})
})
2 changes: 1 addition & 1 deletion core/modules/cms/test/unit/createPageLoadingQuery.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ describe('createPageLoadingQuery', () => {
const filter = { filterField: 'test', filterValues: undefined }
let pageLoadingQuery = createPageLoadingQuery(filter)

expect(pageLoadingQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _searchText: '' })
expect(pageLoadingQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _appliedSort: [], _searchText: '' })
})
})
2 changes: 1 addition & 1 deletion core/modules/cms/test/unit/createSingleBlockQuery.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ describe('createSingleBlockLoadQuery should', () => {
const argsMock = { key: 'test', value: undefined }
let mockSingleBlockQuery = createSingleBlockQuery(argsMock)

expect(mockSingleBlockQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _searchText: '' })
expect(mockSingleBlockQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _appliedSort: [], _searchText: '' })
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ describe('createSinglePageLoadQuery should', () => {
const filter = { key: 'test', value: undefined }
let singlePageMockQuery = createSinglePageLoadQuery(filter)

expect(singlePageMockQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _searchText: '' })
expect(singlePageMockQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _appliedSort: [], _searchText: '' })
})
})
100 changes: 94 additions & 6 deletions docs/guide/cookbook/module.md
Original file line number Diff line number Diff line change
Expand Up @@ -678,10 +678,98 @@ It's hands down no-brainer to bootstrap a module _manually_ because the skeleton
### 2. Recipe
### 3. Peep into the kitchen (what happens internally)
### 4. Chef's secret (protip)
<br />
<br />

## 6. Anti-patterns & Common pitfalls
## 6. Extend Elasticsearch request body using `storefront-query-builder`

If you're using the new [`storefront-query-builder`](https://github.com/DivanteLtd/storefront-query-builder) and the `api-search-query` search-adapter ([introduced with v1.1.12](/guide/upgrade-notes/#_1-11-1-12)) it is now possible to extend it by new filters, or even overwrite a existing filter, to customize your Elasticsearch request-bodies.

So, this way you can add custom Elasticsearch queries to the query-chain and still use the notation of `SearchQuery` in the Vue Storefront.

> **Note:** This will only work from `storefront-query-builder` version `1.0.0` and `vue-storefront` version `1.12.2`.

### Usecases

One usecases where this feature would come in handy is for example if you like to add complex queries on multiple points in your source code. Using the following technique you can just add a custom filter to your `SearchQuery` in a single line inside your VSF source-code using the `query.applyFilter(...)` method and then add the complex logic into your custom-filter inside the API.

### Registering a new filter

The `vue-storefront-api` will only try to load filters that are registered in the configs. The extension/module, that contains the filter, must be enabled and the new filter module-classes needs to be registered in its extension config inside the `catalogFilter` array. The filter files must be located inside `filter/catalog/` of your module folder.

For example: If you have a module called `extend-catalog` with a filter called `StockFilter`, the file path to filter would be `src/api/extensions/extend-catalog/filter/catalog/StockFilter.ts` and the config would look like:
```
{
"registeredExtensions": [ "extend-catalog" ],
"extensions": {
"extend-catalog": {
"catalogFilter": [ "StockFilter" ]
}
}
}
```

### Filter module-class properties

The filter can contain four different properties. Followed a short explaination, what they are doing.

* `check` – This method checks the condition that be must matched to execute the filter. The first valid filter is executed – all afterwards are ignored.
* `priority` – This is the priority in which the filters are going to be called. The sort is lower to higher.
* `mutator` – The mutator method is in charge of prehandling the filter value, to e.g. set defaults or check and change the type.
* `filter` – This method contains the query logic we wan't to add and mutates the `bodybuilder` query-chain.

### Example

Lets assume we like to add a possibility to add a default set of product-attribute filters we can apply to each `SearchQuery` without repeating ourselfs in source-code. So, for example, it should filter for two `color`'s and a specific `cut` to supply a filter for spring-coloured short's we implement at several places in our VSF.

#### Changes in `vue-storefront` repository

The query in the VSF code would look like this (that's it on the VSF side):
```js
import { SearchQuery } from 'storefront-query-builder'
import { quickSearchByQuery } from '@vue-storefront/core/lib/search'

//...

const query = new SearchQuery()
query.applyFilter({ key: 'spring-shorts', value: 'male', scope: 'default' })
const products = await dispatch('product/list', { query, size: 5 })
```

#### Changes in `vue-storefront-api` repository

In the `vue-storefront-api` we are going to add the real filter/query magic.
There is already an example module called `example-custom-filter` which we are going to use for our filter.

As you look inside its module folder `src/api/extensions/example-custom-filter/`, you will find a child folder `filter/catalog/` with all existing custom filters for this module. Inside this folder we are going to duplicate the existing `SampleFilter.ts` into another one called `SpringShorts.ts` – this is our new custom filter module-class.

This file needs to be registered in the config JSON to let the API know that there is a new custom filter inside our extension.
Therefore you open your `default.json` or specific config JSON file and add our new filename `SpringShorts` to the config node `extensions.example-custom-filter.catalogFilter` array.

Our `SpringShorts.ts` contains an object that contains [four properties](#filter-module-class-properties): `priority`, `check`, `filter`, `mutator`. We don't need a `mutator` nor `priority`, so we can remove these lines. `check` and `filter` needs to be changed to fulfill our needs. So, this is how our filter finally looks like:

```js
import { FilterInterface } from 'storefront-query-builder'

const filter: FilterInterface = {
check: ({ attribute }) => attribute === 'spring-shorts',
filter ({ value, attribute, operator, queryChain }) {
return queryChain
.filter('terms', 'pants', [ 'shorts' ])
.filter('terms', 'cut', [ 1, 2 ])
.filter('terms', 'color', [ 3, 4 ])
.filter('terms', 'gender', [ value ])
}
}

export default filter
```

Inside `check` we tell the filter to just be applied if the attribute is named exactly `spring-shorts`.

Inside `filter` we extend the Elasticsearch query-chain by our desired filters, using the `bodybuilder` library syntax.

That's it, now we are able to filter by a complex query in only one line inside VSF.

## 7. Anti-patterns & Common pitfalls

### 1. Preparation
### 2. Recipe
Expand All @@ -699,7 +787,7 @@ _[INSERT VIDEO HERE]_
<br />
<br />

## 7. Building a module from A to Z in an iteration
## 8. Building a module from A to Z in an iteration


### 1. Preparation
Expand All @@ -709,7 +797,7 @@ _[INSERT VIDEO HERE]_
<br />
<br />

## 8. Deprecated legacy of Modules
## 9. Deprecated legacy of Modules
In this recipe, we will take a review of how to deal with modules in an old fashioned way , just in case you really need it.

### 1. Preparation
Expand All @@ -720,7 +808,7 @@ In this recipe, we will take a review of how to deal with modules in an old fash
<br />


## 9. Converting old modules to new modules
## 10. Converting old modules to new modules
There are useful modules out there already developed in the old way.

### 1. Preparation
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
"redis-tag-cache": "^1.2.1",
"reflect-metadata": "^0.1.12",
"register-service-worker": "^1.5.2",
"storefront-query-builder": "^0.0.9",
"storefront-query-builder": "^1.0.0",
"ts-node": "^8.6.2",
"vue": "^2.6.11",
"vue-analytics": "^5.16.1",
Expand Down
59 changes: 54 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2321,6 +2321,16 @@
"@typescript-eslint/typescript-estree" "1.13.0"
eslint-scope "^4.0.0"

"@typescript-eslint/experimental-utils@2.27.0":
version "2.27.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.27.0.tgz#801a952c10b58e486c9a0b36cf21e2aab1e9e01a"
integrity sha512-vOsYzjwJlY6E0NJRXPTeCGqjv5OHgRU1kzxHKWJVPjDYGbPgLudBXjIlc+OD1hDBZ4l1DLbOc5VjofKahsu9Jw==
dependencies:
"@types/json-schema" "^7.0.3"
"@typescript-eslint/typescript-estree" "2.27.0"
eslint-scope "^5.0.0"
eslint-utils "^2.0.0"

"@typescript-eslint/parser@^1.7.1-alpha.17":
version "1.13.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-1.13.0.tgz#61ac7811ea52791c47dc9fd4dd4a184fae9ac355"
Expand All @@ -2331,6 +2341,16 @@
"@typescript-eslint/typescript-estree" "1.13.0"
eslint-visitor-keys "^1.0.0"

"@typescript-eslint/parser@^2.26.0":
version "2.27.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.27.0.tgz#d91664335b2c46584294e42eb4ff35838c427287"
integrity sha512-HFUXZY+EdwrJXZo31DW4IS1ujQW3krzlRjBrFRrJcMDh0zCu107/nRfhk/uBasO8m0NVDbBF5WZKcIUMRO7vPg==
dependencies:
"@types/eslint-visitor-keys" "^1.0.0"
"@typescript-eslint/experimental-utils" "2.27.0"
"@typescript-eslint/typescript-estree" "2.27.0"
eslint-visitor-keys "^1.1.0"

"@typescript-eslint/typescript-estree@1.13.0":
version "1.13.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz#8140f17d0f60c03619798f1d628b8434913dc32e"
Expand All @@ -2339,6 +2359,19 @@
lodash.unescape "4.0.1"
semver "5.5.0"

"@typescript-eslint/typescript-estree@2.27.0":
version "2.27.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.27.0.tgz#a288e54605412da8b81f1660b56c8b2e42966ce8"
integrity sha512-t2miCCJIb/FU8yArjAvxllxbTiyNqaXJag7UOpB5DVoM3+xnjeOngtqlJkLRnMtzaRcJhe3CIR9RmL40omubhg==
dependencies:
debug "^4.1.1"
eslint-visitor-keys "^1.1.0"
glob "^7.1.6"
is-glob "^4.0.1"
lodash "^4.17.15"
semver "^6.3.0"
tsutils "^3.17.1"

"@vue/babel-helper-vue-jsx-merge-props@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.0.0.tgz#048fe579958da408fb7a8b2a3ec050b50a661040"
Expand Down Expand Up @@ -6751,13 +6784,28 @@ eslint-scope@^4.0.0, eslint-scope@^4.0.3:
esrecurse "^4.1.0"
estraverse "^4.1.1"

eslint-scope@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9"
integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==
dependencies:
esrecurse "^4.1.0"
estraverse "^4.1.1"

eslint-utils@^1.3.1:
version "1.4.3"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f"
integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==
dependencies:
eslint-visitor-keys "^1.1.0"

eslint-utils@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.0.0.tgz#7be1cc70f27a72a76cd14aa698bcabed6890e1cd"
integrity sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==
dependencies:
eslint-visitor-keys "^1.1.0"

eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2"
Expand Down Expand Up @@ -14607,11 +14655,12 @@ stealthy-require@^1.1.1:
resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=

storefront-query-builder@^0.0.9:
version "0.0.9"
resolved "https://registry.yarnpkg.com/storefront-query-builder/-/storefront-query-builder-0.0.9.tgz#e2733851d08dd98566d4f81281326658c74d660a"
integrity sha512-c6zIRyJwFQJtlLG1R+qlrHHDea1Z4O/EJbTvjbGRHIrsMKav2XfX5GzEai7SWU4+GomySU9eIR/M2YAJhVo5rw==
storefront-query-builder@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/storefront-query-builder/-/storefront-query-builder-1.0.0.tgz#8bab0b6bd61a574dfa913cad5c2fdd17858f8b07"
integrity sha512-j0JhHOYhcfDcsBUMYWVJ0UApQDOem5zo20XikxJcxfFEcM5Et1Z16674wx3++KrWCbU5raEMgHvqEPxkDFajdw==
dependencies:
"@typescript-eslint/parser" "^2.26.0"
clone-deep "^4.0.1"

stream-browserify@^2.0.1:
Expand Down Expand Up @@ -15443,7 +15492,7 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==

tsutils@^3.7.0:
tsutils@^3.17.1, tsutils@^3.7.0:
version "3.17.1"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==
Expand Down