Skip to content

Commit

Permalink
Merge branch 'develop' into feat/SFCC-397
Browse files Browse the repository at this point in the history
  • Loading branch information
htuzel committed Feb 18, 2025
2 parents 77a7a61 + ce9745f commit e4fece8
Show file tree
Hide file tree
Showing 17 changed files with 717 additions and 148 deletions.
77 changes: 70 additions & 7 deletions cartridges/bm_algolia/cartridge/controllers/AlgoliaBM.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ var Resource = require('dw/web/Resource');

var algoliaData = require('*/cartridge/scripts/algolia/lib/algoliaData');
var algoliaExportAPI = require('*/cartridge/scripts/algoliaExportAPI');
var algoliaIndexingService = require('*/cartridge/scripts/services/algoliaIndexingService');
const BMHelper = require('../scripts/helper/BMHelper');
var algoliaServiceHelper = require('*/cartridge/scripts/services/algoliaServiceHelper');

/**
* @description Render default template
* @returns {void} ISML.renderTemplate
*/
function start() {
const BMHelper = require('../scripts/helper/BMHelper');

var pdictValues = {
setttingsUpdateUrl: URLUtils.https('AlgoliaBM-HandleSettings'),
algoliaData: algoliaData,
Expand All @@ -30,30 +31,92 @@ function start() {
*/
function handleSettings() {
var params = request.httpParameterMap;
var applicationID = params.ApplicationID.value;
var adminApikey = params.AdminApiKey.value || '';
var searchApikey = params.SearchApiKey.value || '';
var indexPrefix = params.IndexPrefix.value || '';

var adminValidation = {};

try {
var algoliaEnable = ('Enable' in params) && (params.Enable.submitted === true);
var algoliaEnableContentSearch = ('EnableContentSearch' in params) && (params.EnableContentSearch.submitted === true);
var algoliaEnableRecommend = ('EnableRecommend' in params) && (params.EnableRecommend.submitted === true);
var algoliaEnablePricingLazyLoad = ('EnablePricingLazyLoad' in params) && (params.EnablePricingLazyLoad.submitted === true);

// If the user typed an empty prefix, the cartridge logic eventually
// uses the default <hostname>__<siteID>
var typedPrefix = indexPrefix.trim();
var finalIndexPrefix = typedPrefix || algoliaData.getDefaultIndexPrefix();

// 1) Validate indexing key
var serviceAdmin = algoliaIndexingService.getService({
jobID: 'API_KEY_VALIDATION_ADMIN',
stepID: 'validatePermissions',
applicationID: applicationID,
adminApikey: adminApikey
});

adminValidation = algoliaServiceHelper.validateAPIKey(
serviceAdmin,
applicationID,
adminApikey,
finalIndexPrefix
);

if (adminValidation.error) {
showDashboardWithMessages(adminValidation);
return;
}

// If we get here, the check is fine or not applicable. Save settings:
algoliaData.setPreference('Enable', algoliaEnable);
algoliaData.setPreference('ApplicationID', params.ApplicationID.value);
algoliaData.setPreference('ApplicationID', applicationID);
algoliaData.setSetOfStrings('AdditionalAttributes', params.AdditionalAttributes.value);
algoliaData.setPreference('InStockThreshold', params.InStockThreshold.value * 1);
algoliaData.setPreference('SearchApiKey', params.SearchApiKey.value);
algoliaData.setPreference('AdminApiKey', params.AdminApiKey.value);
algoliaData.setPreference('IndexPrefix', params.IndexPrefix.value);
algoliaData.setPreference('SearchApiKey', searchApikey);
algoliaData.setPreference('AdminApiKey', adminApikey);
algoliaData.setPreference('IndexPrefix', indexPrefix);
algoliaData.setPreference('RecordModel', params.RecordModel.value);
algoliaData.setSetOfStrings('LocalesForIndexing', params.LocalesForIndexing.value);
algoliaData.setPreference('EnableInsights', params.EnableInsights.submitted);
algoliaData.setPreference('EnableSSR', params.EnableSSR.submitted);
algoliaData.setPreference('EnableContentSearch', algoliaEnableContentSearch);
algoliaData.setPreference('EnableRecommend', algoliaEnableRecommend);
algoliaData.setPreference('EnablePricingLazyLoad', algoliaEnablePricingLazyLoad);
algoliaData.setPreference('IndexOutOfStock', params.IndexOutOfStock.submitted);
} catch (error) {
Logger.error(error);
showDashboardWithMessages(adminValidation, error.message);
return;
}

if (adminValidation.error) {
showDashboardWithMessages(adminValidation, '');
return;
}

start();
if (adminValidation.warning) {
showDashboardWithMessages(adminValidation, '');
} else {
start();
}
}

/**
* Helper to re-render the dashboard with an error message
* @param {Object} adminValidation admin key check results
* @param {string} errorMessage optional error message
*/
function showDashboardWithMessages(adminValidation, errorMessage) {
ISML.renderTemplate('algoliabm/dashboard/index', {
setttingsUpdateUrl: URLUtils.https('AlgoliaBM-HandleSettings'),
algoliaData: algoliaData,
latestReports: BMHelper.getLatestCOReportsByJob(),
adminErrorMessage: adminValidation.errorMessage,
adminWarningMessage: adminValidation.warning,
errorMessage: errorMessage
});
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ table#algolia-report-table .cell-number {
.red {
color: red;
}
.yellow {
color: #f0ad4e;
}
.narrow {
font-family: 'Arial Narrow', Helvetica, Arial, Verdana, sans-serif;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,29 @@
<iscomment> Algolia_AdminApiKey </iscomment>
<tr>
<td class="table_detail w s" colspan="1">
<isprint value="${Resource.msg('algolia.label.preference.adminkey', 'algolia', null)}" encoding="jshtml" /><span class="red b"> ${Resource.msg('algolia.label.util.required', 'algolia', null)}</span>
<isprint value="${Resource.msg('algolia.label.preference.adminkey', 'algolia', null)}" encoding="jshtml" />
<span class="red b"> ${Resource.msg('algolia.label.util.required', 'algolia', null)}</span>
<i class="fa fa-info-circle dw-nc-text-info info-hover"></i>
<div class="tooltip">${Resource.msg('algolia.label.preference.adminkey.help', 'algolia', null)}</div>
<div class="tooltip">
<isprint value="${Resource.msg('algolia.label.preference.adminkey.help', 'algolia', null)}" />
</div>
<isif condition="${pdict.adminWarningMessage}">
<div class="warning-message yellow narrow">
${pdict.adminWarningMessage}
</div>
</isif>
<isif condition="${pdict.adminErrorMessage}">
<div class="error-message red narrow">
${pdict.adminErrorMessage}
</div>
</isif>
</td>
<td class="table_detail w e s">
<input type="text" value="${pdict.algoliaData.getPreference('AdminApiKey') ? pdict.algoliaData.getPreference('AdminApiKey') : ''}" id="AdminApiKey" name="AdminApiKey" required/>
<input type="text"
value="${pdict.algoliaData.getPreference('AdminApiKey') ? pdict.algoliaData.getPreference('AdminApiKey') : ''}"
id="AdminApiKey"
name="AdminApiKey"
required />
</td>
</tr>

Expand All @@ -92,6 +109,18 @@
</td>
</tr>

<iscomment> Algolia_IndexOutOfStock </iscomment>
<tr>
<td class="table_detail w s" colspan="1">
<isprint value="${Resource.msg('algolia.label.preference.indexoutofstock', 'algolia', null)}" />
<i class="fa fa-info-circle dw-nc-text-info info-hover"></i>
<div class="tooltip">${Resource.msg('algolia.label.preference.indexoutofstock.help', 'algolia', null)}</div>
</td>
<td class="table_detail w e s">
<input type="checkbox" ${pdict.algoliaData.getPreference('IndexOutOfStock') ? "checked": ""} id="IndexOutOfStock" name="IndexOutOfStock" />
</td>
</tr>

<iscomment> Algolia_AdditionalAttributes </iscomment>
<tr>
<td class="table_detail w s" colspan="1">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ algolia.label.preference.name=Preference Name
algolia.label.preference.enable=Enable Algolia frontend cartridge
algolia.label.preference.applicationid=Application ID
algolia.label.preference.searchkey=Search API key
algolia.label.preference.adminkey=Admin API key
algolia.label.preference.adminkey=Write API Key
algolia.label.preference.instock=InStock Threshold
algolia.label.preference.custom=Additional Product Attributes
algolia.label.preference.indexprefix=Index Prefix. If set, it replaces the default index prefix:
Expand All @@ -20,6 +20,7 @@ algolia.label.preference.enablessr=Enable server-side rendering
algolia.label.preference.enablecontentsearch=Enable content search
algolia.label.preference.enablerecommend=Recommend
algolia.label.preference.enablePricingLazyLoad=Lazy Loading for prices
algolia.label.preference.indexoutofstock=Index out of stock products
algolia.label.preference.clientid=OCAPI Client ID
algolia.label.preference.clientpassword=OCAPI Client password
algolia.label.button.apply=Apply
Expand All @@ -32,7 +33,7 @@ algolia.label.util.version=Cartridge Version:
algolia.label.preference.enable.help=Enable the SFRA or SiteGenesis cartridge on your Storefront.
algolia.label.preference.applicationid.help=The Application ID is the unique identifier of your Algolia application. It is available in the API Keys section of your Algolia Dashboard.
algolia.label.preference.searchkey.help=The Search API key is used to perform search queries. It is available in the API Keys section of your Algolia Dashboard.
algolia.label.preference.adminkey.help=The Admin API key is used to perform indexing operations. It is available in the API Keys section of your Algolia Dashboard.
algolia.label.preference.adminkey.help=The Write API Key is used to perform indexing operations. It is available in the API Keys section of your Algolia Dashboard.
algolia.label.preference.instock.help=The InStock Threshold is used to determine if a product is in stock or not. If the stock level is below the threshold, the product is considered out of stock. The default value is 0,0.
algolia.label.preference.custom.help=The Additional Product Attributes is a comma-separated list of product attributes that will be indexed in Algolia. The default value is empty.
algolia.label.preference.indexprefix.help=By default the index name generated by the system looks like this: <hostname>__<siteID>__<"product" | "category">__<locale>. Setting this preference replaces the first two segments, the final index name becoming '<Algolia_IndexPrefix>__<"product" | "category">__<locale>'.
Expand All @@ -43,7 +44,7 @@ algolia.label.preference.enablessr.help=Server-side rendering for SFRA's CLP (ca
algolia.label.preference.enablecontentsearch.help=Enable Content Search on the storefront
algolia.label.preference.enablerecommend.help=Enable Algolia Recommend on the SFRA storefront cartridge. See the documentation for additional setup steps
algolia.label.preference.enablePricingLazyLoad.help=When enabled, prices are fetched from SFCC when search results are displayed. Permits to display the most up-to-date promotions without having to index them.

algolia.label.preference.indexoutofstock.help=Index out of stock products. When disabled, only in-stock (ATS higher than the InStock Threshold) products are indexed.

# v2 job report table
algolia.label.jobtype=job type:
Expand Down Expand Up @@ -110,3 +111,8 @@ algolia.msg.loading=Loading...
algolia.msg.confirmclean=Are you sure you want to clean the indexing queue?

algolia.error.service=Service request failed
algolia.error.missing.permissions=The API key is missing required permissions: {0}
algolia.error.key.validation=Failed to validate API key
algolia.warning.excessive.permissions=Your API key has more permissions than necessary. Consider removing these permissions: {0}
algolia.error.index.restrictedprefix=The API key does not have access to the required indices: {0}*

Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,42 @@ function isInclude(product) {
return true;
}

module.exports.isInclude = isInclude;
/**
* Returns `true` if the product’s ATS >= threshold, false otherwise.
* @param {dw.catalog.Product} product - The SFCC product or variant to check
* @param {number} threshold - The min ATS to consider in-stock
* @returns {boolean}
*/
function isInStock(product, threshold) {
var availabilityModel = product.getAvailabilityModel();
if (!availabilityModel) {
return false;
}

// even if one variant is in stock, we consider the product as in stock
if (product.master || product.variationGroup) {
const variantsIt = product.variants.iterator();
while (variantsIt.hasNext()) {
let variant = variantsIt.next();
let variantAvailabilityModel = variant.getAvailabilityModel();
if (variantAvailabilityModel) {
let variantInvRecord = variantAvailabilityModel.getInventoryRecord();
if (variantInvRecord) {
let variantAtsValue = variantInvRecord.getATS().getValue();
if (variantAtsValue >= threshold) {
return true;
}
}
}
}
}

var invRecord = availabilityModel.getInventoryRecord();
var atsValue = invRecord ? invRecord.getATS().getValue() : 0;
return atsValue >= threshold;
}

module.exports = {
isInStock: isInStock,
isInclude: isInclude,
};
Original file line number Diff line number Diff line change
Expand Up @@ -240,16 +240,25 @@ function getIndexPrefix() {
if (!empty(indexPrefix)) {
return indexPrefix.trim();
} else {
return getInstanceHostName() + '__' + currentSite.getID();
return getDefaultIndexPrefix();
}
}

/**
* Get default index prefix
* @returns {string} default index prefix
*/
function getDefaultIndexPrefix() {
return getInstanceHostName() + '__' + currentSite.getID();
}

/**
* Create index name for search results request
* If custom site preference Algolia_IndexPrefix is set in BM,
* its value will be used as a prefix instead of the first part of the hostname and the siteID
* @param {string} type type of indices: products | categories
* @param {string} locale optional: requested locale
* @param {string} prefix optional: index prefix
* @returns {string} index name
*/
function calculateIndexName(type, locale) {
Expand Down Expand Up @@ -350,4 +359,5 @@ module.exports = {
csvStringToArray: csvStringToArray,
CATEGORIES_SEPARATOR: CATEGORIES_SEPARATOR,
clientSideData: clientSideData,
getDefaultIndexPrefix: getDefaultIndexPrefix
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ var productModelCustomizer = require('*/cartridge/scripts/algolia/customization/
var ObjectHelper = require('*/cartridge/scripts/algolia/helper/objectHelper');
var jobHelper = require('*/cartridge/scripts/algolia/helper/jobHelper');
var logger = require('*/cartridge/scripts/algolia/helper/jobHelper').getAlgoliaLogger();
var productFilter = require('*/cartridge/scripts/algolia/filters/productFilter');

var extendedProductAttributesConfig;
try {
Expand All @@ -28,6 +29,7 @@ try {
}

const ALGOLIA_IN_STOCK_THRESHOLD = algoliaData.getPreference('InStockThreshold');
const INDEX_OUT_OF_STOCK = algoliaData.getPreference('IndexOutOfStock');

/**
* Get the lowest promotional price for product
Expand Down Expand Up @@ -356,14 +358,12 @@ var aggregatedValueHandlers = {
return pricebooks;
},
in_stock: function (product) {
let availabilityModel = product.getAvailabilityModel();
let inStock = productFilter.isInStock(product, ALGOLIA_IN_STOCK_THRESHOLD);

if (product.isMaster() || product.isVariationGroup()) {
return availabilityModel.availabilityStatus === 'IN_STOCK';
if (!inStock && !INDEX_OUT_OF_STOCK) {
return undefined;
}

let inventoryRecord = availabilityModel.getInventoryRecord();
let inStock = (inventoryRecord ? inventoryRecord.getATS().getValue() >= ALGOLIA_IN_STOCK_THRESHOLD : false);
return inStock;
},
image_groups: function (product) {
Expand Down Expand Up @@ -444,11 +444,21 @@ var aggregatedValueHandlers = {
const variantsIt = product.variants.iterator();
while (variantsIt.hasNext()) {
var variant = variantsIt.next();

let inStock = productFilter.isInStock(variant, ALGOLIA_IN_STOCK_THRESHOLD);

if (!inStock && !INDEX_OUT_OF_STOCK) {
continue;
}

var baseModel = { in_stock: inStock };

var localizedVariant = new algoliaLocalizedProduct({
product: variant,
locale: request.getLocale(),
attributeList: parameters.variantAttributes,
isVariant: true,
baseModel: baseModel,
});
variants.push(localizedVariant);
}
Expand Down
Loading

0 comments on commit e4fece8

Please sign in to comment.