mock-data.service.ts 14 KB

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