-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #547 from Pinelab-studio/feat/fpgrowth-impl
New Plugin: Frequently bought together products
- Loading branch information
Showing
22 changed files
with
2,793 additions
and
2,067 deletions.
There are no files selected for viewing
6 changes: 6 additions & 0 deletions
6
packages/vendure-plugin-frequently-bought-together/CHANGELOG.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
packages/vendure-plugin-frequently-bought-together/LICENSE
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
Checkout https://www.tldrlegal.com/license/business-source-license-bsl-1-1 | ||
for a short summary of the BSL 1.1 license. | ||
Disclaimer: the link above is only a short summary of the Full Text. The license below is leading. | ||
|
||
-------------------------------------------------------------------------------- | ||
|
||
Licensor: Pinelab | ||
|
||
Licensed Work: Pinelab Frequently Bought Together plugin for Vendure | ||
The Licensed Work is Copyright © 2024 Pinelab | ||
|
||
Additional Use Grant: 1. License Purchase Requirement: The right to use the | ||
Licensed Work in a production environment is granted only | ||
when a valid license has been purchased from the | ||
Vendure Plugin Marketplace. Proof of purchase must be | ||
maintained and provided upon request by the Licensor. | ||
|
||
2. Single Project Restriction: You are permitted to use | ||
the Licensed Work in the production environment of only one | ||
project. For the purposes of this License, a "project" is | ||
defined as one or more server instances of the Vendure | ||
application connected to a single production database. | ||
Use of the Licensed Work in multiple projects or multiple | ||
production databases is prohibited without obtaining an | ||
additional license from the Licensor. | ||
|
||
The terms of the License will be governed by and construed | ||
in accordance with Dutch law of the Netherlands. | ||
|
||
Change Date: Three years from release date | ||
|
||
Change License: GNU General Public License (GPL) v3 | ||
|
||
-------------------------------------------------------------------------------- | ||
|
||
Business Source License 1.1 | ||
|
||
Terms | ||
|
||
The Licensor hereby grants you the right to copy, modify, create derivative | ||
works, redistribute, and make non-production use of the Licensed Work. The | ||
Licensor may make an Additional Use Grant, above, permitting limited production | ||
use. | ||
|
||
Effective on the Change Date, or the fourth anniversary of the first publicly | ||
available distribution of a specific version of the Licensed Work under this | ||
License, whichever comes first, the Licensor hereby grants you rights under the | ||
terms of the Change License, and the rights granted in the paragraph above | ||
terminate. | ||
|
||
If your use of the Licensed Work does not comply with the requirements currently | ||
in effect as described in this License, you must purchase a commercial license | ||
from the Licensor, its affiliated entities, or authorized resellers, or you must | ||
refrain from using the Licensed Work. | ||
|
||
All copies of the original and modified Licensed Work, and derivative works of | ||
the Licensed Work, are subject to this License. This License applies separately | ||
for each version of the Licensed Work and the Change Date may vary for each | ||
version of the Licensed Work released by Licensor. | ||
|
||
You must conspicuously display this License on each original or modified copy of | ||
the Licensed Work. If you receive the Licensed Work in original or modified form | ||
from a third party, the terms and conditions set forth in this License apply to | ||
your use of that work. | ||
|
||
Any use of the Licensed Work in violation of this License will automatically | ||
terminate your rights under this License for the current and all other versions | ||
of the Licensed Work. | ||
|
||
This License does not grant you any right in any trademark or logo of Licensor | ||
or its affiliates (provided that you may use a trademark or logo of Licensor as | ||
expressly required by this License). | ||
|
||
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN | ||
“AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS | ||
OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE. | ||
|
||
MariaDB hereby grants you permission to use this License’s text to license your | ||
works, and to refer to it using the trademark “Business Source License”, as long | ||
as you comply with the Covenants of Licensor below. | ||
|
||
Covenants of Licensor | ||
|
||
In consideration of the right to use this License’s text and the “Business | ||
Source License” name and trademark, Licensor covenants to MariaDB, and to all | ||
other recipients of the licensed work to be provided by Licensor: | ||
|
||
1. To specify as the Change License the GPL Version 2.0 or any later version, | ||
or a license that is compatible with GPL Version 2.0 or a later version, | ||
where “compatible” means that software provided under the Change License can | ||
be included in a program with software provided under GPL Version 2.0 or a | ||
later version. Licensor may specify additional Change Licenses without | ||
limitation. | ||
|
||
2. To either: (a) specify an additional grant of rights to use that does not | ||
impose any additional restriction on the right granted in this License, as | ||
the Additional Use Grant; or (b) insert the text “None”. | ||
|
||
3. To specify a Change Date. | ||
|
||
4. Not to modify this License in any other way. | ||
|
||
Notice | ||
|
||
The Business Source License (this document, or the "License") is not an Open | ||
Source license. However, the Licensed Work will eventually be made available | ||
under an Open Source License, as stated in this License. | ||
|
||
License text copyright © 2023 MariaDB plc, All Rights Reserved. | ||
“Business Source License” is a trademark of MariaDB plc. |
130 changes: 130 additions & 0 deletions
130
packages/vendure-plugin-frequently-bought-together/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,133 @@ | ||
# Frequently Bought Together Plugin | ||
|
||
### [Official documentation here](https://pinelab-plugins.com/plugin/vendure-plugin-frequently-bought-together) | ||
|
||
This plugin finds products that are often bought together by looking at past orders. You can integrate these frequently bought together products on your storefront, and so increase your revenue. | ||
|
||
## Installation | ||
|
||
1. To install the plugin, add it to your Vendure config and include its Admin UI extension in the Admin UI plugin: | ||
|
||
```ts | ||
import { FrequentlyBoughtTogetherPlugin } from '@vendure-hub/pinelab-frequently-bought-together-plugin'; | ||
|
||
const vendureConfig = { | ||
plugins: [ | ||
FrequentlyBoughtTogetherPlugin.init({ | ||
// Disable this in production! | ||
experimentMode: true, | ||
// Test this support level first. See below for more details. | ||
supportLevel: 0.001, | ||
}), | ||
AdminUiPlugin.init({ | ||
port: 3002, | ||
route: 'admin', | ||
app: compileUiExtensions({ | ||
outputPath: path.join(__dirname, '__admin-ui'), | ||
extensions: [FrequentlyBoughtTogetherPlugin.ui], | ||
}), | ||
}), | ||
], | ||
}; | ||
``` | ||
|
||
2. Run a database migration to add the custom fields to your database. | ||
|
||
## Storefront usage | ||
|
||
You can get the related product via the shop API with the following query: | ||
|
||
```graphql | ||
{ | ||
product(id: 2) { | ||
id | ||
name | ||
slug | ||
frequentlyBoughtWith { | ||
id | ||
name | ||
slug | ||
} | ||
} | ||
} | ||
``` | ||
|
||
Product relations in the Shop API are sorted by support, meaning that the most bought together products will appear first in the list. | ||
The admin UI shows relations in random order due to the unordered nature of SQL relations. | ||
|
||
## Experiment mode | ||
|
||
Each shop's optimal support level varies based on data density. For example, a shop with many variants and few orders requires a lower support level. To experiment with support levels: | ||
|
||
1. Start the server locally, and make sure you have set `experimentMode: true` in the plugin's init function. | ||
2. Go to `http://localhost:3000/admin-api` or use a GraphQL client like Yaak to use the admin API | ||
3. Execute the following query against the admin api: | ||
|
||
```graphql | ||
{ | ||
previewFrequentlyBoughtTogether(support: 0.1) { | ||
# The peak amount of memory that was used during calculation. This should be a max of 80% of your worker instance | ||
maxMemoryUsedInMB | ||
# The different products that are included in the relations | ||
uniqueProducts | ||
# Total number of item sets | ||
totalItemSets | ||
# Most confident item sets | ||
bestItemSets { | ||
# E.g. ['product-1', 'product-5'] | ||
items | ||
# Support is the number of orders this combination was in | ||
support | ||
} | ||
# Least confident item sets | ||
worstItemSets { | ||
items | ||
support | ||
} | ||
} | ||
} | ||
``` | ||
|
||
When you have found your desired support level, you set it in the plugin: | ||
|
||
```ts | ||
FrequentlyBoughtTogetherPlugin.init({ | ||
// Disable experiment mode in production! | ||
experimentMode: false, | ||
supportLevel: 0.00005 | ||
}), | ||
``` | ||
|
||
1. Run the server again | ||
2. Go to `/admin/catalog/products` | ||
3. Click the three buttons at the top right and click `Calculate frequently bought together relations` | ||
|
||
The frequently bought together relations are now set on your products. | ||
|
||
Tips for Tweaking Support Levels: | ||
|
||
- Start high (e.g., `0.1`) and gradually reduce (`0.01`, `0.001`, etc.). | ||
- Review the worst item sets: | ||
- Increase the support level if they seem irrelevant. | ||
- If support equals 1, it indicates a single order—a poor indicator of frequent purchases. | ||
- Monitor memory usage to avoid exceeding worker RAM. | ||
|
||
## Channel specific support | ||
|
||
To set different support levels for channels: | ||
|
||
```ts | ||
FrequentlyBoughtTogetherPlugin.init({ | ||
// Disable experiment mode in production! | ||
experimentMode: false, | ||
supportLevel: (ctx) => { | ||
if (ctx.channel.token === 'channel-with-lots-of-variants') { | ||
return 0.000001 | ||
} else { | ||
return 0.0001 | ||
} | ||
} | ||
}), | ||
``` | ||
|
||
Use the `vendure-token` header to preview queries for specific channels. |
10 changes: 10 additions & 0 deletions
10
packages/vendure-plugin-frequently-bought-together/codegen.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
schema: 'src/**/*.ts' | ||
generates: | ||
./src/generated-graphql-types.ts: | ||
plugins: | ||
- typescript | ||
- typescript-operations | ||
config: | ||
avoidOptionals: false | ||
scalars: | ||
DateTime: Date |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
{ | ||
"name": "@pinelab/vendure-plugin-frequently-bought-together", | ||
"name": "@vendure-hub/pinelab-frequently-bought-together-plugin", | ||
"version": "0.0.2", | ||
"description": "Increase average order value by suggesting frequently bought together products based on past orders. Also known as related products or product recommendations.", | ||
"keywords": [ | ||
|
@@ -11,7 +11,7 @@ | |
"author": "Martijn van de Brug <[email protected]>", | ||
"homepage": "https://pinelab-plugins.com/", | ||
"repository": "https://github.com/Pinelab-studio/pinelab-vendure-plugins", | ||
"license": "MIT", | ||
"license": "SEE LICENSE IN FILE 'LICENSE'", | ||
"private": false, | ||
"publishConfig": { | ||
"access": "public" | ||
|
@@ -21,15 +21,19 @@ | |
"files": [ | ||
"dist", | ||
"README.md", | ||
"CHANGELOG.md" | ||
"CHANGELOG.md", | ||
"LICENSE" | ||
], | ||
"scripts": { | ||
"build": "rimraf dist && tsc", | ||
"build": "rimraf dist && yarn generate && tsc && copyfiles -u 1 'src/ui/**/*' dist/", | ||
"start": "yarn ts-node test/dev-server.ts", | ||
"generate": "graphql-codegen --config codegen.yml", | ||
"test": "vitest run --bail 1", | ||
"lint": "eslint ." | ||
}, | ||
"dependencies": { | ||
"catch-unknown": "^2.0.0" | ||
"@vendure-hub/vendure-hub-plugin": "^0.0.2", | ||
"catch-unknown": "^2.0.0", | ||
"node-fpgrowth": "^1.2.1" | ||
} | ||
} |
59 changes: 57 additions & 2 deletions
59
packages/vendure-plugin-frequently-bought-together/src/api/api-extensions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,61 @@ | ||
import gql from 'graphql-tag'; | ||
|
||
const frequentlyBoughtTogetherAdminApiExtensions = gql``; | ||
export const adminApiExtensions = gql` | ||
${frequentlyBoughtTogetherAdminApiExtensions} | ||
type FrequentlyBoughtTogetherPreview { | ||
""" | ||
The max memory used during the calculation. | ||
Make sure this doesn't exceed your worker's memory limit. | ||
process.memoryUsage().rss is used to calculate this. | ||
""" | ||
maxMemoryUsedInMB: Int! | ||
""" | ||
The total number of sets found. | ||
""" | ||
totalItemSets: Int! | ||
""" | ||
The number of unique products for which a related product was found | ||
""" | ||
uniqueProducts: Int! | ||
""" | ||
The item sets with the most support | ||
""" | ||
bestItemSets: [FrequentlyBoughtTogetherItemSet!]! | ||
""" | ||
The item sets with the worst support. If these make sense, the others probably do too. | ||
""" | ||
worstItemSets: [FrequentlyBoughtTogetherItemSet!]! | ||
} | ||
""" | ||
An item set with a support value. | ||
An item set is a combination of products which are frequently bought together, e.g. ['product-1', 'product-2', 'product-3'] | ||
Support is the number of orders this combination was in | ||
""" | ||
type FrequentlyBoughtTogetherItemSet { | ||
items: [String!]! | ||
support: Int! | ||
} | ||
extend type Query { | ||
""" | ||
Preview the frequently bought together item sets, | ||
to check what level of support is reasonable for your data set | ||
""" | ||
previewFrequentlyBoughtTogether( | ||
support: Float! | ||
): FrequentlyBoughtTogetherPreview! | ||
} | ||
extend type Mutation { | ||
""" | ||
Trigger the job to calculate and set frequently bought together products. | ||
""" | ||
triggerFrequentlyBoughtTogetherCalculation: Boolean! | ||
} | ||
`; | ||
|
||
export const shopApiExtensions = gql` | ||
extend type Product { | ||
frequentlyBoughtWith: [Product!]! | ||
} | ||
`; |
Oops, something went wrong.