@nuskin/ns-shop 7.5.6-mdigi-4997.2 → 7.6.0-brw-988.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuskin/ns-shop",
3
- "version": "7.5.6-mdigi-4997.2",
3
+ "version": "7.6.0-brw-988.1",
4
4
  "description": "The description that will amaze and astound your audience when they read it",
5
5
  "main": "src/shop.js",
6
6
  "scripts": {
@@ -395,7 +395,7 @@ function _assembleChildSkus(requestData, options) {
395
395
  if (personalOffer !== null) {
396
396
  personalOffer = JSON.parse(personalOffer);
397
397
  productWithVariant = personalOffer.products.filter((sku) =>
398
- sku.type.toLowerCase() === 'bundle' && sku.variantSelected !== null && sku.sku === productSku
398
+ sku.type === 'bundle' && sku.variantSelected !== null && sku.sku === productSku
399
399
  )
400
400
  }
401
401
 
@@ -411,18 +411,18 @@ function _assembleChildSkus(requestData, options) {
411
411
  }
412
412
  if (personalOffer != null && selectedVariants && cs.skuId != selectedVariants[cs.productId] && (cs.type !== "BUNDLE" && cs.type !== "OPTIONAL")) return null
413
413
  const childSku =
414
- (personalOffer != null && selectedVariants && selectedVariants[cs.productId] && config && config.MySite_graphql_active)
415
- ? {
416
- productId: cs.productId,
417
- skuId: selectedVariants[cs.productId],
418
- type: cs.type,
419
- availableChannels: cs.availableChannels
420
- } : {
421
- productId: cs.productId,
422
- skuId: cs.skuId,
423
- type: cs.type,
424
- availableChannels: cs.availableChannels
425
- }
414
+ (personalOffer != null && selectedVariants && selectedVariants[cs.productId] && config.MySite_graphql_active)
415
+ ? {
416
+ productId: cs.productId,
417
+ skuId: selectedVariants[cs.productId],
418
+ type: cs.type,
419
+ availableChannels: cs.availableChannels
420
+ } : {
421
+ productId: cs.productId,
422
+ skuId: cs.skuId,
423
+ type: cs.type,
424
+ availableChannels: cs.availableChannels
425
+ }
426
426
  if (cs.inventory) {
427
427
  childSku.skuQuantity = cs.skuQuantity;
428
428
  childSku.backOrdered = cs.inventory.backOrdered;
@@ -4,7 +4,8 @@ import {ProductResultsEnum} from "./productResultsEnum.js";
4
4
  import OrderType from "./orderType.js";
5
5
  import {SolrProductAdaptor} from "./solrProductAdaptor.js";
6
6
  import {SolrQueryUrlService} from "./solrQueryUrlService.js";
7
- import {getCachedConfigField} from '@nuskin/configuration-sdk';
7
+ import {getCachedConfigField, getCachedConfiguration} from '@nuskin/configuration-sdk';
8
+ import {allPredictiveSearch, predictiveSearch} from "./productSearchAPIQueryService.js"
8
9
 
9
10
  /**
10
11
  *
@@ -436,120 +437,134 @@ let SolrQueryService = function() {
436
437
  productResultType, searchTerm, productSearchFields, language, market, searchByOrderType ? rows*5 : rows, start
437
438
  );
438
439
 
439
- // Search for language nodes for the given market that match the search term.
440
- let queryResponsePromise = $.ajax({
441
- url: solrUrl,
442
- type: "GET",
443
- contentType: 'application/json; charset=utf-8',
444
- headers: {
445
- "client_id": getCachedConfigField('solrClientId'),
446
- "client_secret": getCachedConfigField('solrClientSecret')
447
- },
448
- timeout: SolrTimeout,
449
- success: function () {
450
- //console.log("Search success!");
451
- },
452
- error: function (xmlHttpRequest, textStatus) {
453
- deferred.reject(textStatus);
454
- //console.log('Search failed!');
455
- }
456
- });
440
+ const {active, MySite_search_API_Base_URLs} = getCachedConfiguration('Equinox_Markets');
457
441
 
458
- queryResponsePromise.then(function(responseData) {
459
- let retValue;
460
- let docs, totalNumFound/*, responseHeader*/;
442
+ if(active && MySite_search_API_Base_URLs !== '') {
461
443
 
462
- if (typeof(responseData) === "string") {
463
- docs = $.parseJSON(responseData).response.docs;
464
- totalNumFound = $.parseJSON(responseData).response.numFound;
465
- // responseHeader = $.parseJSON(responseData).responseHeader;
466
- } else {
467
- docs = responseData.response.docs;
468
- totalNumFound = responseData.response.numFound;
469
- // responseHeader = responseData.responseHeader;
470
- }
444
+ predictiveSearch(deferred, {
445
+ searchTerm,
446
+ rows,
447
+ language,
448
+ market,
449
+ page: start/rows
450
+ })
471
451
 
472
- if (searchByOrderType) {
473
- solrUrl = SolrQueryUrlService.getProductsByRoots(docs, language, market, rows, 0, orderTypes);
474
- let _orderTypes = orderTypes;
475
- if (solrUrl.url) {
476
- let productDocuments = [];
477
- queryResponsePromise = $.ajax({
478
- url: solrUrl.url,
479
- type: "POST",
480
- contentType: 'application/json; charset=utf-8',
481
- headers: {
482
- "client_id": getCachedConfigField('solrClientId'),
483
- "client_secret":getCachedConfigField('solrClientSecret')
484
- },
485
- timeout: SolrTimeout,
486
- data: JSON.stringify(solrUrl.postData),
487
- success: function (responseData) {
488
- if (typeof(responseData) === "string") {
489
- docs = $.parseJSON(responseData).response.docs;
490
- totalNumFound = $.parseJSON(responseData).response.numFound;
491
- }
492
- else {
493
- docs = responseData.response.docs;
494
- totalNumFound = responseData.response.numFound;
495
- }
452
+ } else {
453
+ // Search for language nodes for the given market that match the search term.
454
+ let queryResponsePromise = $.ajax({
455
+ url: solrUrl,
456
+ type: "GET",
457
+ contentType: 'application/json; charset=utf-8',
458
+ headers: {
459
+ "client_id": getCachedConfigField('solrClientId'),
460
+ "client_secret": getCachedConfigField('solrClientSecret')
461
+ },
462
+ timeout: SolrTimeout,
463
+ success: function () {
464
+ //console.log("Search success!");
465
+ },
466
+ error: function (xmlHttpRequest, textStatus) {
467
+ deferred.reject(textStatus);
468
+ //console.log('Search failed!');
469
+ }
470
+ });
496
471
 
497
- // Wrap the products in an adaptor for ease of use.
498
- for (let i = 0; i < docs.length; i++) {
499
- //ONLY RETURN PRODUCTS THAT HAVE A MARKET
500
- if (docs[i]._childDocuments_) {
501
- for (let m = 0; m < docs[i]._childDocuments_.length; m++) {
502
- if ((docs[i]._childDocuments_[m].path_exact === "product/" + docs[i].sku + '/' + market.toUpperCase()) &&
503
- !docs[i]._childDocuments_[m].excludeFromSearch) {
472
+ queryResponsePromise.then(function(responseData) {
473
+ let retValue;
474
+ let docs, totalNumFound/*, responseHeader*/;
504
475
 
505
- if (!skuExistsInProductArray(productDocuments, docs[i].sku.toString())) {
506
- let prodAdaptor = new SolrProductAdaptor(docs[i]);
507
- let marketChild = prodAdaptor.getMarketChild(market);
508
- let languageChild = prodAdaptor.getLanguageChild(language);
509
- if (existsInArray(marketChild.getOrderType(), _orderTypes)) {
510
- productDocuments.push({
511
- sku: docs[i].sku,
512
- fullImage: marketChild.getFullImageUrl(),
513
- name: languageChild.getTitle(),
514
- _root_: "product/" + docs[i].sku,
515
- isExclusive: languageChild.getIsExclusive()
516
- });
476
+ if (typeof(responseData) === "string") {
477
+ docs = $.parseJSON(responseData).response.docs;
478
+ totalNumFound = $.parseJSON(responseData).response.numFound;
479
+ // responseHeader = $.parseJSON(responseData).responseHeader;
480
+ } else {
481
+ docs = responseData.response.docs;
482
+ totalNumFound = responseData.response.numFound;
483
+ // responseHeader = responseData.responseHeader;
484
+ }
485
+
486
+ if (searchByOrderType) {
487
+ solrUrl = SolrQueryUrlService.getProductsByRoots(docs, language, market, rows, 0, orderTypes);
488
+ let _orderTypes = orderTypes;
489
+ if (solrUrl.url) {
490
+ let productDocuments = [];
491
+ queryResponsePromise = $.ajax({
492
+ url: solrUrl.url,
493
+ type: "POST",
494
+ contentType: 'application/json; charset=utf-8',
495
+ headers: {
496
+ "client_id": getCachedConfigField('solrClientId'),
497
+ "client_secret":getCachedConfigField('solrClientSecret')
498
+ },
499
+ timeout: SolrTimeout,
500
+ data: JSON.stringify(solrUrl.postData),
501
+ success: function (responseData) {
502
+ if (typeof(responseData) === "string") {
503
+ docs = $.parseJSON(responseData).response.docs;
504
+ totalNumFound = $.parseJSON(responseData).response.numFound;
505
+ }
506
+ else {
507
+ docs = responseData.response.docs;
508
+ totalNumFound = responseData.response.numFound;
509
+ }
510
+
511
+ // Wrap the products in an adaptor for ease of use.
512
+ for (let i = 0; i < docs.length; i++) {
513
+ //ONLY RETURN PRODUCTS THAT HAVE A MARKET
514
+ if (docs[i]._childDocuments_) {
515
+ for (let m = 0; m < docs[i]._childDocuments_.length; m++) {
516
+ if ((docs[i]._childDocuments_[m].path_exact === "product/" + docs[i].sku + '/' + market.toUpperCase()) &&
517
+ !docs[i]._childDocuments_[m].excludeFromSearch) {
518
+
519
+ if (!skuExistsInProductArray(productDocuments, docs[i].sku.toString())) {
520
+ let prodAdaptor = new SolrProductAdaptor(docs[i]);
521
+ let marketChild = prodAdaptor.getMarketChild(market);
522
+ let languageChild = prodAdaptor.getLanguageChild(language);
523
+ if (existsInArray(marketChild.getOrderType(), _orderTypes)) {
524
+ productDocuments.push({
525
+ sku: docs[i].sku,
526
+ fullImage: marketChild.getFullImageUrl(),
527
+ name: languageChild.getTitle(),
528
+ _root_: "product/" + docs[i].sku,
529
+ isExclusive: languageChild.getIsExclusive()
530
+ });
531
+ }
517
532
  }
518
533
  }
519
534
  }
520
535
  }
521
536
  }
522
- }
523
537
 
524
- let retValue = {
525
- products: productDocuments,
526
- totalNumFound: totalNumFound
527
- };
538
+ let retValue = {
539
+ products: productDocuments,
540
+ totalNumFound: totalNumFound
541
+ };
528
542
 
529
- deferred.resolve(retValue);
530
- },
531
- error: function (xmlHttpRequest, textStatus) {
532
- deferred.reject(textStatus);
533
- //console.log('Search failed!');
534
- }
535
- });
543
+ deferred.resolve(retValue);
544
+ },
545
+ error: function (xmlHttpRequest, textStatus) {
546
+ deferred.reject(textStatus);
547
+ //console.log('Search failed!');
548
+ }
549
+ });
550
+ } else {
551
+ retValue = {
552
+ products: docs,
553
+ totalNumFound: totalNumFound
554
+ };
555
+ deferred.resolve(retValue);
556
+ }
536
557
  } else {
537
558
  retValue = {
538
559
  products: docs,
539
560
  totalNumFound: totalNumFound
540
561
  };
562
+
541
563
  deferred.resolve(retValue);
542
564
  }
543
- } else {
544
- retValue = {
545
- products: docs,
546
- totalNumFound: totalNumFound
547
- };
548
-
549
- deferred.resolve(retValue);
550
- }
551
- });
552
-
565
+ });
566
+ }
567
+
553
568
  return deferred;
554
569
  }
555
570
 
@@ -573,113 +588,127 @@ let SolrQueryService = function() {
573
588
  productResultType, searchTerm, productSearchFields, language, market, rows, start
574
589
  );
575
590
 
576
- // Search for language nodes for the given market that match the search term.
577
- let queryResponsePromise = $.ajax({
578
- url: solrUrl,
579
- type: "GET",
580
- contentType: 'application/json; charset=utf-8',
581
- headers: {
582
- "client_id": getCachedConfigField('solrClientId'),
583
- "client_secret": getCachedConfigField('solrClientSecret')
584
- },
585
- timeout: SolrTimeout,
586
- success: function () {
591
+ const {active, MySite_search_API_Base_URLs} = getCachedConfiguration('Equinox_Markets');
592
+
593
+ if(active && MySite_search_API_Base_URLs !== '') {
594
+
595
+ allPredictiveSearch(deferred, {
596
+ searchTerm,
597
+ language,
598
+ market
599
+ })
600
+
601
+ } else {
602
+
603
+ // Search for language nodes for the given market that match the search term.
604
+ let queryResponsePromise = $.ajax({
605
+ url: solrUrl,
606
+ type: "GET",
607
+ contentType: 'application/json; charset=utf-8',
608
+ headers: {
609
+ "client_id": getCachedConfigField('solrClientId'),
610
+ "client_secret": getCachedConfigField('solrClientSecret')
611
+ },
612
+ timeout: SolrTimeout,
613
+ success: function () {
587
614
  //console.log("Search success!");
588
- },
589
- error: function (xmlHttpRequest, textStatus) {
590
- deferred.reject(textStatus);
615
+ },
616
+ error: function (xmlHttpRequest, textStatus) {
617
+ deferred.reject(textStatus);
591
618
  //console.log('Search failed!');
592
- }
593
- });
619
+ }
620
+ });
594
621
 
595
- queryResponsePromise.then(function(responseData){
596
- let matchingRootProducts, totalNumFound, responseHeader;
622
+ queryResponsePromise.then(function(responseData){
623
+ let matchingRootProducts, totalNumFound, responseHeader;
597
624
 
598
- if (typeof(responseData) === "string") {
599
- matchingRootProducts = $.parseJSON(responseData).response.docs;
600
- totalNumFound = $.parseJSON(responseData).response.numFound;
601
- responseHeader = $.parseJSON(responseData).responseHeader;
602
- } else {
603
- matchingRootProducts = responseData.response.docs;
604
- totalNumFound = responseData.response.numFound;
605
- responseHeader = responseData.responseHeader;
606
- }
625
+ if (typeof(responseData) === "string") {
626
+ matchingRootProducts = $.parseJSON(responseData).response.docs;
627
+ totalNumFound = $.parseJSON(responseData).response.numFound;
628
+ responseHeader = $.parseJSON(responseData).responseHeader;
629
+ } else {
630
+ matchingRootProducts = responseData.response.docs;
631
+ totalNumFound = responseData.response.numFound;
632
+ responseHeader = responseData.responseHeader;
633
+ }
607
634
 
608
- // If the zkConnected header is true, we count on the data returned from the query being accurate.
609
- // If it's false, we will re-try the Solr query so that we are sure we have the most up-to-date information
610
- // (We only try again once, which is what sets the retrySolrRequest variable)
611
- if (responseHeader.zkConnected || !retrySolrRequest) {
635
+ // If the zkConnected header is true, we count on the data returned from the query being accurate.
636
+ // If it's false, we will re-try the Solr query so that we are sure we have the most up-to-date information
637
+ // (We only try again once, which is what sets the retrySolrRequest variable)
638
+ if (responseHeader.zkConnected || !retrySolrRequest) {
612
639
  // eslint-disable-next-line
613
640
  // Example solrUrl: http://dev-gateway-mulesoft-21:8080/mec/solr/oak/select?fl=*,[child%20parentFilter=path_exact:product/????????%20childFilter=path_exact:product/????????/en],[child%20parentFilter=path_exact:product/????????%20childFilter=path_exact:product/????????/US]&q=path_exact:product/01003611&rows=5&wt=json
614
641
  // Always start at 0 so we get all the rows we are asking for.
615
- solrUrl = SolrQueryUrlService.getProductsByRoots(matchingRootProducts, language, market, rows, 0, orderTypes);
616
-
617
- if (solrUrl.url) {
618
- let productDocuments = [];
619
-
620
- // Return the complete records for the given matches
621
- queryResponsePromise = $.ajax({
622
- url: solrUrl.url,
623
- type: "POST",
624
- contentType: 'application/json; charset=utf-8',
625
- headers: {
626
- "client_id": getCachedConfigField('solrClientId'),
627
- "client_secret": getCachedConfigField('solrClientSecret')
628
- },
629
- data: JSON.stringify(solrUrl.postData),
630
- timeout: SolrTimeout,
631
- success: function (responseData) {
632
- let docs;
633
-
634
- if (typeof(responseData) === "string") {
635
- docs = $.parseJSON(responseData).response.docs;
636
- }
637
- else {
638
- docs = responseData.response.docs;
639
- }
642
+ solrUrl = SolrQueryUrlService.getProductsByRoots(matchingRootProducts, language, market, rows, 0, orderTypes);
643
+
644
+ if (solrUrl.url) {
645
+ let productDocuments = [];
646
+
647
+ // Return the complete records for the given matches
648
+ queryResponsePromise = $.ajax({
649
+ url: solrUrl.url,
650
+ type: "POST",
651
+ contentType: 'application/json; charset=utf-8',
652
+ headers: {
653
+ "client_id": getCachedConfigField('solrClientId'),
654
+ "client_secret": getCachedConfigField('solrClientSecret')
655
+ },
656
+ data: JSON.stringify(solrUrl.postData),
657
+ timeout: SolrTimeout,
658
+ success: function (responseData) {
659
+ let docs;
660
+
661
+ if (typeof(responseData) === "string") {
662
+ docs = $.parseJSON(responseData).response.docs;
663
+ }
664
+ else {
665
+ docs = responseData.response.docs;
666
+ }
640
667
 
641
- for (let i = 0; i < matchingRootProducts.length; i++) {
642
- matchingRootProducts[i].path_exact = matchingRootProducts[i]._root_[0];
643
- }
668
+ for (let i = 0; i < matchingRootProducts.length; i++) {
669
+ matchingRootProducts[i].path_exact = matchingRootProducts[i]._root_[0];
670
+ }
644
671
 
645
- // Wrap the products in an adaptor for ease of use.
646
- for (let i = 0; i < docs.length; i++) {
672
+ // Wrap the products in an adaptor for ease of use.
673
+ for (let i = 0; i < docs.length; i++) {
647
674
  //ONLY RETURN PRODUCTS THAT HAVE A MARKET
648
- if (docs[i]._childDocuments_) {
649
- for (let m = 0; m < docs[i]._childDocuments_.length; m++) {
650
- if ((docs[i]._childDocuments_[m].path_exact === "product/" + docs[i].sku + '/' + market.toUpperCase()) &&
675
+ if (docs[i]._childDocuments_) {
676
+ for (let m = 0; m < docs[i]._childDocuments_.length; m++) {
677
+ if ((docs[i]._childDocuments_[m].path_exact === "product/" + docs[i].sku + '/' + market.toUpperCase()) &&
651
678
  !docs[i]._childDocuments_[m].excludeFromSearch) {
652
679
 
653
- if (!skuExistsInSolrProductAdaptorArray(productDocuments, docs[i].sku.toString())) {
654
- const match = matchingRootProducts.find(root => root.path_exact === docs[i].path_exact) || null;
655
- const score = match ? match.score : 0;
656
- productDocuments.push(new SolrProductAdaptor(docs[i], score));
680
+ if (!skuExistsInSolrProductAdaptorArray(productDocuments, docs[i].sku.toString())) {
681
+ const match = matchingRootProducts.find(root => root.path_exact === docs[i].path_exact) || null;
682
+ const score = match ? match.score : 0;
683
+ productDocuments.push(new SolrProductAdaptor(docs[i], score));
684
+ }
657
685
  }
658
686
  }
659
687
  }
660
688
  }
661
- }
662
689
 
663
- let retValue = {
664
- products: productDocuments,
665
- totalNumFound: totalNumFound
666
- };
690
+ let retValue = {
691
+ products: productDocuments,
692
+ totalNumFound: totalNumFound
693
+ };
667
694
 
668
- deferred.resolve(retValue);
669
- },
670
- error: function (xmlHttpRequest, textStatus) {
671
- deferred.reject(textStatus);
695
+ deferred.resolve(retValue);
696
+ },
697
+ error: function (xmlHttpRequest, textStatus) {
698
+ deferred.reject(textStatus);
672
699
  //console.log('Search failed!');
673
- }
674
- });
675
- }
676
- else {
677
- deferred.resolve(undefined);
700
+ }
701
+ });
702
+ }
703
+ else {
704
+ deferred.resolve(undefined);
705
+ }
706
+ } else {
707
+ getProductsBySearchFields(productResultType, searchTerm, productSearchFields, language, market, rows, start, orderTypes, deferred)
678
708
  }
679
- } else {
680
- getProductsBySearchFields(productResultType, searchTerm, productSearchFields, language, market, rows, start, orderTypes, deferred)
681
- }
682
- });
709
+ });
710
+
711
+ }
683
712
 
684
713
  return deferred;
685
714
  }
@@ -0,0 +1,521 @@
1
+ import $ from '@nuskin/nuskinjquery';
2
+ import { getCachedConfiguration } from '@nuskin/configuration-sdk';
3
+ import { SolrProductAdaptor } from './solrProductAdaptor';
4
+ import { CustomerTypes } from '@nuskin/ns-product-lib';
5
+
6
+ // const {$} = require('@nuskin/nuskinjquery');
7
+ // const { getCachedConfiguration } = require('@nuskin/configuration-sdk');
8
+ // const { SolrProductAdaptor } = require('./solrProductAdaptor');
9
+ // const { CustomerTypes } = require('@nuskin/ns-product-lib');
10
+
11
+ let baseScore = 5;
12
+ const searchQuery = `
13
+ query ProductSearch(
14
+ $market: String!
15
+ $language: String!
16
+ $searchTerm: String
17
+ $sizeOpt: Int
18
+ $pageOpt: Int
19
+ ) {
20
+ productSearch(
21
+ sizeOpt: $sizeOpt
22
+ language: $language
23
+ market: $market
24
+ searchTerm: $searchTerm
25
+ pageOpt: $pageOpt
26
+ ) {
27
+ ProductContent {
28
+ products {
29
+ error {
30
+ errors
31
+ }
32
+ title
33
+ productImages {
34
+ url
35
+ }
36
+ variants {
37
+ title
38
+ sku
39
+ productImages {
40
+ url
41
+ alt
42
+ thumbnail
43
+ }
44
+ isExclusive
45
+ availableChannels
46
+ customerTypes
47
+ description
48
+ searchKeywords
49
+ }
50
+ bundle {
51
+ purchaseTypes {
52
+ buyOnce
53
+ subscription
54
+ }
55
+ customerTypes
56
+ }
57
+ id
58
+ }
59
+ }
60
+ pageableInfo {
61
+ page
62
+ size
63
+ count
64
+ totalCount
65
+ }
66
+ }
67
+ }`;
68
+ const predictiveQuery = `
69
+ query PredictiveSearch($language: String, $market: String, $page: Int, $searchTerm: String, $size: Int) {
70
+ predictiveSearch(
71
+ language: $language
72
+ market: $market
73
+ page: $page
74
+ searchTerm: $searchTerm
75
+ size: $size
76
+ ) {
77
+ didYouMean
78
+ pagination {
79
+ count
80
+ page
81
+ size
82
+ totalCount
83
+ }
84
+ products {
85
+ id
86
+ isExclusive
87
+ sku
88
+ slug
89
+ title
90
+ price {
91
+ retail
92
+ retailSales
93
+ wholesale
94
+ wholesaleSales
95
+ }
96
+ productImages {
97
+ url
98
+ thumbnail
99
+ alt
100
+ }
101
+ status {
102
+ status
103
+ inventory
104
+ isBackordered
105
+ }
106
+ }
107
+ }
108
+ }`;
109
+
110
+ /**
111
+ * Checks if the input string follows a specific SKU format.
112
+ * @param {string} str - The input string to be checked.
113
+ * @returns {boolean} Returns true if the input string is a valid SKU format, otherwise false.
114
+ */
115
+ function isSkuFormat(str) {
116
+ return typeof str === 'string' && str.length === 8 && /^[a-zA-Z0-9]+$/.test(str);
117
+ }
118
+
119
+ /**
120
+ * Sends a query to the product-search graphql
121
+ * @returns {Promise} A promise that resolves with the query response.
122
+ */
123
+ function sendQuery(query, variables, page) {
124
+ if (page) {
125
+ variables.pageOpt = page;
126
+ }
127
+
128
+ const { MySite_search_API_Base_URLs } = getCachedConfiguration('Equinox_Markets');
129
+
130
+ const data = JSON.stringify({
131
+ query: query,
132
+ variables: variables
133
+ });
134
+
135
+ return $.ajax({
136
+ url: MySite_search_API_Base_URLs,
137
+ type: "POST",
138
+ contentType: 'application/json; charset=utf-8',
139
+ data: data
140
+ });
141
+ }
142
+
143
+ /**
144
+ * Extracts relevant information from the product search response.
145
+ * @param {Object} response - The response from the product-search graphql.
146
+ * @returns {Object} An object containing products and total number found.
147
+ */
148
+ function processSearchResponse(response, options) {
149
+ let retValue = {
150
+ products: [],
151
+ totalNumFound: 0
152
+ };
153
+
154
+ const result = response.data.productSearch || {};
155
+
156
+ if (result.ProductContent && result.ProductContent.products && result.ProductContent.products.length > 0) {
157
+ let products = result.ProductContent.products;
158
+ products = processGroupByParent(products, options);
159
+
160
+ retValue = {
161
+ products,
162
+ totalNumFound: result.pageableInfo.totalCount
163
+ };
164
+ }
165
+
166
+ return retValue;
167
+ }
168
+
169
+ /**
170
+ * Extracts relevant information from the predictive search response.
171
+ * @param {Object} response - The response from the product-search graphql.
172
+ * @returns {Object} An object containing products and total number found.
173
+ */
174
+ function processPredictiveResponse(response, options) {
175
+ let retValue = {
176
+ products: [],
177
+ totalNumFound: 0
178
+ };
179
+
180
+ const result = response.data.predictiveSearch || {};
181
+
182
+ if (result.products && result.products.length > 0) {
183
+ let products = result.products;
184
+ const unfilteredProductCount = products.length;
185
+ products = processGroupByVariant(products, options);
186
+
187
+ retValue = {
188
+ products,
189
+ totalNumFound: unfilteredProductCount === result.pagination.totalCount ? products.length : result.pagination.totalCount
190
+ };
191
+ }
192
+
193
+ return retValue;
194
+ }
195
+
196
+ /**
197
+ * Processes a list of products to be grouped by parent, generating a mapped product structure.
198
+ *
199
+ * @param {Array} products - The list of products to process.
200
+ * @returns {Array} An array of mapped products with child documents for each variant.
201
+ */
202
+ function processGroupByParent(products, options) {
203
+ let mappedProducts = [];
204
+
205
+ //clean up null data (a lot exists in test environment)
206
+ products = products.filter((product) => {
207
+ if(product.error) {
208
+ console.log('Removed Products with error' , product.error)
209
+ }
210
+
211
+ return !product.error
212
+ })
213
+
214
+ products.forEach((parentProduct) => {
215
+ let mappedParentProduct = parentProduct;
216
+
217
+ if (!isSkuFormat(parentProduct.id) && parentProduct.variants && parentProduct.variants.length > 0) {
218
+ mappedParentProduct = parentProduct.variants[0];
219
+ } else {
220
+ mappedParentProduct.sku = parentProduct.id;
221
+ mappedParentProduct.variants = [];
222
+ mappedParentProduct.variants.push({ ...mappedParentProduct, sku: parentProduct.id });
223
+ }
224
+
225
+ let indexDocuments = parentProduct.variants.map((variant) => {
226
+
227
+ let orderTypes = mapOrderTypes(variant.availableChannels, variant.bundle ? variant.bundle.purchaseTypes : undefined);
228
+
229
+ return {
230
+ path_exact: `product/${variant.sku}/${options.language}`,
231
+ sku: [`${variant.sku}`],
232
+ markets: [`${options.market}`],
233
+ variantType: [""],
234
+ name: [`${variant.title}`],
235
+ shortDescription: variant.description,
236
+ longDescription: variant.description,
237
+ searchTerms: variant.searchKeywords,
238
+ isExclusive: mappedParentProduct.isExclusive || "false",
239
+ custType: mapCustomerTypes(variant.bundle ? variant.bundle.customerTypes : variant.customerTypes),
240
+ statusCode: ["10"],
241
+ orderType: Object.keys(orderTypes).filter(key => orderTypes[key]),
242
+ _root_: [`product/${mappedParentProduct.sku}`]
243
+ };
244
+ });
245
+
246
+ let mySiteIndexDocuments = parentProduct.variants.map((variant) => {
247
+
248
+ let orderTypes = mapOrderTypes(variant.availableChannels, variant.bundle ? variant.bundle.purchaseTypes : undefined);
249
+
250
+ return {
251
+ path_exact: `product/${variant.sku}/${options.market.toUpperCase()}`,
252
+ sku: [`${variant.sku}`],
253
+ searchTerms: variant.searchKeywords,
254
+ orderType: Object.keys(orderTypes).filter(key => orderTypes[key]),
255
+ custType: mapCustomerTypes(variant.bundle ? variant.bundle.customerTypes : variant.customerTypes),
256
+ statusCode: ["10"],
257
+ webEnabled: ["true"],
258
+ _root_: [`product/${mappedParentProduct.sku}`]
259
+ };
260
+ });
261
+
262
+ indexDocuments = indexDocuments.concat(mySiteIndexDocuments);
263
+
264
+ if (indexDocuments.length > 0) {
265
+ mappedProducts.push({
266
+ path_exact: `product/${mappedParentProduct.sku}`,
267
+ languages: [`${options.language.toLowerCase()}`],
268
+ markets: [`${options.market.toLowerCase()}`],
269
+ sku: [`${mappedParentProduct.sku}`],
270
+ _childDocuments_: indexDocuments
271
+ });
272
+ } else {
273
+ console.log('Invalid Product', mappedParentProduct);
274
+ }
275
+ });
276
+
277
+ console.log('Products' , mappedProducts)
278
+
279
+ return mappedProducts.map((product) => {
280
+ baseScore -= 0.01;
281
+ return new SolrProductAdaptor(product, baseScore)
282
+ });
283
+ }
284
+
285
+ /**
286
+ * Processes a list of products to be grouped by variant, generating a mapped product structure.
287
+ *
288
+ * @param {Array} products - The list of products to process.
289
+ * @returns {Array} An array of mapped products with a flattened structure.
290
+ */
291
+ function processGroupByVariant(products, options) {
292
+ let sellableProducts = products.filter((v) => {
293
+ return v.status && v.status.status && (v.status.status !== 'inactive' && v.status.status !== 'discontinued');
294
+ });
295
+
296
+ sellableProducts = sellableProducts.filter((v) => v !== undefined).map((product) => {
297
+ return {
298
+ sku: [product.sku],
299
+ fullImage: (product.productImages && product.productImages.length > 0) ? product.productImages[0].thumbnail ? product.productImages[0].thumbnail : product.productImages[0].url : '',
300
+ name: product.title || '',
301
+ _root_: `product/${product.sku}`,
302
+ isExclusive: product.isExclusive || "false",
303
+ price: product.price,
304
+ status: product.status
305
+ };
306
+ });
307
+
308
+ let exactSkuResult = sellableProducts.filter((product) => product.sku[0].toLowerCase() === options.searchTerm.toLowerCase());
309
+
310
+ if (isSkuFormat(options.searchTerm) && exactSkuResult.length > 0) {
311
+ sellableProducts = exactSkuResult
312
+ }
313
+
314
+ return sellableProducts;
315
+ }
316
+
317
+
318
+ /**
319
+ * map EQ customer types to supported product card customer types
320
+ * @param {Array<string>} eqCustomerTypes
321
+ * @returns{string} customer types
322
+ */
323
+ function mapCustomerTypes(eqCustomerTypes) {
324
+ if (!eqCustomerTypes) {
325
+ return "";
326
+ }
327
+
328
+ const custTypes = eqCustomerTypes.map(str => str.toLowerCase());
329
+
330
+ let newCustType = [];
331
+ if (custTypes.includes(CustomerTypes.BrandAffiliate)) {
332
+ newCustType.push(CustomerTypes.properties.BRAND_AFFILIATE.code);
333
+ }
334
+
335
+ if (custTypes.includes(CustomerTypes.Retail)) {
336
+ newCustType.push(CustomerTypes.properties.RETAIL.code);
337
+ }
338
+
339
+ if (custTypes.includes(CustomerTypes.Preferred) || custTypes.includes(CustomerTypes.PreferredCustomer)) {
340
+ newCustType.push(CustomerTypes.properties.PREFERRED.code);
341
+ }
342
+
343
+ return newCustType.toString().split(",")
344
+ }
345
+
346
+ /**
347
+ * Map Order Types
348
+ * @param {Array<string>} availableChannels
349
+ * @param {object} purchaseTypes
350
+ * @returns {object} orderTypes
351
+ */
352
+ function mapOrderTypes(availableChannels, purchaseTypes) {
353
+
354
+ let orderTypes = {
355
+ "adr": false,
356
+ "order": false,
357
+ "zpfc": false,
358
+ "zadp": false,
359
+ "ars": false,
360
+ "kiosk": false,
361
+ "mobile": false,
362
+ "preferred customer": false,
363
+ "retail": false,
364
+ "web": false,
365
+ "web display": false
366
+ };
367
+
368
+ if (availableChannels) {
369
+ let availableChannelsArr = Array.isArray(availableChannels) ? availableChannels : availableChannels.split(',');
370
+
371
+ availableChannelsArr.forEach(channel => {
372
+ if (channel == 'arsPhone')
373
+ orderTypes.ars = true
374
+ if (channel == 'web') {
375
+ orderTypes.order = true
376
+ orderTypes.web = true
377
+ }
378
+ if (channel == 'kiosk')
379
+ orderTypes.kiosk = true
380
+ if (channel == 'mobile')
381
+ orderTypes.mobile = true
382
+ if (channel == 'subscription')
383
+ orderTypes.adr = true
384
+ });
385
+ }
386
+
387
+ if (purchaseTypes && purchaseTypes.buyOnce) {
388
+ orderTypes.web = true
389
+ orderTypes.order = true
390
+ }
391
+ if (purchaseTypes && purchaseTypes.subscription) {
392
+ orderTypes.adr = true
393
+ }
394
+
395
+ return orderTypes;
396
+ }
397
+
398
+
399
+ /**
400
+ * Performs a product search to the product-search graphql
401
+ * @param {Promise} deferred - A promise object that resolves when the search is complete.
402
+ * @param {Object} options - The search options including searchTerm, rows, language, market.
403
+ */
404
+ function productSearch(deferred, options) {
405
+
406
+ let allProducts = [];
407
+ let currentPage = 1;
408
+ const variables = {
409
+ "language": options.language,
410
+ "market": options.market,
411
+ "sizeOpt": options.rows,
412
+ "pageOpt": currentPage,
413
+ "searchTerm": options.searchTerm
414
+ };
415
+
416
+ function fetchNextPage() {
417
+ sendQuery(searchQuery, variables, currentPage)
418
+ .then((qresp) => processSearchResponse(qresp, options))
419
+ .then((result) => {
420
+ allProducts = allProducts.concat(result.products);
421
+ if ((currentPage * options.rows) < result.totalNumFound) {
422
+ currentPage++;
423
+ fetchNextPage();
424
+ } else {
425
+ baseScore = 5;
426
+ deferred.resolve({
427
+ products: allProducts,
428
+ totalNumFound: result.totalNumFound
429
+ });
430
+ }
431
+ })
432
+ .catch((error) => {
433
+ console.error(error);
434
+ deferred.reject(error);
435
+ });
436
+ }
437
+
438
+ fetchNextPage();
439
+ }
440
+
441
+ /**
442
+ * Performs a product search to the product-search graphql
443
+ * @param {Promise} deferred - A promise object that resolves when the search is complete.
444
+ * @param {Object} options - The search options including searchTerm, rows, language, market.
445
+ */
446
+ function predictiveSearch(deferred, options, page) {
447
+ const variables = {
448
+ "language": options.language,
449
+ "market": options.market,
450
+ "searchTerm": options.searchTerm,
451
+ "page": page ? page : options.page ? options.page : 1,
452
+ "size": options.rows || 6
453
+ }
454
+
455
+ sendQuery(predictiveQuery, variables)
456
+ .then((qresult) => processPredictiveResponse(qresult, options))
457
+ .then((result) => {
458
+ deferred.resolve({
459
+ products: result.products,
460
+ totalNumFound: result.totalNumFound
461
+ });
462
+ })
463
+ .catch((error) => {
464
+ console.error(error);
465
+ deferred.reject(error);
466
+ });
467
+ }
468
+
469
+ /**
470
+ * Performs multiple predictive searches to the product-search graphql to retrieve all results for the term
471
+ * @param {Promise} deferred - A promise object that resolves when the search is complete.
472
+ * @param {Object} options - The search options including searchTerm, rows, language, market.
473
+ */
474
+ function allPredictiveSearch(deferred, options) {
475
+
476
+ let allProducts = [];
477
+ let currentPage = 1;
478
+ let variables = {
479
+ "language": options.language,
480
+ "market": options.market,
481
+ "searchTerm": options.searchTerm,
482
+ "page": currentPage,
483
+ "size": 50
484
+ };
485
+
486
+ function fetchNextPage() {
487
+ sendQuery(predictiveQuery, variables)
488
+ .then((qresp) => processPredictiveResponse(qresp, options))
489
+ .then((result) => {
490
+ result.products.map((product) => {
491
+ baseScore -= 0.01;
492
+ product.searchScore = baseScore
493
+ });
494
+ allProducts = allProducts.concat(result.products);
495
+ if ((currentPage * 6) < result.totalNumFound) {
496
+ currentPage++;
497
+ variables.page = currentPage
498
+ fetchNextPage();
499
+ } else {
500
+ baseScore = 5;
501
+ deferred.resolve({
502
+ products: allProducts,
503
+ totalNumFound: result.totalNumFound
504
+ });
505
+ }
506
+ })
507
+ .catch((error) => {
508
+ console.error(error);
509
+ deferred.reject(error);
510
+ });
511
+ }
512
+
513
+ fetchNextPage();
514
+ }
515
+
516
+ export {
517
+ allPredictiveSearch,
518
+ predictiveSearch,
519
+ productSearch,
520
+ isSkuFormat
521
+ };
@@ -44,7 +44,7 @@ let productSearchService = function() {
44
44
  productFields.push(ProductSearchFieldsEnum.USAGE);
45
45
  productFields.push(ProductSearchFieldsEnum.KEY_INGREDIENTS);
46
46
  }
47
- if (typeof(limit) === "undefined"|| limit === null)
47
+ if (typeof(limit) === "undefined" || limit === null)
48
48
  limit = 6;
49
49
  if (typeof(market) === "undefined" || market === null) {
50
50
  market = util.countryCode;
@@ -255,46 +255,60 @@ let productSearchService = function() {
255
255
  let skuArray = [];
256
256
 
257
257
  for (let i = 0; i < products.length; i++) {
258
- skuArray.push(products[i].getSku());
258
+ if (products[i].sku && products[i].sku.length > 0) {
259
+ skuArray.push(products[i].sku[0]);
260
+ } else {
261
+ skuArray.push(products[i].getSku());
262
+ }
259
263
  }
260
264
 
261
265
  if (skuArray.length > 0) {
262
266
  let productResults = [];
263
267
  let marketChild;
268
+ const addProductData = (productData) => {
269
+ if (productData.sku && Array.isArray(productData.sku)) {
270
+ productData.sku = productData.sku[0]
271
+ }
272
+ let addProduct = new Product(productData);
273
+
274
+ const siteUrl = UrlService.getSiteUrl();
275
+ const isEdge = BrowserDetection.isEdge();
276
+ const isFirefox = BrowserDetection.isFirefox();
277
+
278
+ // set the base url for images, etc
279
+ addProduct.setBaseUrl(siteUrl);
280
+
281
+ // fix image paths for non-edge/firefox
282
+ if (!isEdge && !isFirefox) {
283
+ if (addProduct.fullImage && !addProduct.fullImage.includes("media.nuskin.com/transform")) {
284
+ addProduct.fullImage = addProduct.fullImage
285
+ ? `${addProduct.fullImage.split("?")[0]}?format=pjpg`
286
+ : undefined;
287
+ addProduct.thumbnail = addProduct.thumbnail
288
+ ? `${addProduct.thumbnail.split("?")[0]}?format=pjpg`
289
+ : undefined;
290
+ }
291
+ }
292
+
293
+ // set price type
294
+ const priceType = getCachedConfigField('showWholeSalePricing') && !AccountManager.isLoggedIn()
295
+ ? PriceType.WWHL
296
+ : PriceType.RTL;
297
+ addProduct.setPriceAndPvFromType(priceType);
298
+ productResults.push(addProduct);
299
+ }
264
300
  for (let i = 0; i < products.length; i++) {
265
- marketChild = products[i].getMarketChild(market);
266
- if (searchedProductIsBase(products[i], productResultTypes) || (marketChild.getPurchasable() &&
267
- existsInArray(marketChild.getOrderType(), orderTypes))) {
268
- let productData = createDataFromSolrSearch(products[i], market, language);
269
- if (productData) {
270
- let addProduct = new Product(productData);
271
-
272
- const siteUrl = UrlService.getSiteUrl();
273
- const isEdge = BrowserDetection.isEdge();
274
- const isFirefox = BrowserDetection.isFirefox();
275
-
276
- // set the base url for images, etc
277
- addProduct.setBaseUrl(siteUrl);
278
-
279
- // fix image paths for non-edge/firefox
280
- if (!isEdge && !isFirefox) {
281
- if (addProduct.fullImage && !addProduct.fullImage.includes("media.nuskin.com/transform")) {
282
- addProduct.fullImage = addProduct.fullImage
283
- ? `${addProduct.fullImage.split("?")[0]}?format=pjpg`
284
- : undefined;
285
- addProduct.thumbnail = addProduct.thumbnail
286
- ? `${addProduct.thumbnail.split("?")[0]}?format=pjpg`
287
- : undefined;
288
- }
301
+ if (products[i].getMarketChild) {
302
+ marketChild = products[i].getMarketChild(market);
303
+ if (searchedProductIsBase(products[i], productResultTypes) || (marketChild.getPurchasable() &&
304
+ existsInArray(marketChild.getOrderType(), orderTypes))) {
305
+ let productData = createDataFromSolrSearch(products[i], market, language);
306
+ if (productData) {
307
+ addProductData(productData)
289
308
  }
290
-
291
- // set price type
292
- const priceType = getCachedConfigField('showWholeSalePricing') && !AccountManager.isLoggedIn()
293
- ? PriceType.WWHL
294
- : PriceType.RTL;
295
- addProduct.setPriceAndPvFromType(priceType);
296
- productResults.push(addProduct);
297
309
  }
310
+ } else {
311
+ addProductData(products[i])
298
312
  }
299
313
  }
300
314
  productResults = sortProducts(productResults, sortOrder, productFieldAsText(sortField));
@@ -636,6 +650,28 @@ let productSearchService = function() {
636
650
  }
637
651
  }
638
652
 
653
+ if (basicProduct.price) {
654
+ productData.priceMap = {
655
+ RTL: basicProduct.price.retail,
656
+ WRTL: basicProduct.price.retail,
657
+ WHL: basicProduct.price.wholesale,
658
+ WWHL: basicProduct.price.wholesale
659
+ }
660
+ }
661
+
662
+ if (basicProduct.status) {
663
+ switch (basicProduct.status) {
664
+ case "discontinued":
665
+ case "inactive":
666
+ case "bundleOnly":
667
+ productData.status = "NOT_RELEASED_FOR_SALE"
668
+ break;
669
+ default:
670
+ productData.status = "RELEASED_FOR_SALE"
671
+ break;
672
+ }
673
+ }
674
+
639
675
  return new Product(productData);
640
676
  }
641
677