@oraclecc/dcu
Version:
Development tools for Oracle Commerce Cloud.
329 lines (260 loc) • 11.6 kB
JavaScript
const Promise = require("bluebird")
const constants = require("./constants").constants
const copyFieldContentsToFile = require("./grabberUtils").copyFieldContentsToFile
const endPointTransceiver = require("./endPointTransceiver")
const exists = require("./utils").exists
const getGrabbingConcurrency = require("./concurrencySettings").getGrabbingConcurrency
const getInitialMatchName = require("./localeUtils").getInitialMatchName
const getFallBackName = require("./localeUtils").getFallBackName
const hasFallBack = require("./localeUtils").hasFallBack
const info = require("./logger").info
const makeTrackedDirectory = require("./utils").makeTrackedDirectory
const makeTrackedTree = require("./utils").makeTrackedTree
const readMetadataFromDisk = require("./metadata").readMetadataFromDisk
const request = require("./requestBuilder").request
const sanitizeName = require("./utils").sanitizeName
const warn = require("./logger").warn
const writeEtag = require("./etags").writeEtag
const writeFile = require("./utils").writeFile
const writeFileAndETag = require("./grabberUtils").writeFileAndETag
const writeMetadata = require("./metadata").writeMetadata
// Set up a map to enable us to find the directory that corresponds to a widget type.
const widgetTypeToDirectoryMap = new Map()
/**
* Grab all the widget instances on the system, assuming we have already got the base widgets.
*/
function grabWidgetInstances(widgetName) {
return endPointTransceiver.listWidgets().then(results => {
// Now look at each instance in turn.
return Promise.map(preProcessInstances(results.data.items), widgetInstance => {
// See if we are after a specific widget.
if (widgetName && widgetName != widgetInstance.descriptor.displayName) {
return
}
return grabWidgetInstance(widgetInstance)
}, getGrabbingConcurrency())
})
}
/**
* Process the supplied widget instance.
* @param widgetInstance
*/
function grabWidgetInstance(widgetInstance) {
// Let the user know something is happening.
info("grabbingWidgetInstance", {name: widgetInstance.displayName})
// See if we can find a widget dir.
const widgetDir = widgetTypeToDirectoryMap.get(widgetInstance.descriptor.widgetType)
// If there's no widget dir, it implies the widget is not editable and can be ignored.
if (widgetDir) {
// Create directory for each widget instance.
const widgetInstanceDir = getWidgetInstancePath(widgetInstance.descriptor.widgetType, widgetInstance.displayName)
// See if we already have grabbed a version of the instance.
if (exists(widgetInstanceDir)) {
// Get the version from the instance we currently have on disk.
const versionOnDisk = readMetadataFromDisk(widgetInstanceDir, constants.widgetInstanceMetadataJson).version
// If the one on disk is more up to date, don't go any further.
if (versionOnDisk > widgetInstance.descriptor.version) {
return null
}
}
// Safe to go ahead and start building.
makeTrackedTree(widgetInstanceDir)
// Save off the metadata for the instance.
saveInstanceMetadata(widgetInstance, widgetInstanceDir)
// Build up the default promise list. Always want the template, style sheet, snippets and modifiable metadata.
return Promise.all([
getInstanceTemplate(widgetInstance.descriptor.widgetType, widgetInstance.id, widgetInstanceDir),
getInstanceCss(widgetInstance, widgetInstanceDir),
getInstanceSnippets(widgetInstance, widgetInstanceDir),
getInstanceMetadata(widgetInstance, widgetInstanceDir)
])
}
}
/**
* In certain scenarios, it is possible to get the multiple instances of the same name but different versions.
* We only want instances of the latest version.
* @param items
*/
function preProcessInstances(widgetInstances) {
// Create a map to guarantee uniqueness.
const instancesMap = new Map()
// Walk through the list, looking for duplicates.
widgetInstances.forEach(widgetInstance => {
// We have seen an instance with this name before.
if (instancesMap.has(widgetInstance.displayName)) {
// See if the instance is more up to date than the one we have in the map.
const storedInstance = instancesMap.get(widgetInstance.displayName)
if (widgetInstance.version > storedInstance.version) {
// This one is more up to date so replace what is in the map.
instancesMap.set(widgetInstance.displayName, widgetInstance)
}
} else {
// Never seen the instance before - just need to add it.
instancesMap.set(widgetInstance.displayName, widgetInstance)
}
})
// Send back the processed list.
return instancesMap.values()
}
/**
* Save information for widget instance off in the tracking directory.
* @param widgetInstance
* @param widgetInstanceDir
*/
function saveInstanceMetadata(widgetInstance, widgetInstanceDir) {
const widgetInstanceJson = {
version: widgetInstance.descriptor.version,
displayName: widgetInstance.displayName
}
writeMetadata(`${widgetInstanceDir}/${constants.widgetInstanceMetadataJson}`, widgetInstanceJson)
}
/**
* Get the template for the instance.
* @param widgetType
* @param widgetInstanceId
* @param widgetInstanceDir
* @returns A Bluebird promise.
*/
function getInstanceTemplate(widgetType, widgetInstanceId, widgetInstanceDir) {
const promises = []
// Grab the base template.
promises.push(copyFieldContentsToFile("getWidgetSourceCode", widgetInstanceId, "source", `${widgetInstanceDir}/${constants.displayTemplate}`))
// Web Content widgets are special in that the template with the actual content is on a different endpoint. Pull it down as well in a different file.
if (widgetType === "webContent") {
promises.push(copyFieldContentsToFile("getWidgetWebContent", widgetInstanceId, "content", `${widgetInstanceDir}/${constants.webContentTemplate}`))
}
return Promise.all(promises)
}
/**
* Get the CSS for the supplied instance.
* @param widgetInstance
* @param widgetInstanceDir
* @returns A BlueBird promise
*/
function getInstanceCss(widgetInstance, widgetInstanceDir) {
// Match value will be a combination of widget ID and widget instance ID. We want to replace this with something
// neutral that we can transform again when put the code back up.
return copyFieldContentsToFile("getWidgetLess", widgetInstance.id, "source",
`${widgetInstanceDir}/${constants.widgetLess}`, constants.lessFileSubstitutionReqExp, constants.widgetInstanceSubstitutionValue)
}
/**
* Get all the snippet stuff for widget instance.
* @param widgetInstance
* @param widgetInstanceDir
* @returns {*}
*/
function getInstanceSnippets(widgetInstance, widgetInstanceDir) {
// Make the locales directories first.
makeTrackedDirectory(`${widgetInstanceDir}/locales`)
// Then do the locales request one by one to stop us running out of connections.
return Promise.each(endPointTransceiver.locales, locale => getWidgetSnippets(widgetInstance, widgetInstanceDir, locale))
}
/**
* Put the boilerplate in one place.
* @param results
* @return {localeData|{resources, custom}|{resources}|{localeKey}|number}
*/
function localeDataFound(results) {
return results.data.localeData && Object.keys(results.data.localeData.resources).length
}
/**
* Figure out the parameters we need to send to the endpoint.
* @param widgetInstanceId
* @param localeName
* @return {*[]}
*/
function getSnippetEndpointParams(widgetInstanceId, localeName) {
let endpointParams = [widgetInstanceId]
if (endPointTransceiver["getWidgetLocaleContentForLocale"]) {
endpointParams = [widgetInstanceId, localeName]
}
return endpointParams
}
/**
* Get all the snippets associated with a specific locale and widget instance.
* @param widgetInstance
* @param widgetInstanceDir
* @param locale
* @returns a Bluebird promise.
*/
function getWidgetSnippets(widgetInstance, widgetInstanceDir, locale) {
// Get the name we will first try to match on.
let localeName = getInitialMatchName(locale)
// Prefer an endpoint that will lock at locale level if available.
let getSnippetsEndpoint = endPointTransceiver["getWidgetLocaleContent"]
if (endPointTransceiver["getWidgetLocaleContentForLocale"]) {
getSnippetsEndpoint = endPointTransceiver["getWidgetLocaleContentForLocale"]
}
// Get the text snippets for the "current" locale.
return getSnippetsEndpoint(getSnippetEndpointParams(widgetInstance.id, localeName), request().withLocale(localeName)).tap(results => {
// See if we got any locale data.
if (localeDataFound(results)) {
writeWidgetSnippets(widgetInstanceDir, localeName, results, widgetInstance)
} else if (hasFallBack(locale)) {
// No snippets were found but there is a callback - try again with that.
localeName = getFallBackName(locale)
return getSnippetsEndpoint(getSnippetEndpointParams(widgetInstance.id, localeName), request().withLocale(localeName)).tap(results => {
// See if we got any locale data this time.
localeDataFound(results) && writeWidgetSnippets(widgetInstanceDir, localeName, results, widgetInstance)
})
}
})
}
/**
* Boilerplate to write out the widget snippets.
* @param widgetInstanceDir
* @param localeName
* @param results
* @param widgetInstance
*/
function writeWidgetSnippets(widgetInstanceDir, localeName, results, widgetInstance) {
// Create directory for the current locale.
const widgetInstanceLocaleDir = `${widgetInstanceDir}/locales/${localeName}`
makeTrackedDirectory(widgetInstanceLocaleDir)
// If there are custom field values, use these to override the base values.
results.data.localeData.custom && Object.keys(results.data.localeData.resources).forEach(key => {
if (results.data.localeData.custom[key]) {
results.data.localeData.resources[key] = results.data.localeData.custom[key]
}
})
// Then extract just what we want for the file.
const fileContents = {
resources: results.data.localeData.resources
}
// Write out the text strings as a JSON file.
const localeStringsFile = `${widgetInstanceLocaleDir}/ns.${widgetInstance.descriptor.i18nresources}.json`
writeFile(localeStringsFile, JSON.stringify(fileContents, null, 2))
// Write out the etag.
writeEtag(localeStringsFile, results.response.headers.etag)
}
/**
* Get user modifiable metadata for the instance.
* @param widgetInstance
* @param widgetInstanceDir
* @returns {Promise.<TResult>|*}
*/
function getInstanceMetadata(widgetInstance, widgetInstanceDir) {
// Make sure the endpoint exists on the instance.
if (endPointTransceiver.serverSupports("getWidgetMetadata")) {
// Call the custom metadata endpoint created specially for this purpose.
return endPointTransceiver.getWidgetMetadata([widgetInstance.repositoryId]).then(results => {
// Write out the massaged data.
writeFileAndETag(`${widgetInstanceDir}/${constants.userWidgetInstanceMetadata}`,
JSON.stringify(results.data.metadata, null, 2), results.response.headers.etag)
})
} else {
warn("widgetInstanceMetadataCannotBeGrabbed")
}
}
/**
* We need to be able to get the correct path for a widget instance from more than one place.
* @param widgetType
* @param displayName
* @returns {string}
*/
function getWidgetInstancePath(widgetType, displayName) {
return `${widgetTypeToDirectoryMap.get(widgetType)}/instances/${sanitizeName(displayName)}`;
}
exports.getWidgetInstancePath = getWidgetInstancePath
exports.grabWidgetInstances = grabWidgetInstances
exports.widgetTypeToDirectoryMap = widgetTypeToDirectoryMap