mock-data.service.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. import * as faker from 'faker/locale/en_GB';
  2. import * as fs from 'fs-extra';
  3. import gql from 'graphql-tag';
  4. import * as path from 'path';
  5. import {
  6. AddOptionGroupToProduct,
  7. AddOptionGroupToProductVariables,
  8. Asset,
  9. CreateFacet,
  10. CreateFacetValueWithFacetInput,
  11. CreateFacetVariables,
  12. CreateProduct,
  13. CreateProductOptionGroup,
  14. CreateProductOptionGroupVariables,
  15. CreateProductVariables,
  16. GenerateProductVariants,
  17. GenerateProductVariantsVariables,
  18. LanguageCode,
  19. ProductTranslationInput,
  20. UpdateProductVariants,
  21. UpdateProductVariantsVariables,
  22. } from 'shared/generated-types';
  23. import { CREATE_FACET } from '../../admin-ui/src/app/data/definitions/facet-definitions';
  24. import {
  25. ADD_OPTION_GROUP_TO_PRODUCT,
  26. CREATE_PRODUCT,
  27. CREATE_PRODUCT_OPTION_GROUP,
  28. GENERATE_PRODUCT_VARIANTS,
  29. UPDATE_PRODUCT_VARIANTS,
  30. } from '../../admin-ui/src/app/data/definitions/product-definitions';
  31. import { CreateAddressDto } from '../src/entity/address/address.dto';
  32. import { Channel } from '../src/entity/channel/channel.entity';
  33. import { CreateCustomerDto } from '../src/entity/customer/customer.dto';
  34. import { Customer } from '../src/entity/customer/customer.entity';
  35. import { SimpleGraphQLClient } from './simple-graphql-client';
  36. // tslint:disable:no-console
  37. /**
  38. * A service for creating mock data via the GraphQL API.
  39. */
  40. export class MockDataService {
  41. apiUrl: string;
  42. constructor(private client: SimpleGraphQLClient, private logging = true) {
  43. // make the generated results deterministic
  44. faker.seed(1);
  45. }
  46. async populateChannels(channelCodes: string[]): Promise<Channel[]> {
  47. const channels: Channel[] = [];
  48. for (const code of channelCodes) {
  49. const channel = await this.client.query<any>(gql`
  50. mutation {
  51. createChannel(code: "${code}") {
  52. id
  53. code
  54. token
  55. }
  56. }
  57. `);
  58. channels.push(channel.createChannel);
  59. this.log(`Created Channel: ${channel.createChannel.code}`);
  60. }
  61. return channels;
  62. }
  63. async populateOptions(): Promise<string> {
  64. return this.client
  65. .query<CreateProductOptionGroup, CreateProductOptionGroupVariables>(CREATE_PRODUCT_OPTION_GROUP, {
  66. input: {
  67. code: 'size',
  68. translations: [
  69. { languageCode: LanguageCode.en, name: 'Size' },
  70. { languageCode: LanguageCode.de, name: 'Größe' },
  71. ],
  72. options: [
  73. {
  74. code: 'small',
  75. translations: [
  76. { languageCode: LanguageCode.en, name: 'Small' },
  77. { languageCode: LanguageCode.de, name: 'Klein' },
  78. ],
  79. },
  80. {
  81. code: 'large',
  82. translations: [
  83. { languageCode: LanguageCode.en, name: 'Large' },
  84. { languageCode: LanguageCode.de, name: 'Groß' },
  85. ],
  86. },
  87. ],
  88. },
  89. })
  90. .then(data => {
  91. this.log('Created option group:', data.createProductOptionGroup.name);
  92. return data.createProductOptionGroup.id;
  93. });
  94. }
  95. async populateCustomers(count: number = 5): Promise<any> {
  96. for (let i = 0; i < count; i++) {
  97. const firstName = faker.name.firstName();
  98. const lastName = faker.name.lastName();
  99. const query1 = gql`
  100. mutation CreateCustomer($input: CreateCustomerInput!, $password: String) {
  101. createCustomer(input: $input, password: $password) {
  102. id
  103. emailAddress
  104. }
  105. }
  106. `;
  107. const variables1 = {
  108. input: {
  109. firstName,
  110. lastName,
  111. emailAddress: faker.internet.email(firstName, lastName),
  112. phoneNumber: faker.phone.phoneNumber(),
  113. } as CreateCustomerDto,
  114. password: 'test',
  115. };
  116. const customer: Customer | void = await this.client
  117. .query(query1, variables1)
  118. .then((data: any) => data.createCustomer as Customer, err => this.log(err));
  119. if (customer) {
  120. const query2 = gql`
  121. mutation($customerId: ID!, $input: CreateAddressInput) {
  122. createCustomerAddress(customerId: $customerId, input: $input) {
  123. id
  124. streetLine1
  125. }
  126. }
  127. `;
  128. const variables2 = {
  129. input: {
  130. fullName: `${firstName} ${lastName}`,
  131. streetLine1: faker.address.streetAddress(),
  132. city: faker.address.city(),
  133. province: faker.address.county(),
  134. postalCode: faker.address.zipCode(),
  135. country: faker.address.country(),
  136. } as CreateAddressDto,
  137. customerId: customer.id,
  138. };
  139. await this.client.query(query2, variables2).then(
  140. data => {
  141. this.log(`Created Customer ${i + 1}:`, data);
  142. return data as Customer;
  143. },
  144. err => this.log(err),
  145. );
  146. }
  147. }
  148. }
  149. async populateAssets(): Promise<Asset[]> {
  150. const fileNames = await fs.readdir(path.join(__dirname, 'assets'));
  151. const filePaths = fileNames.map(fileName => path.join(__dirname, 'assets', fileName));
  152. return this.client.uploadAssets(filePaths).then(response => {
  153. console.log(`Created ${response.createAssets.length} Assets`);
  154. return response.createAssets;
  155. });
  156. }
  157. async populateProducts(count: number = 5, optionGroupId: string, assets: Asset[]): Promise<any> {
  158. for (let i = 0; i < count; i++) {
  159. const query = CREATE_PRODUCT;
  160. const name = faker.commerce.productName();
  161. const slug = name.toLowerCase().replace(/\s+/g, '-');
  162. const description = faker.lorem.sentence();
  163. const languageCodes = [LanguageCode.en, LanguageCode.de];
  164. // get 2 (pseudo) random asset ids
  165. const randomAssets = this.shuffleArray(assets).slice(0, 2);
  166. const variables: CreateProductVariables = {
  167. input: {
  168. translations: languageCodes.map(code =>
  169. this.makeProductTranslation(code, name, slug, description),
  170. ),
  171. assetIds: randomAssets.map(a => a.id),
  172. featuredAssetId: randomAssets[0].id,
  173. },
  174. };
  175. const product = await this.client
  176. .query<CreateProduct, CreateProductVariables>(query, variables)
  177. .then(
  178. data => {
  179. this.log(`Created Product ${i + 1}:`, data.createProduct.name);
  180. return data;
  181. },
  182. err => this.log(err),
  183. );
  184. if (product) {
  185. await this.client.query<AddOptionGroupToProduct, AddOptionGroupToProductVariables>(
  186. ADD_OPTION_GROUP_TO_PRODUCT,
  187. {
  188. productId: product.createProduct.id,
  189. optionGroupId,
  190. },
  191. );
  192. const prodWithVariants = await this.makeProductVariant(product.createProduct.id);
  193. const variants = prodWithVariants.generateVariantsForProduct.variants;
  194. for (const variant of variants) {
  195. const variantEN = variant.translations[0];
  196. const variantDE = { ...variantEN };
  197. variantDE.languageCode = LanguageCode.de;
  198. variantDE.name = variantDE.name.replace(LanguageCode.en, LanguageCode.de);
  199. delete variantDE.id;
  200. variant.translations.push(variantDE);
  201. }
  202. await this.client.query<UpdateProductVariants, UpdateProductVariantsVariables>(
  203. UPDATE_PRODUCT_VARIANTS,
  204. {
  205. input: variants.map(({ id, translations, sku, price }) => ({
  206. id,
  207. translations,
  208. sku,
  209. price,
  210. })),
  211. },
  212. );
  213. }
  214. }
  215. }
  216. async populateFacets() {
  217. await this.client.query<CreateFacet, CreateFacetVariables>(CREATE_FACET, {
  218. input: {
  219. code: 'brand',
  220. translations: [
  221. {
  222. languageCode: LanguageCode.en,
  223. name: 'Brand',
  224. },
  225. {
  226. languageCode: LanguageCode.en,
  227. name: 'Marke',
  228. },
  229. ],
  230. values: this.makeFacetValues(10),
  231. },
  232. });
  233. this.log('Created "brand" Facet');
  234. }
  235. private makeFacetValues(count: number): CreateFacetValueWithFacetInput[] {
  236. return Array.from({ length: count }).map(() => {
  237. const brand = faker.company.companyName();
  238. return {
  239. code: brand.replace(/\s/g, '_'),
  240. translations: [
  241. {
  242. languageCode: LanguageCode.en,
  243. name: brand,
  244. },
  245. {
  246. languageCode: LanguageCode.de,
  247. name: brand,
  248. },
  249. ],
  250. };
  251. });
  252. }
  253. private makeProductTranslation(
  254. languageCode: LanguageCode,
  255. name: string,
  256. slug: string,
  257. description: string,
  258. ): ProductTranslationInput {
  259. return {
  260. languageCode,
  261. name: `${languageCode} ${name}`,
  262. slug: `${languageCode} ${slug}`,
  263. description: `${languageCode} ${description}`,
  264. };
  265. }
  266. private async makeProductVariant(productId: string): Promise<GenerateProductVariants> {
  267. const query = GENERATE_PRODUCT_VARIANTS;
  268. return this.client.query<GenerateProductVariants, GenerateProductVariantsVariables>(query, {
  269. productId,
  270. defaultSku: faker.random.alphaNumeric(5),
  271. defaultPrice: faker.random.number({
  272. min: 100,
  273. max: 1000,
  274. }),
  275. });
  276. }
  277. private log(...args: any[]) {
  278. if (this.logging) {
  279. console.log(...args);
  280. }
  281. }
  282. /**
  283. * Deterministacally randomize array element order. Returns a new
  284. * shuffled array and leaves the input array intact.
  285. * Using Durstenfeld shuffle algorithm.
  286. *
  287. * Source: https://stackoverflow.com/a/12646864/772859
  288. */
  289. private shuffleArray<T>(array: T[]): T[] {
  290. const clone = array.slice(0);
  291. for (let i = clone.length - 1; i > 0; i--) {
  292. const j = Math.floor((faker.random.number(1000) / 1000) * (i + 1));
  293. const temp = clone[i];
  294. clone[i] = clone[j];
  295. clone[j] = temp;
  296. }
  297. return clone;
  298. }
  299. }