3367 add facets subquery to product (#146)

This commit is contained in:
Anders Gustafsson
2023-02-28 10:28:58 +01:00
committed by GitHub
parent 5bc4e92c92
commit c475c1c860
8 changed files with 112 additions and 28 deletions
+3 -7
View File
@@ -1,7 +1,8 @@
import { RESTDataSource } from '@apollo/datasource-rest';
import { ScopeAccess, Scopes } from '../cognito/access-control';
import { Product, ProductGroup } from '../types/product-types';
import { Product } from '../types/product-types';
import { DataSourceOptions, User } from '../types/types';
import { isRepeatingPattern } from './utils';
export class InteriorsLambdaAPI extends RESTDataSource {
override baseURL = process.env.INTERIORS_URL;
@@ -23,11 +24,6 @@ export class InteriorsLambdaAPI extends RESTDataSource {
async generateNewInteriors(product: Product): Promise<any> {
ScopeAccess.validate(this.user).all([Scopes.INTERIORS_WRITE]);
const found = product.printProducts.find(
(pr) => pr.groupId === ProductGroup.WALLPAPER,
);
const isRepeating = found !== undefined;
const body = {
token: process.env.INTERIORS_TOKEN,
image: `products/${product.id}.jpg`,
@@ -38,7 +34,7 @@ export class InteriorsLambdaAPI extends RESTDataSource {
autodetect: true,
};
if (isRepeating) {
if (isRepeatingPattern(product)) {
body['print_file_height'] = product.fields.printFileHeight;
body['print_file_dpi'] = product.fields.printFileDpi;
}
+30 -5
View File
@@ -1,11 +1,16 @@
import DataLoader from 'dataloader';
import { SQLDataSource } from 'datasource-sql';
import { ScopeAccess, Scopes } from '../cognito/access-control';
import { Keyword, KeywordType } from '../types/keyword-types';
import { DataSourceOptions, User } from '../types/types';
import { TextsAPI } from './texts-api';
import { MINUTE } from './utils';
import { createQuestionMarksFromList, MINUTE } from './utils';
interface KeywordWithProductId extends Keyword {
product_id: number;
}
export class KeywordAPI extends SQLDataSource {
private productKeywordsLoader: DataLoader<number, Keyword[]>;
textsApi: TextsAPI;
user: User;
constructor(options: DataSourceOptions, config, textsApi) {
@@ -13,6 +18,20 @@ export class KeywordAPI extends SQLDataSource {
this.user = options.user;
this.initialize({ cache: options.cache, context: null });
this.textsApi = textsApi;
this.productKeywordsLoader = new DataLoader(async (ids: number[]) => {
const results = await this.getProductKeywordsBatch(ids);
const indexed = {} as Record<string, KeywordWithProductId[]>;
results.forEach((keywordRow) => {
if (!indexed[keywordRow.product_id]) {
indexed[keywordRow.product_id] = [keywordRow];
} else {
indexed[keywordRow.product_id].push(keywordRow);
}
});
const output = ids.map((id) => indexed[id] ?? []);
return output;
});
}
// -------------------------------
@@ -36,18 +55,24 @@ export class KeywordAPI extends SQLDataSource {
// Functions used in resolvers or other datasources
// ----------------------------
async getProductKeywords(productId: number): Promise<Array<Keyword>> {
async getProductKeywords(productId: number): Promise<Keyword[]> {
return this.productKeywordsLoader.load(productId);
}
async getProductKeywordsBatch(
productIds: number[],
): Promise<KeywordWithProductId[]> {
ScopeAccess.validate(this.user).all([Scopes.KEYWORDS_READ]);
const query = /* sql */ `
SELECT keywords.*, keyword_type.type_id FROM keywords
SELECT product_keyword.product_id,keywords.*, keyword_type.type_id FROM keywords
LEFT JOIN keyword_type ON keyword_type.keyword_id = keywords.id
JOIN product_keyword ON product_keyword.keyword_id = keywords.id
WHERE product_keyword.product_id = ?
WHERE product_keyword.product_id IN (${createQuestionMarksFromList(productIds)})
ORDER BY keywords.value
`;
return this.knex
.raw(query, productId)
.raw(query, productIds)
.then((data) => data.rows.map((row) => this.getType(row)));
}
+2 -15
View File
@@ -11,7 +11,6 @@ import {
ProductBlacklist,
ProductWallpaperType,
ProductFields,
Orientation,
ProductsFilterInput,
ProductInfoInput,
ProductBlacklistMarketInput,
@@ -25,6 +24,7 @@ import { DataSourceOptions, Maybe } from '../types/types';
import {
booleanStringField,
createQuestionMarksFromList,
getOrientationString,
isInt,
nullOrNumber,
roundOrDefaultTo,
@@ -153,24 +153,11 @@ export class ProductAPI extends BaseSQLDataSource {
return <ProductFields>fields;
}
getOrientation(fields: ProductFields): string {
if (!fields || !fields.width || !fields.height) {
return Orientation[Orientation.UNKNOWN];
}
if (fields.width > fields.height) {
return Orientation[Orientation.LANDSCAPE];
}
if (fields.width < fields.height) {
return Orientation[Orientation.STANDING];
}
return Orientation[Orientation.SQUARE];
}
createProductFromRow(row: any) {
row.type = this.getProductTypes(row);
row.wallpaperTypes = this.getWallpaperTypes(row);
row.designerId = row.designerid;
row.orientation = this.getOrientation(row.fields);
row.orientation = getOrientationString(row.fields);
row.publishingDate = row.publishing_date;
row.printProducts = this.getPrintProducts(row);
row.blacklisting = this.getBlacklisting(row);
+27
View File
@@ -1,3 +1,10 @@
import {
Orientation,
Product,
ProductFields,
ProductGroup,
} from '../types/product-types';
type InputType = string | undefined | null;
export const MINUTE = 60;
@@ -74,3 +81,23 @@ export function roundOrDefaultTo(
export const createQuestionMarksFromList = (arr: unknown[]): string => {
return '?,'.repeat(arr.length).slice(0, -1);
};
export const isRepeatingPattern = (product: Product): boolean => {
const found = product.printProducts.find(
(pr) => pr.groupId === ProductGroup.WALLPAPER,
);
return found !== undefined;
};
export const getOrientationString = (fields: ProductFields): string => {
if (!fields || !fields.width || !fields.height) {
return Orientation[Orientation.UNKNOWN];
}
if (fields.width > fields.height) {
return Orientation[Orientation.LANDSCAPE];
}
if (fields.width < fields.height) {
return Orientation[Orientation.STANDING];
}
return Orientation[Orientation.SQUARE];
};
+37
View File
@@ -1,5 +1,6 @@
import { ProductAPI } from '../datasources/product-api';
import {
Orientation,
PrintProduct,
Product,
ProductListResult,
@@ -20,6 +21,7 @@ import { DesignerAPI } from '../datasources/designer-api';
import { PrintProductDefaultsAPI } from '../datasources/printproduct-defaults-api';
import { PrintProductDefaults } from '../types/printproduct-defaults-types';
import { GraphQLError } from 'graphql';
import { getOrientationString, isRepeatingPattern } from '../datasources/utils';
const ProductBlacklist = {
async market(parent, _args, { dataSources }) {
@@ -45,6 +47,41 @@ const Product = {
async related({ id }, _args, { dataSources }) {
return (<ProductAPI>dataSources.productApi).getRelatedProducts(id);
},
async facets(product: Product, _args, { dataSources }) {
const keywords = await (<KeywordAPI>(
dataSources.keywordApi
)).getProductKeywords(product.id);
const colors = [];
const types = [];
keywords.forEach((keyword) => {
if (keyword.type === 'COLOR') {
colors.push(keyword.value);
} else if (keyword.value === 'typeillustration') {
types.push('illustration');
} else if (keyword.value === 'typephotography') {
types.push('photograph');
}
});
const isRepeating = isRepeatingPattern(product);
if (isRepeating) {
types.push('repeating-pattern');
}
const orientations = isRepeating
? [
Orientation[Orientation.LANDSCAPE],
Orientation[Orientation.STANDING],
Orientation[Orientation.SQUARE],
]
: [getOrientationString(product.fields)];
return [
{ attribute: 'color', values: colors },
{ attribute: 'orientation', values: orientations },
{ attribute: 'type', values: types },
];
},
};
const PrintProduct = {
+6
View File
@@ -239,6 +239,11 @@ type Copyright {
products: [Product]
}
type Facet {
attribute: String,
values: [String]
}
type Product {
id: ID!
stockid: Int
@@ -261,6 +266,7 @@ type Product {
wallpaperTypes: [ProductWallpaperType]!
fields: ProductFields
orientation: Orientation
facets: [Facet]
"""
Will be single values return in later release
"""
+1 -1
View File
@@ -6,5 +6,5 @@ export enum KeywordType {
export interface Keyword {
id: number;
value: string;
type: KeywordType;
type: string;
}
+6
View File
@@ -136,6 +136,11 @@ export interface Copyright {
products: Promise<Array<Product>>;
}
export interface Facet {
attribute: string;
value: string[];
}
export interface Product {
id: number;
stockid: number;
@@ -152,6 +157,7 @@ export interface Product {
blacklisting: [ProductBlacklist];
type: [ProductType];
fields: ProductFields;
facets: Facet[];
}
// TODO: test stock products to see what props can be mandatory