Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vendure Primary Collection Plugin #258

Merged
merged 5 commits into from
Sep 22, 2023
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 .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"packages/vendure-plugin-order-export/",
"packages/vendure-plugin-picqer/",
"packages/vendure-plugin-popularity-scores/",
"packages/vendure-plugin-primary-collection",
"packages/vendure-plugin-sales-per-variant",
"packages/vendure-plugin-selectable-gifts/",
"packages/vendure-plugin-sendcloud/",
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ jobs:
'vendure-plugin-stripe-subscription',
'vendure-plugin-variant-bulk-update',
'vendure-plugin-webhook',
'vendure-plugin-primary-collection',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good one, I always forget this 👌

]
steps:
- name: Checkout code
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
"modify-customer-orders",
"multiserver-db-sessioncache",
"vendure-order-client",
"primary-collection",
"all-plugins"
]
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ describe('Vendure order client', () => {
additionalOrderFields
);
expect(client).toBeInstanceOf(VendureOrderClient);
expect(client.activeOrder).toBeUndefined();
expect(client.$activeOrder?.value).toBeUndefined();
expect(client.eventBus).toBeDefined();
client.eventBus.on(
'*',
Expand Down
3 changes: 3 additions & 0 deletions packages/vendure-plugin-primary-collection/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# 1.0.0 (2023-09-18)

Implemented services, resolvers and tests.
15 changes: 15 additions & 0 deletions packages/vendure-plugin-primary-collection/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Vendure Primary Collection Plugin

![Vendure version](https://img.shields.io/badge/dynamic/json.svg?url=https%3A%2F%2Fraw.githubusercontent.com%2FPinelab-studio%2Fpinelab-vendure-plugins%2Fmain%2Fpackage.json&query=$.devDependencies[%27@vendure/core%27]&colorB=blue&label=Built%20on%20Vendure)

To construct breadcrumbs and URL's it's useful to have a primary collection for each product, in case a product is part of multiple collections. This plugin extends `vendure`'s `Product` graphql type adding a `primaryCollection` field which points to the primary collection of a product, which is the the highest placed collection in Vendure (Collection's are sortable in Vendure, and it's a good practice to sort by importance).

## Getting started

Add the plugin to your `vendure-config.ts`:

```ts
plugins: [PrimaryCollectionPlugin];
```

and your good to go with this just that.
Empty file.
26 changes: 26 additions & 0 deletions packages/vendure-plugin-primary-collection/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@pinelab/vendure-plugin-primary-collection",
"version": "1.0.0",
"description": "Adds a primary collection to all Products by extending vendure's graphql api",
"author": "Surafel Melese Tariku <[email protected]>",
"homepage": "https://pinelab-plugins.com/",
"repository": "https://github.com/Pinelab-studio/pinelab-vendure-plugins",
"license": "MIT",
"private": false,
"publishConfig": {
"access": "public"
},
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist",
"README.md",
"CHANGELOG.md"
],
"scripts": {
"start": "yarn ts-node test/dev-server.ts",
"build": "rimraf dist && tsc",
"test": "vitest run"
},
"gitHead": "476f36da3aafea41fbf21c70774a30306f1d238f"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { PluginCommonModule, Type, VendurePlugin } from '@vendure/core';
import { gql } from 'graphql-tag';
import { PrimaryCollectionResolver } from './primary-collection.resolver';

@VendurePlugin({
imports: [PluginCommonModule],
shopApiExtensions: {
schema: gql`
extend type Product {
primaryCollection: Collection
}
`,
resolvers: [PrimaryCollectionResolver],
},
compatibility: '^2.0.0',
})
export class PrimaryCollectionPlugin {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Parent, ResolveField, Resolver } from '@nestjs/graphql';
import {
Collection,
CollectionService,
Ctx,
RequestContext,
Product,
} from '@vendure/core';

@Resolver('Product')
export class PrimaryCollectionResolver {
constructor(private readonly collectionService: CollectionService) {}

@ResolveField()
async primaryCollection(
@Ctx() ctx: RequestContext,
@Parent() product: Product
): Promise<Collection | null> {
const collections = await this.collectionService.getCollectionsByProductId(
ctx,
product.id,
true
);
const collectionsExcludingParents = collections.filter(
(coll) => !collections.find((childColl) => childColl.parentId === coll.id)
);
return collectionsExcludingParents.reduce<Collection | null>((acc, val) => {
if (!acc) {
return val;
}
if (val.position < acc.position) {
return val;
}
return acc;
}, null);
}
}
41 changes: 41 additions & 0 deletions packages/vendure-plugin-primary-collection/test/dev-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
import {
DefaultLogger,
DefaultSearchPlugin,
LogLevel,
mergeConfig,
} from '@vendure/core';
import {
createTestEnvironment,
registerInitializer,
SqljsInitializer,
testConfig,
} from '@vendure/testing';
import { initialTestData } from './initial-test-data';
import { PrimaryCollectionPlugin } from '../src/primary-collection-plugin';

require('dotenv').config();

(async () => {
registerInitializer('sqljs', new SqljsInitializer('__data__'));
const devConfig = mergeConfig(testConfig, {
logger: new DefaultLogger({ level: LogLevel.Debug }),
plugins: [
PrimaryCollectionPlugin,
DefaultSearchPlugin,
AdminUiPlugin.init({
port: 3002,
route: 'admin',
}),
],
apiOptions: {
shopApiPlayground: true,
adminApiPlayground: true,
},
});
const { server, adminClient, shopClient } = createTestEnvironment(devConfig);
await server.init({
initialData: initialTestData,
productsCsvPath: './test/products.csv',
});
})();
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { LanguageCode } from '@vendure/common/lib/generated-types';
import { InitialData } from '@vendure/core';

export const initialTestData: InitialData = {
defaultLanguage: LanguageCode.en,
defaultZone: 'Europe',
taxRates: [
{ name: 'Standard Tax', percentage: 20 },
{ name: 'Reduced Tax', percentage: 10 },
{ name: 'Zero Tax', percentage: 0 },
],
shippingMethods: [
{ name: 'Standard Shipping', price: 500 },
{ name: 'Express Shipping', price: 1000 },
],
countries: [
{ name: 'Australia', code: 'AU', zone: 'Oceania' },
{ name: 'Austria', code: 'AT', zone: 'Europe' },
{ name: 'Canada', code: 'CA', zone: 'Americas' },
{ name: 'China', code: 'CN', zone: 'Asia' },
{ name: 'South Africa', code: 'ZA', zone: 'Africa' },
{ name: 'United Kingdom', code: 'GB', zone: 'Europe' },
{ name: 'United States of America', code: 'US', zone: 'Americas' },
{ name: 'Nederland', code: 'NL', zone: 'Europe' },
],
collections: [
{
name: 'Computers',
filters: [
{
code: 'facet-value-filter',
args: { facetValueNames: ['computers'], containsAny: false },
},
],
},
{
name: 'Electronics',
filters: [
{
code: 'facet-value-filter',
args: { facetValueNames: ['electronics'], containsAny: false },
},
],
},
{
name: 'Others',
filters: [
{
code: 'facet-value-filter',
args: { facetValueNames: ['others'], containsAny: false },
},
],
},
],
paymentMethods: [],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { DefaultLogger, LogLevel, mergeConfig } from '@vendure/core';
import {
createTestEnvironment,
registerInitializer,
SimpleGraphQLClient,
SqljsInitializer,
testConfig,
} from '@vendure/testing';
import { TestServer } from '@vendure/testing/lib/test-server';
import { initialTestData } from './initial-test-data';
import { testPaymentMethod } from '../../test/src/test-payment-method';
import { PrimaryCollectionPlugin } from '../src/primary-collection-plugin';
import { expect, describe, beforeAll, afterAll, it } from 'vitest';
import { gql } from 'graphql-tag';

describe('Product Primary Collection', function () {
let server: TestServer;
let adminClient: SimpleGraphQLClient;
let shopClient: SimpleGraphQLClient;
let serverStarted = false;

beforeAll(async () => {
registerInitializer('sqljs', new SqljsInitializer('__data__'));
const config = mergeConfig(testConfig, {
apiOptions: {
port: 3106,
},
logger: new DefaultLogger({ level: LogLevel.Debug }),
plugins: [PrimaryCollectionPlugin],
paymentOptions: {
paymentMethodHandlers: [testPaymentMethod],
},
});

({ server, adminClient, shopClient } = createTestEnvironment(config));
await server.init({
initialData: {
...initialTestData,
paymentMethods: [
{
name: testPaymentMethod.code,
handler: { code: testPaymentMethod.code, arguments: [] },
},
],
},
productsCsvPath: './test/products.csv',
customerCount: 2,
});
serverStarted = true;
}, 60000);

it('Should start successfully', async () => {
expect(serverStarted).toBe(true);
});

const primaryCollectionQuery = gql`
query PrimaryCollectionQuery($productId: ID) {
product(id: $productId) {
name
primaryCollection {
id
name
}
}
}
`;

it("Should return 'Computers' as a primary collection for 'Laptop'", async () => {
const { product } = await shopClient.query(primaryCollectionQuery, {
productId: 'T_1',
});
expect(product.name).toBe('Laptop');
expect(product.primaryCollection.name).toBe('Computers');
});

it("Should return 'Electronics' as a primary collection for 'Cars'", async () => {
const { product } = await shopClient.query(primaryCollectionQuery, {
productId: 'T_2',
});
expect(product.name).toBe('Cars');
expect(product.primaryCollection.name).toBe('Electronics');
});

it("Should return 'Others' as a primary collection for 'Motors'", async () => {
const { product } = await shopClient.query(primaryCollectionQuery, {
productId: 'T_3',
});
expect(product.name).toBe('Motors');
expect(product.primaryCollection.name).toBe('Others');
});

afterAll(async () => {
return server.destroy();
});
});
13 changes: 13 additions & 0 deletions packages/vendure-plugin-primary-collection/test/products.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name , slug , description , assets , facets , optionGroups , optionValues , sku , price , taxCategory , stockOnHand , trackInventory , variantAssets , variantFacets
Laptop , laptop , "Now equipped with seventh-generation Intel Core processors, Laptop is snappier than ever. From daily tasks like launching apps and opening files to more advanced computing, you can power through your day thanks to faster SSDs and Turbo Boost processing up to 3.6GHz." , , , "screen size|RAM" , "13 inch|8GB" , L2201308 , 1299.00 , standard , 100 , false , , category:electronics
, , , , , , "15 inch|8GB" , L2201508 , 1399.00 , standard , 100 , false , , category:computers
, , , , , , "13 inch|16GB" , L2201316 , 2199.00 , standard , 100 , false , , category:computers
, , , , , , "15 inch|16GB" , L2201516 , 2299.00 , standard , 100 , false , , category:computers
Cars , car , "Car is snappier than ever." , , , "tire|frequency" , "1 M|8 Hz" , C2201308 , 120099.00 , standard , 100 , false , , category:electronics
, , , , , , "2 M|16 Hz" , C2201508 , 139009.00 , standard , 100 , false , , category:electronics
, , , , , , "1 M|16 Hz" , C2201316 , 219009.00 , standard , 100 , false , , category:electronics
, , , , , , "2 M|8 Hz" , C2201516 , 229009.00 , standard , 100 , false , , category:electronics
Motors , motor , "All Badjajs are motors. Not all Motors are badjaj" , , , "color|smallness" , "Red|small" , M2201308 , 1760099.00 , standard , 100 , false , , category:others
, , , , , , "Green|small" , M2201508 , 179009.00 , standard , 100 , false , , category:others
, , , , , , "Red|large" , M2201316 , 129009.00 , standard , 100 , false , , category:others
, , , , , , "Green|large" , M2201516 , 123432009.00 , standard , 100 , false , , category:others
8 changes: 8 additions & 0 deletions packages/vendure-plugin-primary-collection/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"types": ["node"]
},
"include": ["src/"]
}
21 changes: 21 additions & 0 deletions packages/vendure-plugin-primary-collection/vitest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import swc from 'unplugin-swc';
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
include: './test/primary-collection.spec.ts',
},
plugins: [
// SWC required to support decorators used in test plugins
// See https://github.com/vitest-dev/vitest/issues/708#issuecomment-1118628479
// Vite plugin
swc.vite({
jsc: {
transform: {
// See https://github.com/vendure-ecommerce/vendure/issues/2099
useDefineForClassFields: false,
},
},
}),
],
});
Loading