Skip to content

Commit

Permalink
Merge pull request #258 from Pinelab-studio/feat/primary-collection
Browse files Browse the repository at this point in the history
Vendure Primary Collection Plugin
  • Loading branch information
martijnvdbrug authored Sep 22, 2023
2 parents 324d80f + 1bc6a6f commit 0a0cdb5
Show file tree
Hide file tree
Showing 16 changed files with 336 additions and 1 deletion.
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',
]
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 @@ -97,6 +97,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,
},
},
}),
],
});

0 comments on commit 0a0cdb5

Please sign in to comment.