Skip to content

Commit

Permalink
refactor(client/recommend): small refactor and cleanup to types
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisFrazier77 committed Sep 5, 2024
1 parent f5f8084 commit f285488
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 162 deletions.
79 changes: 39 additions & 40 deletions docs/INTEGRATION_RECOMMENDATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ It is recommended to utilize the [`RecommendationInstantiator`](https://github.c
profile: 'recently-viewed',
target: '.above-content',
options: {
limit: 5,
limit: 5
}
},
}
];
</script>
```
Expand All @@ -31,7 +31,7 @@ Context variables may be applied to individual recommendation profiles similar t
| products | array of SKU strings | product detail page | SKU value(s) to identify the current product(s) being viewed |
| cart | array (or function that returns an array) of current cart skus | all | optional method of setting cart contents |
| shopper.id | logged in user unique identifier | all | required for personalization functionallity if not provided to the bundle (global) context |
| profiles | array of profile objects | all | required profile specific variables |


### Profile Specific Variables
| Option | Value | Page | Description |
Expand All @@ -42,12 +42,11 @@ Context variables may be applied to individual recommendation profiles similar t
| options.categories | array of category path strings | all | optional category identifiers used in category trending recommendation profiles |
| options.brands | array of brand strings | all | optional brand identifiers used in brand trending recommendation profiles |
| options.branch | template branch overwrite | all | optional branch overwrite for recommendations template (advanced usage) |
| options.dedupe | boolean (default: `true`) | all | dedupe recs from other profiles in the batch |
| options.dedupe | boolean (default: `true`) | all | dedupe products across all profiles in the batch |
| options.searchTerm | string | all | query to search |
| options.filters | array of filters | all | optional recommendation filters |
| options.realtime | boolean | all | optional update recommendations if cart contents change (requires [cart attribute tracking](https://github.com/searchspring/snap/blob/main/docs/INTEGRATION_TRACKING.md)) |
| options.blockedItems | array of strings | all | SKU values to identify which products to exclude from the response |
| options.batched | boolean (default: `true`)| all | only applies to recommendation context, optional disable profile from being batched in a single request, can also be set globally [via config](https://github.com/searchspring/snap/tree/main/packages/snap-controller/src/Recommendation) |
| options.order | number | all | optional order number for recommendation params to be added to the batched request. Profiles that do not specify an order will be placed at the end, in the occurrence they appear in the DOM.
| options.limit | number (default: 20, max: 20) | all | optional maximum number of results to display, can also be set globally [via config globals](https://github.com/searchspring/snap/tree/main/packages/snap-controller/src/Recommendation) |

Expand All @@ -59,48 +58,48 @@ In most cases batching everything is the best practice, however for profiles lik
The example below shows how to manually specify the order and batching of specific profiles.

```html
<script type="searchspring/recommendations" profile="customers-also-bought">
globals: {
<script type="searchspring/recommendations">
globals = {
products: ['product123'];
}
profiles: [
};
profiles = [
{
profile: 'customers-also-bought',
target: '.crosssell',
options: {
limit: 5,
limit: 5
}
},
{
profile: 'customers-also-viewed',
target: '.similar',
target: '.similar'
},
//same batch, but dedupe false
{
profile: 'bundle',
target: '.similar',
options: {
dedupe: false,
dedupe: false
}
},
]
}
];
</script>

<!-- seperate batch -->
<script type="searchspring/recommendations" profile="quick-cart">
globals: {
<script type="searchspring/recommendations">
globals = {
products: ['product123']
}
profiles: [
};
profiles = [
{
profile: 'quick-cart',
target: '.crosssell',
options: {
limit: 5,
limit: 5
}
},
]
}
];
</script>
```

Expand All @@ -112,49 +111,49 @@ A typical "similar" profile that would display products similar to the product p

```html
<script type="searchspring/recommendations">
globals: {
globals = {
products: ['product123']
}
profiles: [
};
profiles = [
{
profile: 'customers-also-viewed',
target: '.similar'
},
]
}
];
</script>
```

If tracking scripts are not in place, "crosssell" profiles may require the cart contents to be provided.

```html
<script type="searchspring/recommendations">
globals: {
globals = {
cart: ['product123']
}
profiles: [
};
profiles = [
{
profile: 'customers-also-bought',
target: '.crosssell'
},
]
}
];
</script>
```

If the shopper identifier is not beeing captured by the `bundle.js` context, it must be provided for proper personalization.

```html
<script type="searchspring/recommendations">
globals: {
globals = {
shopper: {
id: '[email protected]'
};
}
profiles: [
}
};
profiles = [
{
profile: 'view-cart',
target: '.cart'
},
]
}
];
</script>
```

Expand All @@ -163,7 +162,7 @@ The example shown below will filter the recommendations for products matching co

```html
<script type="searchspring/recommendations">
profiles: [
profiles = [
{
profile: 'customers-also-bought',
target: '.crosssell',
Expand All @@ -186,7 +185,7 @@ The example shown below will filter the recommendations for products matching co
}
]
}
},
]
}
];
</script>
```
3 changes: 1 addition & 2 deletions packages/snap-client/src/Client/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export class Client {
return this.requesters.suggest.getTrending(params as TrendingRequestModel);
}

async recommend(params: RecommendRequestModel, groupId?: number): Promise<RecommendCombinedResponseModel> {
async recommend(params: RecommendRequestModel): Promise<RecommendCombinedResponseModel> {
const { tag, ...otherParams } = params;
if (!tag) {
throw 'tag parameter is required';
Expand All @@ -190,7 +190,6 @@ export class Client {
const recommendParams = {
tag: tag,
...otherParams,
groupId: groupId || 1,
siteId: params.siteId || this.globals.siteId,
};

Expand Down
2 changes: 1 addition & 1 deletion packages/snap-client/src/Client/apis/Recommend.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ApiConfiguration } from './Abstract';
import { RecommendAPI } from './Recommend';
import { MockData } from '@searchspring/snap-shared';

import type { RecommendRequestModel, TransformedRequestModel, PostRecommendAPISpec } from '../../types';
import type { PostRecommendAPISpec } from '../../types';

const mockData = new MockData();

Expand Down
64 changes: 31 additions & 33 deletions packages/snap-client/src/Client/apis/Recommend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,7 @@ import { API, ApiConfiguration } from './Abstract';
import { HTTPHeaders, PostRecommendRequestFiltersModel } from '../../types';
import { AppMode } from '@searchspring/snap-toolbox';
import { transformRecommendationFiltersPost } from '../transforms';
import {
ProfileRequestModel,
ProfileResponseModel,
RecommendResponseModel,
RecommendRequestModel,
TransformedRequestModel,
PostRecommendAPISpec,
} from '../../types';
import { ProfileRequestModel, ProfileResponseModel, RecommendResponseModel, RecommendRequestModel, PostRecommendAPISpec } from '../../types';

class Deferred {
promise: Promise<any>;
Expand All @@ -24,7 +17,7 @@ class Deferred {
}
}

type CombinedIncoming = Partial<RecommendRequestModel> & Partial<TransformedRequestModel>;
type CombinedIncoming = Partial<RecommendRequestModel>;
type BatchEntry = {
request: CombinedIncoming;
deferred: Deferred;
Expand Down Expand Up @@ -62,26 +55,24 @@ export class RecommendAPI extends API {
}

async batchRecommendations(parameters: RecommendRequestModel): Promise<RecommendResponseModel> {
const groupId = parameters.groupId || 1;

// set up batch key and deferred promises
const key = parameters.batched ? `${parameters.siteId}:${parameters.groupId}` : `${Math.random()}:${parameters.groupId}`;
const key = parameters.batched ? `${parameters.siteId}:${groupId}` : `${Math.random()}:${groupId}`;
const batch = (this.batches[key] = this.batches[key] || { timeout: null, request: { profiles: [] }, entries: [] });
const deferred = new Deferred();

const { tag, limits, limit, searchTerm, filters, profileFilters, dedupe, categories, brands } = parameters;

const newParams = {
...parameters,
profiles: [
{
tag: tag,
categories,
brands,
limit: limit ? limit : limits ? (typeof limits == 'number' ? limits : limits[0]) : undefined,
searchTerm,
filters: profileFilters,
dedupe,
},
],
tag: tag,
categories,
brands,
limit: limit ? limit : limits ? (typeof limits == 'number' ? limits : limits[0]) : undefined,
searchTerm,
profileFilters,
dedupe,
filters,
};

Expand All @@ -102,23 +93,30 @@ export class RecommendAPI extends API {
// now that the requests are in proper order, map through them
// and build out the batches
batch.entries.map((entry) => {
const { profiles } = entry.request;
const { tag, categories, brands, searchTerm, profileFilters, dedupe } = entry.request;
let limit = entry.request.limit;

if (profiles) {
profiles.forEach((profile) => {
if (!profile.limit) {
profile.limit = 20;
}
if (!limit) {
limit = 20;
}

let transformedFilters;
if (profile.filters) {
transformedFilters = transformRecommendationFiltersPost(profile.filters) as PostRecommendRequestFiltersModel[];
}
const profile: any = {
tag,
categories,
brands,
limit,
searchTerm,
filters: profileFilters,
dedupe,
};

batch.request.profiles?.push({ ...profile, filters: transformedFilters });
});
let transformedFilters;
if (profile.filters) {
transformedFilters = transformRecommendationFiltersPost(profile.filters) as PostRecommendRequestFiltersModel[];
}

batch.request.profiles?.push({ ...profile, filters: transformedFilters });

batch.request = {
...batch.request,
siteId: parameters.siteId,
Expand Down
15 changes: 1 addition & 14 deletions packages/snap-client/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export type ClientConfig = {
search?: RequesterConfig<SearchRequestModel>;
autocomplete?: RequesterConfig<AutocompleteRequestModel> & { requesters?: HybridRequesterConfig };
finder?: RequesterConfig<SearchRequestModel>;
recommend?: RequesterConfig<RecommendRequestModel | TransformedRequestModel>;
recommend?: RequesterConfig<RecommendRequestModel>;
suggest?: RequesterConfig<SuggestRequestModel>;
};

Expand Down Expand Up @@ -126,19 +126,6 @@ export type RecommendRequestModel = {
branch?: string;
};

export type TransformedRequestModel = {
profiles: RecommendPostProfileObject[];
globals: {
product?: string;
products?: string[];
blockedItems?: string[];
cart?: string[];
lastViewed?: string[];
withRecInfo?: boolean;
};
groupId?: number;
};

//TODO: move to snapi
export type PostRecommendAPISpec = {
siteId: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ export class RecommendationController extends AbstractController {
batched: this.config.batched,
branch: this.config.branch || 'production',
order: this.context?.options?.order,
groupId: this.config.groupId,
...this.config.globals,
};

Expand Down Expand Up @@ -448,7 +449,7 @@ export class RecommendationController extends AbstractController {

const searchProfile = this.profiler.create({ type: 'event', name: 'search', context: params }).start();

const response = await this.client.recommend(params, this.config.groupId);
const response = await this.client.recommend(params);
searchProfile.stop();
this.log.profile(searchProfile);

Expand Down
Loading

0 comments on commit f285488

Please sign in to comment.