@oraclecc/dcu
Version:
Development tools for Oracle Commerce Cloud.
1,026 lines (835 loc) • 35.9 kB
JavaScript
const basename = require('path').basename
const dirname = require('path').dirname
const Promise = require("bluebird")
const upath = require("upath")
const cacheWidgetInstances = require("./metadata").cacheWidgetInstances
const createWidgetInExtension = require("./widgetCreator").createWidgetInExtension
const constants = require("./constants").constants
const dumpEtag = require("./etags").dumpEtag
const endPointTransceiver = require("./endPointTransceiver")
const eTagFor = require("./etags").eTagFor
const error = require("./logger").error
const exists = require("./utils").exists
const findCachedWidgetInstanceMatchingDisplayName = require('./metadata').findCachedWidgetInstanceMatchingDisplayName
const getBaseElementTag = require("./elementUtils").getBaseElementTag
const getCachedWidgetInstanceFromMetadata = require("./metadata").getCachedWidgetInstanceFromMetadata
const getElementByTag = require("./metadata").getElementByTag
const getElementTagRepoId = require("./elementUtils").getElementTagRepoId
const i18n = require("./i18n")
const info = require("./logger").info
const inTransferMode = require("./state").inTransferMode
const logError = require("./logger").logError
const makeTrackingDirTree = require("./utils").makeTrackingDirTree
const processPutResultAndEtag = require("./putterUtils").processPutResultAndEtag
const readFile = require("./utils").readFile
const readJsonFile = require("./utils").readJsonFile
const readMetadata = require("./metadata").readMetadata
const readMetadataFromDisk = require("./metadata").readMetadataFromDisk
const request = require("./requestBuilder").request
const resetEtag = require("./etags").resetEtag
const shouldSuppressThemeCompile = require("./putterUtils").shouldSuppressThemeCompile
const splitFromBaseDir = require("./utils").splitFromBaseDir
const splitPath = require("./utils").splitPath
const updateMetadata = require("./metadata").updateMetadata
const walkDirectory = require("./utils").walkDirectory
const warn = require("./logger").warn
const writeDummyEtag = require("./etags").writeDummyEtag
const writeFile = require("./utils").writeFile
const shouldUpdateInstances = require('./putterUtils').shouldUpdateInstances
const shouldSendInstanceConfig = require('./putterUtils').shouldSendInstanceConfig
const autoFix = require('./putterUtils').autoFix
/**
* Do the jiggery-pokery required to upload a file.
* @param elementInstance
* @param imagePath
* @return {*|PromiseLike<T>|Promise<T>}
*/
function uploadImage(elementInstance, imagePath) {
// Need to do the file upload dance. Set up an object to hold segment and path information.
const newFileInfo = {
"filename": elementInstance.imageConfig.fileName,
"segments": 1,
"uploadtype": "general"
}
// Firstly, tell the server we are about to upload something.
return endPointTransceiver.startFileUpload(request().withBody(newFileInfo)).then(results => {
// Build up the payload from the image file.
const payload = {
filename: newFileInfo.filename,
file: readFile(imagePath, "base64"),
index: 0
}
// Send up the base64'd image in a big JSON block.
return endPointTransceiver.doFileSegmentUpload([results.data.token], request().withBody(payload)).then(results => {
// Make sure upload worked.
if (results.data.success) {
// Lastly, find out what the URI for the file is.
return endPointTransceiver.getFileURI([`general/${elementInstance.imageConfig.fileName}`]).then(results => {
// Update the URI so we are pointing at the right thing.
elementInstance.imageConfig.src = results.data.uri
})
}
})
})
}
/**
* Walk through the supplied element instances, looking for any images.
* @param path
* @param elementInstances
* @return A BlueBird promise
*/
function processImages(path, elementInstances) {
// Firstly we only want element instances with image config.
return Promise.each(elementInstances.filter(elementInstance => elementInstance.imageConfig), elementInstance => {
// Figure out where the image file should be.
const imagePath = `${dirname(path)}/${constants.elementInstancesImagesDir}/${elementInstance.imageConfig.fileName}`
// Make sure file is actually there.
if (exists(imagePath)) {
return uploadImage(elementInstance, imagePath)
} else {
// Warn that we cant find image.
info("elementImageNotFound", {imagePath})
}
}).then(() => elementInstances)
}
/**
* Put the path computation logic in one place.
* @param path
* @return {string}
*/
function figureUserElementInstancesPath(path) {
return `${dirname(path)}/${constants.userElementInstancesMetadataJson}`
}
/**
* Walk through the element instances creating or deleting any as necessary.
* @param path
* @param metadata
* @return {PromiseLike<T | never>}
*/
function ensureElementInstances(metadata, path) {
// Load up the instances array.
const elementInstances = readJsonFile(figureUserElementInstancesPath(path)).elementInstances
// Load up the display template for future use.
const template = getShornTemplate(path)
// Massage the element instances into something we can send to the server.
return Promise.each(elementInstances, elementInstance => {
// Fill in the type field.
elementInstance.type = "instance"
// Fill in the config options field from the user metadata.
const elementByInstanceMetadata = getElementByTag(getBaseElementTag(elementInstance.tag))
elementInstance.configOptions = elementByInstanceMetadata.configOptions
// If the matching instance actually appears on the layout AND either :
// we are in transfer mode OR the element instance does not exist.
if (template.includes(getElementTagRepoId(elementInstance.tag)) &&
(inTransferMode() || !getElementByTag(elementInstance.tag))) {
// Need a new element instance tag.
return endPointTransceiver.createFragmentInstance(
[metadata.repositoryId, getBaseElementTag(elementInstance.tag)])
.then(results => {
// Keep track of old tag, new tag and repo ID so we can mess with the template if we have to.
elementInstance.oldTag = elementInstance.tag
elementInstance.tag = results.data.tag
elementInstance.repositoryId = results.data.repositoryId
})
} else {
// We did not create the instance. If its not used on the template add it to the delete list.
if (!template.includes(getElementTagRepoId(elementInstance.tag))) {
elementInstance.unused = true
}
}
},
[]).then(() => processImages(path, elementInstances))
}
/**
* Helps with element instance repository key substitution.
* @param repositoryId
* @return {string}
*/
function makeMatcher(repositoryId) {
return `id: '${repositoryId}'`
}
/**
* Load up the display template for an elementized widget, cutting off the setContextVariable gubbins at the top.
* @param path
* @return {*|string}
*/
function getShornTemplate(path) {
return readFile(path).replace(/<!-- ko setContextVariable: [\s\S]*? \/ko -->/gm, "")
}
/**
* Turn the layout source into something we can send to the server.
* @param path
* @param elementInstances
* @return {*}
*/
function prepareLayoutSource(path, elementInstances) {
// Need to chop off the setContextVariable binding stuff first.
const templateText = getShornTemplate(path)
// Then we need to map any repo IDs in the template.
return elementInstances.filter(elementInstance => elementInstance.oldTag)
.reduce((templateText, elementInstance) =>
templateText.replace(makeMatcher(getElementTagRepoId(elementInstance.oldTag)), makeMatcher(elementInstance.repositoryId)),
templateText)
}
/**
* Holds the boilerplate for updating an elementized widget instance.
* @param metadata
* @param path
* @return {PromiseLike<T | never | never>}
*/
function sendElementizedWidget(metadata, path) {
// Walk through the element instances.
return ensureElementInstances(metadata, path).then(elementInstances => {
// Build up the payload.
const payload = {
layoutConfig: [
{
// Only send instances that we are not deleting.
fragments: elementInstances.filter(elementInstance => !elementInstance.unused)
}
],
widgetConfig: {
name: metadata.displayName, // Need this or the endpoint will choke.
notes: ""
},
layoutSource: prepareLayoutSource(path, elementInstances), // Need to massage the layout so the endPoint is happy.
layoutDescriptorId: metadata.layoutDescriptorId // This also is vital.
}
// Only put in layout instance ID if we have it.
metadata.layoutInstanceId && (payload.layoutInstanceId = metadata.layoutInstanceId)
// Call the endpoint, do not supply a etag for now.
return deleteElementInstances(metadata, elementInstances.filter(elementInstance =>
elementInstance.unused && !inTransferMode())).then(() =>
endPointTransceiver.updateWidget([metadata.repositoryId], request().withBody(payload)))
})
}
/**
* From the server, take a note of all existing element instances for later use.
* @param metadata
*/
function getExistingElementInstances(metadata) {
return endPointTransceiver.getWidget([metadata.repositoryId]).then(results => {
if (results.data.fragments) {
return results.data.fragments.filter(element => element.type == "instance")
} else {
return []
}
})
}
/**
* Does the trickery required to update an elementized widget.
* @param metadata
* @param path
* @return a BlueBird promise
*/
function putElementizedWidget(metadata, path) {
if (inTransferMode()) {
// In transfer mode, we always create a new element instance for each instance that is used in the template.
// We do not attempt to reuse any existing instances as we cant guarantee these and we delete them at the very end.
return getExistingElementInstances(metadata).then(existingInstances =>
deleteElementInstances(metadata, existingInstances).then(() =>
sendElementizedWidget(metadata, path)))
} else {
// Outside of transfer mode, things are less awkward.
return sendElementizedWidget(metadata, path)
}
}
/**
* Blow away the supplied element instances that are marked for deletion.
* @param instancesToDelete
* @return {*}
*/
function deleteElementInstances(metadata, elementInstances) {
return Promise.each(elementInstances, instance => {
// Only try to delete the element if it is actually there.
if (getElementByTag(instance.tag)) {
// In non-transfer mode i.e. --put/--putAll, we delete element instances that are not in the template.
// In transfer mode - previous instances are deleted and all replaced.
!inTransferMode() && warn("deletingUnusedElementInstance", {tag : instance.tag})
return endPointTransceiver.deleteFragment([metadata.repositoryId, instance.tag])
}
})
}
/**
* Do the needful to get the supplied template back to the server.
* @param path
* @return
*/
function putWidgetInstanceTemplate(path) {
// Template may not exist; silently return if it does not.
return exists(path) && getWidgetAndWidgetInstanceMetadata(path).then(metadata => {
if (metadata) {
// Elementized widgets are updated via a different endpoint.
// Version 1 web content instances are not elementized however.
if (metadata.elementized && !(metadata.widgetType == "webContent" && metadata.instance.version == 1)) {
return putElementizedWidget(metadata.instance, path)
} else {
// Just a plain ordinary widget.
return putWidgetInstanceFile(metadata, path, "updateWidgetSourceCode")
}
}
})
}
/**
* Get the widget metadata (that is, the stuff we let people change) back to the server.
* @param path
*/
function putWidgetModifiableMetadata(path) {
return putMetadata(path, constants.widgetMetadataJson, "updateWidgetDescriptorMetadata", syncWidgetMetadata)
}
/**
* Get the widget instance metadata (that is, the stuff we let people change) back to the server.
* @param path
*/
function putWidgetInstanceModifiableMetadata(path) {
// Firstly, make sure that we actually want to send widget instance metadata.
if (shouldSendInstanceConfig()) {
// See if endpoint exists - metadata endpoints are a recent innovation.
if (!endPointTransceiver.serverSupports("updateWidgetMetadata")) {
warn("widgetContentFileCannotBeSent", {path})
return
}
return getWidgetAndWidgetInstanceMetadata(path).then(metadata => {
if (metadata) {
return endPointTransceiver.updateWidgetMetadata([metadata.instance.repositoryId],
request().fromPathAsJSON(path, "metadata").withEtag(metadata.instance.etag)).tap(
results => processPutResultAndEtag(path, results, syncWidgetInstanceMetadata))
}
})
}
}
/**
* This is fiddly. Users can change the display name of an instance which we store internally (because we need it).
* So we need to make sure that the display name held by us is the same what the external metadata file says it is.
* @param path
*/
function syncWidgetInstanceMetadata(path) {
if (!inTransferMode()) {
// Load up the display name value that the user can change.
const displayName = readJsonFile(path).displayName
// If there is a value (there should always be but play safe), use it to modify the internal metadata.
displayName && updateMetadata(path, constants.widgetInstanceMetadataJson, {displayName})
}
}
/**
* This is fiddly. Users can change the display name of a widget which we store internally (because we need it).
* So we need to make sure that the display name held by us is the same what the external metadata file says it is.
* This is somewhat more complex in that widget names can be internationalized.
* @param path
*/
function syncWidgetMetadata(path) {
if (!inTransferMode()) {
// Defensively load the translations array holding the display name value that the user can change.
const translations = readJsonFile(path).translations
if (translations) {
// Look for a translation with the same name as the current working locale.
const translation = translations.find(t => t.language == endPointTransceiver.locale)
// If there is one (there should be) use it to update the value in the internal metadata.
if (translation) {
updateMetadata(path, constants.widgetMetadataJson, {displayName: translation.name})
}
}
}
}
/**
* Holds the boilerplate for getting a metadata file back to the server.
* @param path
* @param metadataType
* @param endpoint
* @param successCallback
* @returns {Promise.<TResult>|*}
*/
function putMetadata(path, metadataType, endpoint, successCallback) {
// See if endpoint exists - metadata endpoints are a recent innovation.
if (!endPointTransceiver.serverSupports(endpoint)) {
warn("widgetContentFileCannotBeSent", {path})
return
}
return readMetadata(path, metadataType).then(metadata => {
if (metadata) {
return endPointTransceiver[endpoint]([metadata.repositoryId],
request().fromPathAsJSON(path, "metadata").withEtag(metadata.etag)).tap(
results => processPutResultAndEtag(path, results, successCallback))
}
})
}
/**
* Boilerplate for sending base widget content file to server.
* @param path
* @param endpoint
* @returns {Promise.<TResult>|*}
*/
function putBaseWidgetFile(path, endpoint, field) {
// See if endpoint exists - base endpoints are a recent innovation.
if (!endPointTransceiver.serverSupports(endpoint)) {
warn("widgetContentFileCannotBeSent", {path})
return
}
// Get the metadata for the widget.
return readMetadata(path, constants.widgetMetadataJson).then(metadata => {
if (metadata) {
return endPointTransceiver[endpoint]([metadata.repositoryId],
`?updateInstances=${shouldUpdateInstances()}`,
request().fromPathAs(path, field).withEtag(metadata.etag)).tap(
results => processPutResultAndEtag(path, results))
}
})
}
/**
* Get the contents of the base template back to the server.
* @param path
*/
function putWidgetBaseTemplate(path) {
checkSyncWithInstances(path, constants.displayTemplate, readFile(path))
return putBaseWidgetFile(path, "updateWidgetDescriptorBaseTemplate", "source")
}
/**
* Need to ensure that if we want sync instances with base content that the file contents on disk match.
* @param path
* @param fileName
*/
function checkSyncWithInstances(path, fileName, contents) {
// See if we are syncing the instances with the base resources.
if (shouldUpdateInstances()) {
// Walk through the instances directory, looking for suitable files.
walkDirectory(`${getWidgetBaseDir(path)}/instances`, {
listeners: {
file: (root, fileStat, next) => {
const fullPath = upath.resolve(root, fileStat.name)
// Look for any corresponding instance content.
if (fullPath.endsWith(fileName)) {
// Make the instance file look like the base file.
writeFile(fullPath, contents)
}
// Jump to the next file.
next()
}
}
})
}
}
/**
* Find the base directory for the widget from the path.
* @param path
* @returns {string}
*/
function getWidgetBaseDir(path) {
const tokens = path.split("/")
return tokens.slice(0, tokens.indexOf("widget") + 2).join("/")
}
/**
* Get the contents of the base less file back to the server.
* @param path
*/
function putWidgetBaseLess(path) {
checkSyncWithInstances(path, constants.widgetLess, `${constants.widgetInstanceSubstitutionValue} {\n${readFile(path)}\n}\n`)
return putBaseWidgetFile(path, "updateWidgetDescriptorBaseLess", "source")
}
/**
* Get the contents of the config json back to the server.
* @param path
*/
function putWidgetConfigJson(path) {
return putMetadata(path, constants.widgetMetadataJson, "updateConfigMetadataForWidgetDescriptor")
}
/**
* Get the contents of the config snippets json back to the server.
* @param path
*/
function putWidgetConfigSnippets(path) {
// See if endpoint exists - base endpoints are a recent innovation.
if (!endPointTransceiver.serverSupports("updateConfigLocaleContentForWidgetDescriptor")) {
warn("widgetContentFileCannotBeSent", {path})
return
}
// Get the metadata for the widget.
return readMetadata(path, constants.widgetMetadataJson).then(metadata => {
if (metadata) {
// Get the locale from the path.
const tokens = path.split("/")
const locale = basename(tokens[tokens.length - 1], ".json")
return endPointTransceiver.updateConfigLocaleContentForWidgetDescriptor([metadata.repositoryId, locale],
request().withLocale(locale).fromPathAsJSON(path, "localeData").withEtag(metadata.etag)).tap(
results => processPutResultAndEtag(path, results))
}
})
}
/**
* Get the contents of the base snippets file back to the server.
* @param path
* @returns {Promise.<TResult>|*}
*/
function putWidgetBaseSnippets(path) {
// See if endpoint exists - base endpoints are a recent innovation.
if (!endPointTransceiver.serverSupports("updateWidgetDescriptorBaseLocaleContent")) {
warn("widgetContentFileCannotBeSent", {path})
return
}
// Get the metadata for the widget.
return readMetadata(path, constants.widgetMetadataJson).then(metadata => {
if (metadata) {
// Get the locale from the path.
const tokens = path.split("/")
const locale = tokens[tokens.length - 2]
// Make sure the instance content is sync'ed - if needbe.
checkSyncWithInstances(path, `${locale}/${basename(path)}`, readFile(path))
return endPointTransceiver.updateWidgetDescriptorBaseLocaleContent([metadata.repositoryId, locale],
`?updateInstances=${shouldUpdateInstances()}`,
request().withLocale(locale).fromPathAsJSON(path, "localeData").withEtag(metadata.etag)).tap(
results => processPutResultAndEtag(path, results))
}
})
}
/**
* Web content instance templates are a bit special so we handle them here.
* @param metadata
* @param path
* @returns A Bluebird promise.
*/
function putWebContentWidgetInstanceTemplate(path) {
// Need the metadata first. Make sure template is actually there.
return exists(path) && getWidgetAndWidgetInstanceMetadata(path).then(metadata => {
// Get the name and notes first so we don't overwrite these.
if (metadata) {
return endPointTransceiver.getWidget([metadata.instance.repositoryId]).then(results => {
// Build up the payload, using some data from the server.
const payload = {
widgetConfig: {
name: results.data.name,
notes: results.data.notes
},
content: readFile(path)
}
return endPointTransceiver.updateWidgetWebContent(
[metadata.instance.repositoryId], request().withBody(payload).withEtag(metadata.etag)).tap(
results => processPutResultAndEtag(path, results))
})
}
})
}
/**
* Send a widget JavaScript file back up to the server.
* @param path
* @returns A BlueBird promise.
*/
function putWidgetJavaScript(path) {
// Get the base metadata for the widget.
return readMetadata(path, constants.widgetMetadataJson).then(metadata => {
if (metadata) {
// Call the endpoint, passing in the widget ID and the base file name of the .js file.
return endPointTransceiver.updateWidgetDescriptorJavascript(
[metadata.repositoryId, basename(path)], request().fromPathAs(path, "source").withEtag(metadata.etag)).tap(
results => processPutResultAndEtag(path, results))
}
})
}
/**
* Do the needful to get the supplied widget instance less back on the server.
* @param path
*/
function putWidgetInstanceLess(path) {
return getWidgetAndWidgetInstanceMetadata(path).then(metadata => {
if (metadata) {
return putWidgetInstanceFile(metadata, path, "updateWidgetLess", true)
}
})
}
/**
* Send the text snippets for the widget instance back to the server.
* @param path
* @returns a BlueBird promise.
*/
function putWidgetInstanceSnippets(path) {
// Get the metadata.
return getWidgetAndWidgetInstanceMetadata(path).then(metadata => {
if (metadata) {
// Get the locale from the path.
const tokens = path.split("/")
const locale = tokens[tokens.length - 2]
const widgetInstanceId = metadata.instance.repositoryId
let putSnippetsEndpoint = endPointTransceiver["updateWidgetCustomTranslations"]
let endpointParams = [widgetInstanceId]
// Prefer an endpoint that will lock at locale level if available.
if (endPointTransceiver["updateWidgetCustomTranslationsForLocale"]) {
putSnippetsEndpoint = endPointTransceiver["updateWidgetCustomTranslationsForLocale"]
endpointParams = [widgetInstanceId, locale]
}
// Build up the payload. Need to chop off the enclosing key.
const payload = {
custom: readJsonFile(path).resources
}
return putSnippetsEndpoint(endpointParams,
request().withLocale(locale).withEtag(eTagFor(path)).withBody(payload)).tap(
results => processPutResultAndEtag(path, results))
}
})
}
/**
* Holds the boilerplate associated with getting a widget instance file back on the server.
* @param metadata
* @param path
* @param endpoint
* @param transform
* @returns A Bluebird promise
*/
function putWidgetInstanceFile(metadata, path, endpoint, transform) {
// Build the basic body.
const body = request().fromPathAs(path, "source").withEtag(metadata.etag)
// See if we need to transform the contents before sending.
if (transform) {
// Replace the substitution value in the file with the IDs on the target system.
body.replacing(constants.widgetInstanceSubstitutionValue,
`#${metadata.instance.descriptorRepositoryId}-${metadata.instance.repositoryId}`)
}
return endPointTransceiver[endpoint]([metadata.instance.repositoryId], `?suppressThemeCompile=${shouldSuppressThemeCompile()}`, body).tap(
results => processPutResultAndEtag(path, results))
}
/**
* Try to get the metadata for a widget instance - by hook or by crook.
* @param path
* @param widgetMetadata
* @returns A BlueBird promise.
*/
function getWidgetInstanceMetadata(path, widgetMetadata) {
// Load the metadata for the widget instance.
return readMetadata(path, constants.widgetInstanceMetadataJson).then(widgetInstanceMetadata => {
// Looks like we have metadata but check it actually exists on the server - someone could have deleted it.
if (widgetInstanceMetadata && getCachedWidgetInstanceFromMetadata(widgetInstanceMetadata)) {
widgetMetadata.instance = widgetInstanceMetadata
return widgetMetadata
// We have a widget but no instance. Create the instance, then load the metadata.
} else {
warn("creatingWidgetInstance", {path: path})
return createMatchingWidgetInstance(widgetMetadata, path).then(() => {
// Make sure instance got created. There are certain edge cases when this could fail so deal with it.
const existingMetadata = readMetadataFromDisk(path, constants.widgetInstanceMetadataJson, true)
if (!getCachedWidgetInstanceFromMetadata(existingMetadata)) {
error("failedToCreateInstance", {name: existingMetadata.displayName})
return null
}
// If we are not in transfer mode, need to reset the etag for the file.
!inTransferMode() && writeDummyEtag(path)
// Now the instance exists, can load the metadata.
return readMetadata(path, constants.widgetInstanceMetadataJson).then(widgetInstanceMetadata => {
widgetMetadata.instance = widgetInstanceMetadata
return widgetMetadata
})
})
}
})
}
/**
* Using the path to a widget instance file, find the metadata.
* @param path
*/
function getWidgetAndWidgetInstanceMetadata(path) {
// Load the metadata for the base widget.
return readMetadata(path, constants.widgetMetadataJson).then(widgetMetadata => {
if (widgetMetadata) {
return getWidgetInstanceMetadata(path, widgetMetadata)
} else {
// This can happen in transfer mode.
warn("cannotUpdateWidget", {path})
return null
}
})
}
/**
* Create a widget instance of the same name as that given in the path.
* @param widgetMetadata
* @param path
*/
function createMatchingWidgetInstance(widgetMetadata, path, updateCache = true) {
// Get the metadata for the local instance.
const localWidgetInstanceMetadata = readMetadataFromDisk(path, constants.widgetInstanceMetadataJson)
const displayName = localWidgetInstanceMetadata.displayName
const safeDisplayName = `${displayName} (Renamed ${new Date().toISOString()})`
// Set up the JSON for the clone.
const payload = {
widgetDescriptorId: widgetMetadata.widgetType,
displayName: displayName
}
// Firstly, clone an instance of the same name then update the cache so it
// now contains info on the new widget. By default return results but if
// we got a status error then we want to return a 'retry promise'. We can
// only do the auto-fix if the server supports the endpoint so need to
// check for that too.
if (endPointTransceiver.serverSupports("createWidgetInstanceAtVersion")) {
// Pull in the version we want to create which is required for the
// new endpoint.
payload.version = localWidgetInstanceMetadata.version
return endPointTransceiver.createWidgetInstanceAtVersion([], request().withBody(payload))
.then((results) => {
if (inTransferMode() && autoFix()
&& results.response.statusCode === 400 && results.data.errorCode == '33011') {
// Widget instance with same name already exists.. need to deal with it.
warn('renameWidgetInstanceWarning', {name: displayName})
// Need to find the conflicting instance by name as we'll need the
// repo ID to call the endpoint.
const existingInstance =
findCachedWidgetInstanceMatchingDisplayName(localWidgetInstanceMetadata)
const updateMetaPayload = {
metadata: {
displayName: safeDisplayName,
}
}
if (existingInstance) {
return endPointTransceiver.updateWidgetMetadata([existingInstance.repositoryId],
request().withBody(updateMetaPayload))
.tap(results => processPutResultAndEtag(path, results, syncWidgetInstanceMetadata))
.then(() => {
return endPointTransceiver.createWidgetInstanceAtVersion([], request().withBody(payload))
.then(() => updateCache && cacheWidgetInstances())
})
}
}
// Otherwise we can proceed as normal.
if (updateCache) {
cacheWidgetInstances()
}
return results
})
} else {
// This is the old method were we create a widget instance of the latest
// version. It might be the wrong thing now but we can only do the new
// method and auto-fix if the server supports it. Retaining this code
// for backwards compat.
return endPointTransceiver.createWidgetInstance([], request().withBody(payload))
.then(() => updateCache && cacheWidgetInstances())
}
}
/**
* This is for when the widget does not exist on the target server and so needs to be created.
* @param path
*/
function putWidget(path) {
// Get the metadata for the widget.
const localWidgetMetadata = readMetadataFromDisk(path, constants.widgetMetadataJson)
// If we grabbed a non-Oracle widget from a pre-17.6 system there will not be enough information to create it again.
// This is an edge case but we need to do the right thing.
if (localWidgetMetadata.source !== 101) {
warn("insufficientInfoToCreateWidget", {widgetName: localWidgetMetadata.displayName})
return
}
// Need element endpoint to - make sure it exists.
if (!endPointTransceiver.serverSupports("getElements")) {
warn("widgetCannotBeCreated", {path})
return
}
// Need to reset the etags for the widget.
resetEtagsFor(path)
// Call the widget creator to do the business.
return createWidgetInExtension(localWidgetMetadata.displayName, localWidgetMetadata.widgetType, localWidgetMetadata.global, path, false)
}
/**
* Send a widget JavaScript file back up to the server.
* @param path
* @returns A BlueBird promise.
*/
function putWidgetModuleJavaScript(path) {
return readMetadata(path, constants.widgetMetadataJson).then(metadata => {
if (metadata) {
if (inTransferMode()) {
// Er.. If we're in transfer mode then things are slightly different...
// We can't rely on the etag so we need to try and create the extension module
// and if that fails because it already exists we'll update the existing one.
// (A future server change will be made to streamline this API.)
// Call the endpoint, passing in the widget ID and the base file name of the .js file.
return endPointTransceiver.createWidgetDescriptorJavascriptExtension(
[metadata.repositoryId, basename(path)], request().fromPathAs(path, "source").ignoring(409)).tap(
(results) => processPutResultAndEtag(path, results))
.then((results) => {
if (results.response.statusCode == 409) {
return endPointTransceiver.updateWidgetDescriptorJavascriptExtension(
[metadata.repositoryId, basename(path)], request().fromPathAs(path, "source").withEtag(metadata.etag)).tap(
(results) => processPutResultAndEtag(path, results))
} else {
return results
}
})
}
// Get the base metadata for the widget.
const moduleEtag = eTagFor(path)
if (moduleEtag) {
// Call the endpoint, passing in the widget ID and the base file name of the .js file.
return endPointTransceiver.updateWidgetDescriptorJavascriptExtension(
[metadata.repositoryId, basename(path)], request().fromPathAs(path, "source").withEtag(metadata.etag)).tap(
(results) => processPutResultAndEtag(path, results))
} else {
// OK this is a new module, create the tracking directory and
// call the create endpoint, passing in the widget ID and the base file name of the .js file.
makeTrackingDirTree(path)
return endPointTransceiver.createWidgetDescriptorJavascriptExtension(
[metadata.repositoryId, basename(path)], request().fromPathAs(path, "source")).tap(
(results) => processPutResultAndEtag(path, results))
}
}
})
}
/**
* Find all etags associated with the supplied path and reset them.
* @param path
*/
function resetEtagsFor(path) {
// Chop the directory up so we can insert the tracking dir.
const splitDirs = splitFromBaseDir(path)
const baseDir = splitDirs[0], subDir = splitDirs[1]
// Walk through the tracking dir looking for etags.
walkDirectory(`${baseDir}/${constants.trackingDir}/${subDir}`, {
listeners: {
file: (root, fileStat, next) => {
const fullPath = upath.resolve(root, fileStat.name)
// Replace any etag files with dummies.
if (fullPath.endsWith(constants.etagSuffix)) {
resetEtag(fullPath)
}
// Jump to the next file.
next()
}
}
})
}
/**
* Look and see if the widget instance exists. If not, reset the etag files then we create the instance on the server.
* @param path
*/
function putWidgetInstance(path) {
// Get the metadata for the widget.
const localWidgetInstanceMetadata = readMetadataFromDisk(path, constants.widgetInstanceMetadataJson)
// See if it exists on the server.
if (!getCachedWidgetInstanceFromMetadata(localWidgetInstanceMetadata)) {
// Make sure the etags are sorted as we don't want opt lock issues later.
resetEtagsFor(path)
// Now create the instance on the server but don't update the cache; we will do that at the end.
const widgetMetadata = readMetadataFromDisk(path, constants.widgetMetadataJson)
warn("creatingWidgetInstance", {path: path})
return createMatchingWidgetInstance(widgetMetadata, path, false)
}
}
/**
* Put the element instance metadata - as this is only for elementized widgets,
* this has to be done by combining it with an update to the display template.
* @param path
* @return {*}
*/
function putElementInstanceMetadata(path) {
// This will be handled by the display template update.
return putWidgetInstanceTemplate(`${dirname(path)}/${constants.displayTemplate}`)
}
exports.putElementInstanceMetadata = putElementInstanceMetadata
exports.putWebContentWidgetInstanceTemplate = putWebContentWidgetInstanceTemplate
exports.putWidget = putWidget
exports.putWidgetBaseTemplate = putWidgetBaseTemplate
exports.putWidgetBaseLess = putWidgetBaseLess
exports.putWidgetBaseSnippets = putWidgetBaseSnippets
exports.putWidgetConfigJson = putWidgetConfigJson
exports.putWidgetConfigSnippets = putWidgetConfigSnippets
exports.putWidgetInstance = putWidgetInstance
exports.putWidgetInstanceLess = putWidgetInstanceLess
exports.putWidgetInstanceSnippets = putWidgetInstanceSnippets
exports.putWidgetInstanceTemplate = putWidgetInstanceTemplate
exports.putWidgetJavaScript = putWidgetJavaScript
exports.putWidgetModuleJavaScript = putWidgetModuleJavaScript
exports.putWidgetModifiableMetadata = putWidgetModifiableMetadata
exports.putWidgetInstanceModifiableMetadata = putWidgetInstanceModifiableMetadata