@graphcommerce/algolia-products 9.1.0-canary.28 → 9.1.0-canary.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/Config.graphqls +3 -5
- package/README.md +1 -77
- package/algolia-spec.yaml +317 -210
- package/graphql/GetAlgoliaSettings.graphql +1 -1
- package/index.ts +1 -6
- package/mesh/algoliaFacetsToAggregations.ts +48 -84
- package/mesh/algoliaHitToMagentoProduct.ts +19 -14
- package/mesh/getAlgoliaSettings.ts +30 -15
- package/mesh/getAttributeList.ts +7 -4
- package/mesh/getSearchResults.ts +8 -1
- package/mesh/getSearchResultsInput.ts +7 -1
- package/mesh/getSearchSuggestions.ts +6 -3
- package/mesh/getSearchSuggestionsIndexName.ts +6 -0
- package/mesh/getSearchSuggestionsInput.ts +0 -5
- package/mesh/getSortedIndex.ts +54 -0
- package/mesh/getStoreConfig.ts +19 -16
- package/mesh/index.ts +16 -0
- package/mesh/productFilterInputToAlgoliafacetFiltersInput.ts +24 -4
- package/mesh/resolvers.ts +20 -34
- package/mesh/sortAggregations.ts +23 -0
- package/mesh/{sortOptions.ts → sortFieldOptions.ts} +7 -31
- package/package.json +9 -9
- package/schema/AlgoliaSchema.graphqls +4 -0
- package/scripts/generate-spec.mts +3 -3
package/mesh/resolvers.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AlgoliasearchResponse,
|
|
1
|
+
import type { AlgoliasearchResponse, Resolvers } from '@graphcommerce/graphql-mesh'
|
|
2
2
|
import { hasSelectionSetPath } from '@graphcommerce/graphql-mesh'
|
|
3
3
|
import type { GraphQLError, GraphQLResolveInfo } from 'graphql'
|
|
4
4
|
import { algoliaFacetsToAggregations, getCategoryList } from './algoliaFacetsToAggregations'
|
|
@@ -7,11 +7,13 @@ import { algoliaHitToMagentoProduct } from './algoliaHitToMagentoProduct'
|
|
|
7
7
|
import { getAlgoliaSettings } from './getAlgoliaSettings'
|
|
8
8
|
import { getAttributeList } from './getAttributeList'
|
|
9
9
|
import { getGroupId } from './getGroupId'
|
|
10
|
+
import { getIndexName } from './getIndexName'
|
|
10
11
|
import { getSearchResults } from './getSearchResults'
|
|
11
12
|
import { getSearchSuggestions } from './getSearchSuggestions'
|
|
12
13
|
import { isSuggestionsEnabled } from './getSearchSuggestionsInput'
|
|
13
14
|
import { getStoreConfig } from './getStoreConfig'
|
|
14
|
-
import {
|
|
15
|
+
import { sortAggregations } from './sortAggregations'
|
|
16
|
+
import { sortFieldsOptions } from './sortFieldOptions'
|
|
15
17
|
|
|
16
18
|
function isAlgoliaResponse<T extends object>(
|
|
17
19
|
root: T,
|
|
@@ -42,12 +44,15 @@ export const resolvers: Resolvers = {
|
|
|
42
44
|
aggregations: async (root, _args, context) => {
|
|
43
45
|
if (!isAlgoliaResponse(root)) return root.aggregations ?? null
|
|
44
46
|
|
|
45
|
-
return
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
return sortAggregations(
|
|
48
|
+
algoliaFacetsToAggregations(
|
|
49
|
+
root.algoliaSearchResults?.facets,
|
|
50
|
+
await getAttributeList(context),
|
|
51
|
+
await getStoreConfig(context),
|
|
52
|
+
await getCategoryList(context),
|
|
53
|
+
getGroupId(context),
|
|
54
|
+
),
|
|
55
|
+
root.algoliaSearchResults?.renderingContent,
|
|
51
56
|
)
|
|
52
57
|
},
|
|
53
58
|
|
|
@@ -55,12 +60,10 @@ export const resolvers: Resolvers = {
|
|
|
55
60
|
if (isAlgoliaResponse(root)) {
|
|
56
61
|
return {
|
|
57
62
|
default: 'relevance',
|
|
58
|
-
options:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
context,
|
|
63
|
-
),
|
|
63
|
+
options: sortFieldsOptions(
|
|
64
|
+
await getAlgoliaSettings(context),
|
|
65
|
+
await getAttributeList(context),
|
|
66
|
+
context,
|
|
64
67
|
),
|
|
65
68
|
}
|
|
66
69
|
}
|
|
@@ -69,13 +72,13 @@ export const resolvers: Resolvers = {
|
|
|
69
72
|
|
|
70
73
|
total_count: (root) => {
|
|
71
74
|
if (!isAlgoliaResponse(root)) return root.total_count ?? null
|
|
72
|
-
return root.algoliaSearchResults?.nbHits
|
|
75
|
+
return root.algoliaSearchResults?.nbHits ?? null
|
|
73
76
|
},
|
|
74
77
|
|
|
75
78
|
page_info: (root) => {
|
|
76
79
|
if (!isAlgoliaResponse(root)) return root.page_info ?? null
|
|
77
80
|
return {
|
|
78
|
-
current_page: root.algoliaSearchResults.page + 1,
|
|
81
|
+
current_page: (root.algoliaSearchResults.page ?? 0) + 1,
|
|
79
82
|
page_size: root.algoliaSearchResults.hitsPerPage,
|
|
80
83
|
total_pages: root.algoliaSearchResults.nbPages,
|
|
81
84
|
}
|
|
@@ -97,23 +100,6 @@ export const resolvers: Resolvers = {
|
|
|
97
100
|
return items
|
|
98
101
|
},
|
|
99
102
|
},
|
|
100
|
-
Customer: {
|
|
101
|
-
group_id: {
|
|
102
|
-
resolve: async (root, args, context, info) => {
|
|
103
|
-
const { headers } = context as MeshContext & {
|
|
104
|
-
headers?: Record<string, string | undefined>
|
|
105
|
-
}
|
|
106
|
-
if (!headers?.authorization) return 0
|
|
107
|
-
const customer = await context.m2rest.Query.m2rest_GetV1CustomersMe({
|
|
108
|
-
root,
|
|
109
|
-
context,
|
|
110
|
-
info,
|
|
111
|
-
autoSelectionSetWithDepth: 10,
|
|
112
|
-
})
|
|
113
|
-
return customer?.group_id ?? null
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
103
|
Query: {
|
|
118
104
|
products: async (root, args, context, info) => {
|
|
119
105
|
const isAgolia = (args.filter?.engine?.in ?? [args.filter?.engine?.eq])[0] === 'algolia'
|
|
@@ -127,7 +113,6 @@ export const resolvers: Resolvers = {
|
|
|
127
113
|
getSearchSuggestions(args.search, context)
|
|
128
114
|
|
|
129
115
|
const searchResults = hasSearchRequest(info) ? getSearchResults(args, context, info) : null
|
|
130
|
-
|
|
131
116
|
if (isGraphQLError(await searchResults))
|
|
132
117
|
return context.m2.Query.products({ root, args, context, info })
|
|
133
118
|
|
|
@@ -135,6 +120,7 @@ export const resolvers: Resolvers = {
|
|
|
135
120
|
algoliaSearchResults: await searchResults,
|
|
136
121
|
suggestions: (await searchSuggestsions) || null,
|
|
137
122
|
algolia_queryID: (await searchResults)?.queryID,
|
|
123
|
+
algolia_indexName: getIndexName(context),
|
|
138
124
|
}
|
|
139
125
|
},
|
|
140
126
|
},
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Aggregation, AlgoliasearchResponse } from '@graphcommerce/graphql-mesh'
|
|
2
|
+
|
|
3
|
+
/** Sort the aggregations based on the ordering given by Algolia. */
|
|
4
|
+
export function sortAggregations(
|
|
5
|
+
aggregations: Aggregation[],
|
|
6
|
+
renderingContent: AlgoliasearchResponse['renderingContent'],
|
|
7
|
+
) {
|
|
8
|
+
const order = renderingContent?.facetOrdering?.facets?.order
|
|
9
|
+
/** @todo: Algolia also supports sorting the attribute values. */
|
|
10
|
+
// const values = renderingContent?.facetOrdering?.values?.additionalProperties
|
|
11
|
+
|
|
12
|
+
if (!order) return aggregations
|
|
13
|
+
|
|
14
|
+
return aggregations
|
|
15
|
+
.sort(
|
|
16
|
+
(a, b) =>
|
|
17
|
+
(order.indexOf(a.attribute_code) ?? Infinity) -
|
|
18
|
+
(order.indexOf(b.attribute_code) ?? Infinity),
|
|
19
|
+
)
|
|
20
|
+
.filter((value) =>
|
|
21
|
+
order.find((sortedAttributeCode) => value.attribute_code === sortedAttributeCode),
|
|
22
|
+
)
|
|
23
|
+
}
|
|
@@ -1,21 +1,16 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AlgoliasettingsResponse,
|
|
3
|
-
MeshContext,
|
|
4
|
-
ProductAttributeSortInput,
|
|
5
|
-
SortEnum,
|
|
6
|
-
SortField,
|
|
7
|
-
} from '@graphcommerce/graphql-mesh'
|
|
1
|
+
import type { MeshContext, SortEnum, SortField } from '@graphcommerce/graphql-mesh'
|
|
8
2
|
import { nonNullable } from '@graphcommerce/magento-customer'
|
|
3
|
+
import type { GetAlgoliaSettingsReturn } from './getAlgoliaSettings'
|
|
9
4
|
import type { AttributeList } from './getAttributeList'
|
|
10
5
|
import { getIndexName } from './getIndexName'
|
|
11
6
|
|
|
12
7
|
export type SortingOptions = Record<string, SortField & { dirs: SortEnum[] }>
|
|
13
8
|
|
|
14
|
-
export
|
|
15
|
-
settings:
|
|
9
|
+
export function sortFieldsOptions(
|
|
10
|
+
settings: GetAlgoliaSettingsReturn,
|
|
16
11
|
attributeList: AttributeList,
|
|
17
12
|
context: MeshContext,
|
|
18
|
-
):
|
|
13
|
+
): SortField[] {
|
|
19
14
|
const sortRecord: SortingOptions = {
|
|
20
15
|
relevance: { label: 'Relevance', value: 'relevance', dirs: ['DESC'] },
|
|
21
16
|
}
|
|
@@ -48,29 +43,10 @@ export async function sortingOptions(
|
|
|
48
43
|
label: attributeList.find((attr) => attr.code === attributeCode)?.label,
|
|
49
44
|
dirs: [curr.dir],
|
|
50
45
|
}
|
|
51
|
-
} else {
|
|
46
|
+
} else if (!sortRecord[attributeCode].dirs.includes(curr.dir)) {
|
|
52
47
|
sortRecord[attributeCode].dirs.push(curr.dir)
|
|
53
48
|
}
|
|
54
49
|
}, {})
|
|
55
50
|
|
|
56
|
-
return sortRecord
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export async function getSortedIndex(
|
|
60
|
-
context: MeshContext,
|
|
61
|
-
settings: Promise<AlgoliasettingsResponse>,
|
|
62
|
-
sortInput: ProductAttributeSortInput | null = {},
|
|
63
|
-
): Promise<string> {
|
|
64
|
-
const baseIndex = getIndexName(context)
|
|
65
|
-
// const availableSorting = Object.values(sortOptions)
|
|
66
|
-
const [attr, dir] = Object.entries(sortInput ?? {}).filter(nonNullable)?.[0] ?? []
|
|
67
|
-
if (!attr || !dir) return baseIndex
|
|
68
|
-
|
|
69
|
-
const found = (await settings).replicas?.find(
|
|
70
|
-
(replica) =>
|
|
71
|
-
replica?.startsWith(`virtual(${baseIndex}_${attr}`) &&
|
|
72
|
-
replica?.endsWith(`${dir?.toLowerCase()})`),
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
return found ? found?.slice(8, -1) : baseIndex
|
|
51
|
+
return Object.values(sortRecord)
|
|
76
52
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@graphcommerce/algolia-products",
|
|
3
3
|
"homepage": "https://www.graphcommerce.org/",
|
|
4
4
|
"repository": "github:graphcommerce-org/graphcommerce",
|
|
5
|
-
"version": "9.1.0-canary.
|
|
5
|
+
"version": "9.1.0-canary.31",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"prettier": "@graphcommerce/prettier-config-pwa",
|
|
8
8
|
"eslintConfig": {
|
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
"generate": "tsx scripts/generate-spec.mts"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
18
|
-
"@graphcommerce/google-datalayer": "^9.1.0-canary.
|
|
19
|
-
"@graphcommerce/graphql": "^9.1.0-canary.
|
|
20
|
-
"@graphcommerce/graphql-mesh": "^9.1.0-canary.
|
|
21
|
-
"@graphcommerce/magento-customer": "^9.1.0-canary.
|
|
22
|
-
"@graphcommerce/magento-product": "^9.1.0-canary.
|
|
23
|
-
"@graphcommerce/magento-search": "^9.1.0-canary.
|
|
24
|
-
"@graphcommerce/next-config": "^9.1.0-canary.
|
|
25
|
-
"@graphcommerce/next-ui": "^9.1.0-canary.
|
|
18
|
+
"@graphcommerce/google-datalayer": "^9.1.0-canary.31",
|
|
19
|
+
"@graphcommerce/graphql": "^9.1.0-canary.31",
|
|
20
|
+
"@graphcommerce/graphql-mesh": "^9.1.0-canary.31",
|
|
21
|
+
"@graphcommerce/magento-customer": "^9.1.0-canary.31",
|
|
22
|
+
"@graphcommerce/magento-product": "^9.1.0-canary.31",
|
|
23
|
+
"@graphcommerce/magento-search": "^9.1.0-canary.31",
|
|
24
|
+
"@graphcommerce/next-config": "^9.1.0-canary.31",
|
|
25
|
+
"@graphcommerce/next-ui": "^9.1.0-canary.31",
|
|
26
26
|
"react": "^18.2.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
@@ -31,6 +31,9 @@ type AlgoliaProductHitAdditionalProperties {
|
|
|
31
31
|
rating_summary: String
|
|
32
32
|
thumbnail_url: String
|
|
33
33
|
image_url: String
|
|
34
|
+
|
|
35
|
+
description: String
|
|
36
|
+
short_description: String
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
type Algoliahit {
|
|
@@ -57,4 +60,5 @@ input CategoryFilterInput {
|
|
|
57
60
|
|
|
58
61
|
type Products {
|
|
59
62
|
algolia_queryID: String
|
|
63
|
+
algolia_indexName: String
|
|
60
64
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import yaml from 'js-yaml'
|
|
2
1
|
import { writeFile } from 'node:fs/promises'
|
|
2
|
+
import conf from '@graphcommerce/prettier-config-pwa'
|
|
3
|
+
import yaml from 'js-yaml'
|
|
3
4
|
import type { OpenAPIV3 } from 'openapi-types'
|
|
4
5
|
import prettier from 'prettier'
|
|
5
|
-
import conf from '@graphcommerce/prettier-config-pwa'
|
|
6
6
|
import { algoliaSchemaBaseFilter } from './base-schema-filter.mjs'
|
|
7
7
|
|
|
8
8
|
const response = await fetch(
|
|
@@ -33,7 +33,7 @@ const newSchema: OpenAPIV3.Document = {
|
|
|
33
33
|
const keys = ['post', 'get', 'put', 'delete', 'patch', 'options'] as const
|
|
34
34
|
|
|
35
35
|
keys.forEach((method) => {
|
|
36
|
-
if (!newValue[method]?.['x-acl']?.
|
|
36
|
+
if (!newValue[method]?.['x-acl']?.every((value: string) => acl.includes(value))) {
|
|
37
37
|
newValue[method] = undefined
|
|
38
38
|
}
|
|
39
39
|
})
|