Skip to content

Commit

Permalink
feat(core): Allow custom CollectionFilters in config
Browse files Browse the repository at this point in the history
Closes #325
  • Loading branch information
michaelbromley committed May 1, 2020
1 parent 16db620 commit 87edc9b
Show file tree
Hide file tree
Showing 11 changed files with 91 additions and 62 deletions.
13 changes: 6 additions & 7 deletions packages/core/e2e/shop-order.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ describe('Shop orders', () => {
it('availableCountries returns enabled countries', async () => {
// disable Austria
const { countries } = await adminClient.query<GetCountryList.Query>(GET_COUNTRY_LIST, {});
const AT = countries.items.find(c => c.code === 'AT')!;
const AT = countries.items.find((c) => c.code === 'AT')!;
await adminClient.query<UpdateCountry.Mutation, UpdateCountry.Variables>(UPDATE_COUNTRY, {
input: {
id: AT.id,
Expand All @@ -106,7 +106,7 @@ describe('Shop orders', () => {

const result = await shopClient.query<GetAvailableCountries.Query>(GET_AVAILABLE_COUNTRIES);
expect(result.availableCountries.length).toBe(countries.items.length - 1);
expect(result.availableCountries.find(c => c.id === AT.id)).toBeUndefined();
expect(result.availableCountries.find((c) => c.id === AT.id)).toBeUndefined();
});

describe('ordering as anonymous user', () => {
Expand Down Expand Up @@ -256,7 +256,7 @@ describe('Shop orders', () => {
quantity: 3,
});
expect(addItemToOrder!.lines.length).toBe(2);
expect(addItemToOrder!.lines.map(i => i.productVariant.id)).toEqual(['T_1', 'T_3']);
expect(addItemToOrder!.lines.map((i) => i.productVariant.id)).toEqual(['T_1', 'T_3']);

const { removeOrderLine } = await shopClient.query<
RemoveItemFromOrder.Mutation,
Expand All @@ -265,7 +265,7 @@ describe('Shop orders', () => {
orderLineId: firstOrderLineId,
});
expect(removeOrderLine!.lines.length).toBe(1);
expect(removeOrderLine!.lines.map(i => i.productVariant.id)).toEqual(['T_3']);
expect(removeOrderLine!.lines.map((i) => i.productVariant.id)).toEqual(['T_3']);
});

it(
Expand Down Expand Up @@ -427,7 +427,6 @@ describe('Shop orders', () => {
});

it('customer default Addresses are updated after payment', async () => {
// TODO: will need to be reworked for https://github.com/vendure-ecommerce/vendure/issues/98
const result = await adminClient.query<GetCustomer.Query, GetCustomer.Variables>(GET_CUSTOMER, {
id: createdCustomerId,
});
Expand Down Expand Up @@ -533,7 +532,7 @@ describe('Shop orders', () => {
quantity: 3,
});
expect(addItemToOrder!.lines.length).toBe(2);
expect(addItemToOrder!.lines.map(i => i.productVariant.id)).toEqual(['T_1', 'T_3']);
expect(addItemToOrder!.lines.map((i) => i.productVariant.id)).toEqual(['T_1', 'T_3']);

const { removeOrderLine } = await shopClient.query<
RemoveItemFromOrder.Mutation,
Expand All @@ -542,7 +541,7 @@ describe('Shop orders', () => {
orderLineId: firstOrderLineId,
});
expect(removeOrderLine!.lines.length).toBe(1);
expect(removeOrderLine!.lines.map(i => i.productVariant.id)).toEqual(['T_3']);
expect(removeOrderLine!.lines.map((i) => i.productVariant.id)).toEqual(['T_3']);
});

it('nextOrderStates returns next valid states', async () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,12 @@ export class AppModule implements NestModule, OnApplicationBootstrap, OnApplicat

private getConfigurableOperations(): Array<ConfigurableOperationDef<any>> {
const { paymentMethodHandlers } = this.configService.paymentOptions;
// TODO: add CollectionFilters once #325 is fixed
const { collectionFilters } = this.configService.catalogOptions;
const { promotionActions, promotionConditions } = this.configService.promotionOptions;
const { shippingCalculators, shippingEligibilityCheckers } = this.configService.shippingOptions;
return [
...paymentMethodHandlers,
...collectionFilters,
...(promotionActions || []),
...(promotionConditions || []),
...(shippingCalculators || []),
Expand Down
11 changes: 11 additions & 0 deletions packages/core/src/config/collection/collection-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ export interface CollectionFilterConfig<T extends CollectionFilterArgs>
apply: ApplyCollectionFilterFn<T>;
}

/**
* @description
* A CollectionFilter defines a rule which can be used to associate ProductVariants with a Collection.
* The filtering is done by defining the `apply()` function, which receives a TypeORM
* [`QueryBuilder`](https://typeorm.io/#/select-query-builder) object to which clauses may be added.
*
* Creating a CollectionFilter is considered an advanced Vendure topic. For more insight into how
* they work, study the [default collection filters](https://github.com/vendure-ecommerce/vendure/blob/master/packages/core/src/config/collection/default-collection-filters.ts)
*
* @docsCategory configuration
*/
export class CollectionFilter<T extends CollectionFilterArgs = {}> extends ConfigurableOperationDef<T> {
private readonly applyFn: ApplyCollectionFilterFn<T>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,5 @@ export const variantNameCollectionFilter = new CollectionFilter({
}
},
});

export const defaultCollectionFilters = [facetValueCollectionFilter, variantNameCollectionFilter];
1 change: 1 addition & 0 deletions packages/core/src/config/config.service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class MockConfigService implements MockClass<ConfigService> {
assetStorageStrategy: {} as any,
assetPreviewStrategy: {} as any,
};
catalogOptions: {};
uploadMaxFileSize = 1024;
dbConnectionOptions = {};
shippingOptions = {};
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/config/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { EntityIdStrategy } from './entity-id-strategy/entity-id-strategy';
import { Logger, VendureLogger } from './logger/vendure-logger';
import {
AssetOptions,
AuthOptions,
AuthOptions, CatalogOptions,
ImportExportOptions,
JobQueueOptions,
OrderOptions,
Expand Down Expand Up @@ -40,6 +40,10 @@ export class ConfigService implements VendureConfig {
return this.activeConfig.authOptions;
}

get catalogOptions(): Required<CatalogOptions> {
return this.activeConfig.catalogOptions;
}

get defaultChannelToken(): string | null {
return this.activeConfig.defaultChannelToken;
}
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/config/default-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { InMemoryJobQueueStrategy } from '../job-queue/in-memory-job-queue-strat
import { DefaultAssetNamingStrategy } from './asset-naming-strategy/default-asset-naming-strategy';
import { NoAssetPreviewStrategy } from './asset-preview-strategy/no-asset-preview-strategy';
import { NoAssetStorageStrategy } from './asset-storage-strategy/no-asset-storage-strategy';
import { defaultCollectionFilters } from './collection/default-collection-filters';
import { AutoIncrementIdStrategy } from './entity-id-strategy/auto-increment-id-strategy';
import { DefaultLogger } from './logger/default-logger';
import { TypeOrmLogger } from './logger/typeorm-logger';
Expand Down Expand Up @@ -47,6 +48,9 @@ export const defaultConfig: RuntimeVendureConfig = {
requireVerification: true,
verificationTokenDuration: '7d',
},
catalogOptions: {
collectionFilters: defaultCollectionFilters,
},
adminApiPath: 'admin-api',
shopApiPath: 'shop-api',
entityIdStrategy: new AutoIncrementIdStrategy(),
Expand Down
25 changes: 23 additions & 2 deletions packages/core/src/config/vendure-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { OrderState } from '../service/helpers/order-state-machine/order-state';
import { AssetNamingStrategy } from './asset-naming-strategy/asset-naming-strategy';
import { AssetPreviewStrategy } from './asset-preview-strategy/asset-preview-strategy';
import { AssetStorageStrategy } from './asset-storage-strategy/asset-storage-strategy';
import { CollectionFilter } from './collection/collection-filter';
import { CustomFields } from './custom-field/custom-field-types';
import { EntityIdStrategy } from './entity-id-strategy/entity-id-strategy';
import { JobQueueStrategy } from './job-queue/job-queue-strategy';
Expand Down Expand Up @@ -244,9 +245,24 @@ export interface AssetOptions {
}

/**
* @docsCategory promotions
* @description
* Options related to products and collections.
*
* */
* @docsCategory configuration
*/
export interface CatalogOptions {
/**
* @description
* Allows custom {@link CollectionFilter}s to be defined.
*
* @default defaultCollectionFilters
*/
collectionFilters: Array<CollectionFilter<any>>;
}

/**
* @docsCategory promotions
*/
export interface PromotionOptions {
/**
* @description
Expand Down Expand Up @@ -437,6 +453,11 @@ export interface VendureConfig {
* Configuration for authorization.
*/
authOptions: AuthOptions;
/**
* @description
* Configuration for Products and Collections.
*/
catalogOptions?: CatalogOptions;
/**
* @description
* The name of the property which contains the token of the
Expand Down
36 changes: 14 additions & 22 deletions packages/core/src/service/controllers/collection.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ import { ID } from '@vendure/common/lib/shared-types';
import { Observable } from 'rxjs';
import { Connection } from 'typeorm';

import {
facetValueCollectionFilter,
variantNameCollectionFilter,
} from '../../config/collection/default-collection-filters';
import { ConfigService } from '../../config/config.service';
import { Logger } from '../../config/logger/vendure-logger';
import { Collection } from '../../entity/collection/collection.entity';
import { ProductVariant } from '../../entity/product-variant/product-variant.entity';
Expand All @@ -27,13 +24,14 @@ export class CollectionController {
constructor(
@InjectConnection() private connection: Connection,
private collectionService: CollectionService,
private configService: ConfigService,
) {}

@MessagePattern(ApplyCollectionFiltersMessage.pattern)
applyCollectionFilters({
collectionIds,
}: ApplyCollectionFiltersMessage['data']): Observable<ApplyCollectionFiltersMessage['response']> {
return asyncObservable(async observer => {
return asyncObservable(async (observer) => {
Logger.verbose(`Processing ${collectionIds.length} Collections`);
const timeStart = Date.now();
const collections = await this.connection.getRepository(Collection).findByIds(collectionIds, {
Expand All @@ -60,7 +58,7 @@ export class CollectionController {
private async applyCollectionFiltersInternal(collection: Collection): Promise<ID[]> {
const ancestorFilters = await this.collectionService
.getAncestors(collection.id)
.then(ancestors =>
.then((ancestors) =>
ancestors.reduce(
(filters, c) => [...filters, ...(c.filters || [])],
[] as ConfigurableOperation[],
Expand All @@ -71,7 +69,7 @@ export class CollectionController {
...ancestorFilters,
...(collection.filters || []),
]);
const postIds = collection.productVariants.map(v => v.id);
const postIds = collection.productVariants.map((v) => v.id);
try {
await this.connection
.getRepository(Collection)
Expand All @@ -90,8 +88,8 @@ export class CollectionController {
const preIdsSet = new Set(preIds);
const postIdsSet = new Set(postIds);
const difference = [
...preIds.filter(id => !postIdsSet.has(id)),
...postIds.filter(id => !preIdsSet.has(id)),
...preIds.filter((id) => !postIdsSet.has(id)),
...postIds.filter((id) => !preIdsSet.has(id)),
];
return difference;
}
Expand All @@ -103,21 +101,15 @@ export class CollectionController {
if (filters.length === 0) {
return [];
}
const facetFilters = filters.filter(f => f.code === facetValueCollectionFilter.code);
const variantNameFilters = filters.filter(f => f.code === variantNameCollectionFilter.code);
const { collectionFilters } = this.configService.catalogOptions;
let qb = this.connection.getRepository(ProductVariant).createQueryBuilder('productVariant');

// Apply any facetValue-based filters
if (facetFilters.length) {
for (const filter of facetFilters) {
qb = facetValueCollectionFilter.apply(qb, filter.args);
}
}

// Apply any variant name-based filters
if (variantNameFilters.length) {
for (const filter of variantNameFilters) {
qb = variantNameCollectionFilter.apply(qb, filter.args);
for (const filterType of collectionFilters) {
const filtersOfType = filters.filter((f) => f.code === filterType.code);
if (filtersOfType.length) {
for (const filter of filtersOfType) {
qb = filterType.apply(qb, filter.args);
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/service/service.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export class ServiceModule {
}
return {
module: ServiceModule,
imports: [workerTypeOrmModule],
imports: [workerTypeOrmModule, ConfigModule],
controllers: workerControllers,
};
}
Expand Down
Loading

0 comments on commit 87edc9b

Please sign in to comment.