Commit 20efb13a authored by Michael Seaholm's avatar Michael Seaholm

Merge branch 'feature/cos-19' of gitlab.entropy.cc:blacklight/bl-edit into release

parents e133a3e7 8c3f515b
......@@ -154,7 +154,7 @@ module.exports = function(parentPath, slingConnector, options, cb){
var id=type + (pageName?(":" + pageName):"");
if(allTypes.indexOf(id)<0){
var component = componentRegistry.get(type), includeIt=false;
var title = component?component.title:"";
var title = pageRules.title || (component?component.title:"");
var parts = type.split("/"), componentClass, longName=[];
for (var i = parts.length; i-- > 0 && !componentClass; ){
if(/^(content|pages|layouts)$/.test(parts[i])){componentClass=parts[i];}
......@@ -171,12 +171,19 @@ module.exports = function(parentPath, slingConnector, options, cb){
var extra=parts[2];
componentClass=componentClass + "-secondary";
if(parts[2]==="pages"){extra = parts[3];}
title=prettyName(extra) + " / " + title;
if(!pageRules.title){title=prettyName(extra) + " / " + title};
}
byCategory[category]=byCategory[category] || [];
byCategory[category].push({ type:type, componentClass, longName:longName.join(" / "), icon: componentIcons[componentClass], title, name: (pageName==="*" ? "" : pageName), unavailable:existingChildren.indexOf(pageName)>-1?"unavailable":false });
if(pageName==="*" && pageRules.allowed){
_.each(pageRules.allowed, (options, allowedName)=>{
byCategory[category].push({ type:type, componentClass, longName:longName.join(" / "), icon: componentIcons[componentClass], title: options.title || title, name: allowedName, unavailable:existingChildren.indexOf(allowedName)>-1?"unavailable":false });
});
}else{
byCategory[category].push({ type:type, componentClass, longName:longName.join(" / "), icon: componentIcons[componentClass], title, name: (pageName==="*" ? "" : pageName), unavailable:existingChildren.indexOf(pageName)>-1?"unavailable":false });
}
allTypes.push(id);
}
}
......
......@@ -204,7 +204,7 @@ function loadComponent(rtype, roots, widgetRegistry, hb){
widget.hbsValidName = _.map(widget.name.split('/'), (nameSegment)=>{
return /[^\w^:]/.test(nameSegment) ? '['+nameSegment +']' : nameSegment;
}).join('/');
widget.lockAllowed = widget.widget !== 'hidden';
widget.lockAllowed = widget.widget !== 'hidden' && !widget.readonly && !widget.diabled;
widget.hide_on_edit_page = widget.widget === 'hidden';
widget.wingo="bigno";
});
......
const SC = require("sling-connector");
const _=require("lodash");
const defaultSite = _.get(global.bl,["config","environment","defaultSite"]);
const defaultSC = new SC(_.get(global.bl.config,[defaultSite, "modes", "author", "sling"]));
const log = global.bl.logger.get("blacklight.post-data");
/*
The purpose of this queue is simply to post data to the indicated SC
*/
module.exports.maxRetries = 10;
module.exports.retryInterval = 500; // 500 ms between retrying failed job
module.exports.taskInterval = 100; // 500 ms between queue processing
module.exports.process = (params, cb)=>{
var {path, form , attempt} = params;
let sc = defaultSC;
if(!path || !form){
cb('Insufficient data provided to the post-data queue');
}else{
let targetPath = path;
let formParams = form;
let submitRequest = (touchErr)=>{
if(touchErr){
log.error("Couldn't touch. Not trying POST", {path, form});
cb(touchErr);
}else{
log.debug('Attempting POST to ' + path);
sc.post(targetPath, formParams, (err)=>{
if(err){
if(attempt < module.exports.maxRetries){
log.warning(`Failed to POST to ${path} on attempt ${attempt}`);
}else{
log.error(`Failed to POST to ${path} on attempt ${attempt}`, {targetPath, formParams});
}
}
cb(err);
});
}
};
/**************************
* Removing all of this code around touching for now.
* I think it's ultimiately overkill now that there's a 100ms
* delay between posts.
*/
//we only want to go with the touch approach when we're under or on a page
// if(_.includes(targetPath, "/jcr:content")){
// //do a 'touch' to ensure there are no current conflicts
// let jcrPath = targetPath.split('/jcr:content')[0] + '/jcr:content';
// //first ensure the path exists
// sc.getSling(`/bin/exists.json?path=${jcrPath}`, (err, data) => {
// if (!data || !data.exists) {
// log.debug(`${jcrPath} doesn't exist. Not touching.`);
// //page doesn't exst, can't touch
// submitRequest();
// }else{
// log.debug(`${jcrPath} exists. Touching.`);
// //make sure all is good before proceeding
// module.exports.touch(sc, jcrPath, submitRequest);
// }
// });
// }else{
submitRequest();
// }
}
};
//keep retring saving to the path until success, or until 30 seconds has passed
module.exports.touch = (sc, path, touchCb, attempt) => {
attempt = attempt || 0;
if(attempt < 60){
sc.post(path, {'bl:touch': (new Date()).getTime() + ""}, (err)=>{
if(!err){
log.debug(`Successfully touched ${path} on attempt ${attempt}`);
touchCb();
}else{
setTimeout(()=>{module.exports.touch(sc, path, touchCb, attempt + 1)}, 500);
}
});
}else{
let touchErr = `Failed to successfully touch ${path} after 60 attempts`;
log.error(touchErr)
touchCb(touchErr);
}
}
\ No newline at end of file
......@@ -8,6 +8,7 @@ const log = global.bl.logger.get("blacklight.restub");
var defaultSite = _.get(global.bl,["config","environment","defaultSite"]);
var defaultSC = new SC(_.get(global.bl.config,[defaultSite, "modes", "author", "sling"]));
var isBlQuery = /\/bl:query$/;
/**
......@@ -22,6 +23,20 @@ module.exports = function restubFromBlQuery(params, cb){
var {blQueryPath, sc} = params;
sc = sc || defaultSC;
// function to add jobs to the post update queue
let addPostJob = (path, form) =>{
const postQueue = global.bl.queues.get("blacklight.edit.post-data");
let tasks={prototype:{}, tasks:[{path, form}]};
log.debug('Restub query job', tasks);
postQueue.makeJob(tasks,(err,job)=>{
if(err){log.error("Couldn't add job to 'post-data' queue", err);}
else{
log.info("Success adding job to 'post-data' queue:", job.id);
}
});
};
if(!isBlQuery.test(blQueryPath)){cb("Provided 'blQueryPath' does not end with 'bl:query': " + blQueryPath); return;}
......@@ -36,7 +51,7 @@ module.exports = function restubFromBlQuery(params, cb){
//we remove the note about processing when the processing is done
let origCb = cb;
cb = (cbErr) => {
sc.post(path, {'bl:processing@Delete': "true" }, _.noop);
addPostJob(path, {'bl:processing@Delete': "true" });
origCb(cbErr)
}
......@@ -51,6 +66,18 @@ module.exports = function restubFromBlQuery(params, cb){
var sourceMapping = querySpecification.sourceMapping;
var allowLocalChildren = querySpecification.allowLocalChildren || false;
if(querySpecification.writeStubTo){
if(!_.isArray(querySpecification.writeStubTo)){
querySpecification.writeStubTo = [querySpecification.writeStubTo];
}
querySpecification.writeStubToTransform = querySpecification.writeStubToTransform ||{
replace: '' ,
with: ''
}
}
if(!queryPath){cb("No 'bl:query' property found at path: " + path); return;}
if(!_.isArray(resourceTypes)){cb("Must be an array. Bad 'resourceTypes' value at bl:query path: " + path); return;}
if(resourceTypes.length !== targetParts.length){cb("There must be one 'resourceTypes' entry for each target child node pattern component at path: " + path); return;}
......@@ -278,6 +305,13 @@ module.exports = function restubFromBlQuery(params, cb){
allNewNodes.push({name:_path.join(curParentPath,"sling:resourceType"), value:resourceType})
}
}
if(querySpecification.writeStubTo){
_.each(querySpecification.writeStubTo ,(path) =>{
let writeToStubValue = sourcePath.replace(new RegExp(querySpecification.writeStubToTransform.replace), querySpecification.writeStubToTransform.with);
allNewNodes.push({name: pageMode ? _path.join(curTargetPath, "jcr:content", path) : _path.join(curTargetPath, path), value: writeToStubValue});
});
}
log.debug("creating:", _path.join(targetPath, curTargetPath), " of type ", resourceType);
}
......@@ -320,27 +354,6 @@ module.exports = function restubFromBlQuery(params, cb){
// TODO: update local bl:query params to exclude local page, upon delete of that local page
//start dealing with deactivation requests and create promises for it
var deactivatePage = function(path) {
return new Promise((resolve) => {
sc.post("/bin/replicate.json", {path: path, cmd: 'deactivate'}, (err) => {resolve();});
});
}
//deactive one at a time to try and fix org.apache.sling.api.resource.PersistenceException
var deactivatePages = function(paths) {
var p = Promise.resolve();
paths.forEach(function(path){
p = p.then(function(){
return deactivatePage(path);
});
});
return p;
};
var deactivatePromise = deactivatePages(deactivations);
let throttledRequests = [];
let reqIdx = 0;
......@@ -368,33 +381,20 @@ module.exports = function restubFromBlQuery(params, cb){
});
let next = function(err){
if(!err){
if(throttledRequests.length){
var formParams = throttledRequests.shift();
sc.post(targetPath, formParams, (err)=>{
if(err){
console.log('Error processing stub POST', {targetPath, formParams});
}
next(err);
});
}else{
cb();
}
}else{
cb(err);
return;
}
}
//wait for deactivations to finish
deactivatePromise.then(() => {
next();
/***************************/
// actually add our params to the queue for processing
/***************************/
_.each(deactivations, deactivation => {
addPostJob("/bin/replicate.json", {path: path, cmd: 'deactivate'});
});
_.each(throttledRequests, (throttled)=>{
addPostJob(targetPath, throttled);
});
log.info('Added ' + (_.size(deactivations) + _.size(throttledRequests)) + ' post update jobs.' );
cb();
});
}
});
......
......@@ -4,6 +4,7 @@ const _=require("lodash");
const defaultSite = _.get(global.bl,["config","environment","defaultSite"]);
const defaultSC = new SC(_.get(global.bl.config,[defaultSite, "modes", "author", "sling"]));
const config = global.bl.config.environment;
const log = global.bl.logger.get("blacklight.restub-move");
/*
......@@ -23,7 +24,7 @@ module.exports = function restubFromMove(params, cb){
var {moveInfo, sc} = params;
sc = sc || defaultSC;
console.log('made it to the restubFromMove', moveInfo);
log.debug('made it to the restubFromMove', moveInfo);
if(moveInfo && moveInfo.originalPath && moveInfo.newPath && moveInfo.distanceMoved === 0){
let originalNodeName = _.last(moveInfo.originalPath.split('/'));
......@@ -55,7 +56,7 @@ module.exports = function restubFromMove(params, cb){
let url = buildQueryUrl(prop);
sc.getSling(url,{leaveMangledNames:true}, (err, matches)=>{
if(err){
console.log("Couldn't query Sling service for " + prop, err);
log.error("Couldn't query Sling service for " + prop, err);
queryCb(err);
}else{
if(matches.length){
......@@ -68,7 +69,7 @@ module.exports = function restubFromMove(params, cb){
}
});
}else{
console.log('No ' + prop + ' entries found for this move');
log.warn('No ' + prop + ' entries found for this move');
}
}
......@@ -79,7 +80,7 @@ module.exports = function restubFromMove(params, cb){
/////////////////////////////
let doneQuerying = (err)=>{
console.log('done querying: ', {err, updates, renames});
log.debug('done querying: ', {err, updates, renames});
if(err){
cb(err)
}else{
......@@ -89,7 +90,6 @@ module.exports = function restubFromMove(params, cb){
let nextOp = ()=>{
if(allOperations.length){
let curOp = allOperations.shift();
console.log('curOp', curOp);
let formParams = {}, slingTarget;
if(curOp.prop){
formParams[curOp.prop] = curOp.updatedValue;
......@@ -99,7 +99,7 @@ module.exports = function restubFromMove(params, cb){
formParams[':dest'] = curOp.to;
slingTarget = curOp.from;
}
console.log('post info', {slingTarget, formParams});
log.debug('post info', {slingTarget, formParams});
sc.post(slingTarget, formParams, (err)=>{
if(err){
cb(err);
......@@ -131,7 +131,7 @@ module.exports = function restubFromMove(params, cb){
let oldStubPrefix = pathParts.splice(0, pathParts.length - (pageMode ? 2 : 1)).join('/');
let oldStubbedNodeName = pathParts[0];
console.log('looking for rename', {path, oldStubPrefix, oldStubbedNodeName});
log.debug('looking for rename', {path, oldStubPrefix, oldStubbedNodeName});
if(oldStubbedNodeName === originalNodeName){
renames.push({from: `${oldStubPrefix}/${originalNodeName}`, to: `${oldStubPrefix}/${newNodeName}`});
}
......
......@@ -90,6 +90,11 @@ module.exports.remapFlattened = function remapFlattened(flattenedInput, mappable
item.oldPath=item.value; item.value=newPath;
}
if(_.endsWith(item.name, 'bl:query')){
//add in the bl:processing node
flattenedResult.push({name: item.name.replace("bl:query", 'bl:processing'), value: "true"});
}
}
flattenedResult.push(item);
}
......
var SC = require("sling-connector");
var restubFromBlQuery = require("./lib/restub-from-blquery");
var restubFromMove = require("./lib/restub-from-move");
var postData = require("./lib/post-data");
var performMigration = require("./lib/migrations").performMigration;
var _=require("lodash");
const _=require("lodash");
const log = global.bl.logger.get("blacklight.queues");
var queues = module.exports;
var config=global.bl.config;
......@@ -12,7 +14,6 @@ var docx=/":docx/g ;
var jcrIndex=/\[(\d)\]\"\:/g;
// console.log("SITE:", global.bl.modules)
// TODO: store details of source connection
......@@ -37,7 +38,7 @@ queues.transfer={
var sourceSC=_.get(global.bl.config,[task.sourceSite,"modes",task.sourceMode, "sling"]);
sourceSC = new SC(sourceSC);
console.log("Transfering: ", task.url, "to", targetSC.baseUri);
log.debug("Transfering: ", task.url, "to", targetSC.baseUri);
var path = task.url.replace(/\/$/,"") + "/jcr:content"
var sourcePath = path + ".infinity.json";
......@@ -55,7 +56,7 @@ queues.transfer={
targetSC.import(path,data,{}, function(err, result, resp, respBody){
if(err){
console.error("Transfer error: ", path, "\n", data, "\nErr:",err,"\n");
log.error("Transfer error: ", path, "\n", data, "\nErr:",err,"\n");
cb({message:"ERROR replicating:" + sourcePath, error: err}); return;
}
cb(null, targetSC.baseUri + sourcePath)
......@@ -66,9 +67,9 @@ queues.transfer={
complete:(err, task, job, results)=>{
if(err){
console.log("Back in bl.edit.queue.js. Transfer failed:", err);
log.error("Back in bl.edit.queue.js. Transfer failed:", err);
}else{
console.log("OK. Back in bl.edit.queue.js. Transfer complete for job:",job.id);
log.info("OK. Back in bl.edit.queue.js. Transfer complete for job:",job.id);
}
}
}
......@@ -100,9 +101,9 @@ queues["mapped-stubs"]={
complete:(err, task, job, results)=>{
if(err){
console.log("mapped-stub queue failed when processing changes to collection data:", err);
log.error("mapped-stub queue failed when processing changes to collection data:", err);
}else{
console.log("mapped-stub job complete, id:",job.id);
log.info("mapped-stub job complete, id:",job.id);
}
}
}
......@@ -127,9 +128,9 @@ queues["mapped-stubs-rename"]={
complete:(err, task, job, results)=>{
if(err){
console.log("rename mapped-stub queue failed:", err);
log.error("rename mapped-stub queue failed:", err);
}else{
console.log("mapped-stub job complete, id:",job.id);
log.info("mapped-stub job complete, id:",job.id);
}
}
}
......@@ -137,6 +138,39 @@ queues["mapped-stubs-rename"]={
/*************************************************************************************************/
/*************************************************************************************************/
/*************************************************************************************************/
queues["post-data"]={
description: "Queue for POSTing data to a sling connector",
displayMap: (task, taskPrototype)=>{
return {title:task.path, link: task.path}
},
on:{
process:(task, job, cb)=>{
postData.process({path: task.path, form: task.form, attempt: task.attemptCount}, cb);
},
complete:(err, task, job, results)=>{
if(err){
log.error(`post-data queue failed after ${task.attemptCount + 1} attempts:`, err);
}else{
log.info(`post-data job complete on attempt ${task.attemptCount + 1}, id:`,job.id);
}
}
},
config: {
maxRetries : postData.maxRetries,
retryInterval : postData.retryInterval,
taskInterval: postData.taskInterval
}
}
/*************************************************************************************************/
/*************************************************************************************************/
......@@ -158,9 +192,9 @@ queues.migrations={
complete:(err, task, job, results)=>{
if(err){
console.log("migrations job failed:", job.id, err);
log.error("migrations job failed:", job.id, err);
}else{
console.log("migrations job complete, id:", job.id);
log.info("migrations job complete, id:", job.id);
}
}
}
......
{{#each annotations}}
{{#each annotations}}
<div class="row annotations-widget z-depth-2">
<div class="col s12" style="background-color:{{color}};">
<table class="highlight" style="background-color: #fafafa;">
{{#each entries}}
{{#ifop author "&&" message}}
<tr>
<th style="width: 1%; white-space: nowrap">{{author}}</th>
<td style="white-space:normal;padding: 15px 5px;color:black;">{{message}}</td>
</tr>
{{/ifop}}
{{/each}}
</table>
</div>
</div>
{{/each}}
{{else}}
<div class="row">
<ul class="collection"><li class="collection-item">No annotations found. Switch to Preview mode to add annotations.</li></ul>
</div>
{{/each}}
\ No newline at end of file
......@@ -9,27 +9,33 @@ module.exports.process=function(data,$,cb){
if(req.method === "GET"){
var pagePath = req.query.path;
var jsonData = req.originalUrl.indexOf('/annotations.js') > -1;
if(pagePath){
var slingPath = pagePath.indexOf('jcr:content') > -1 ? pagePath : pagePath + '/jcr:content';
$.sc.getSling(slingPath + '.infinity.json',{leaveMangledNames: true},function(err, slingData){
if(!err && slingData){
var aggregate = [];
module.exports.findBlAnnotations(slingData, slingPath, aggregate);
res.json(aggregate);
if(jsonData){
res.json(aggregate);
cb(null, false);
}else{
data.annotations = aggregate;
cb();
}
}else{
res.status(500).send('Error getting pagePath data: ' + err);
cb(null, false);
}
});
}else{
res.status(500).send("pagePath is required for GET annotations");
cb(null, false);
}
}
cb(null, false);
return false;
};
//////////////////
......
......@@ -10,9 +10,6 @@
{{{mapping}}}
</div>
<div class="annotations-body" style="display:none;">
{{{annotations}}}
</div>
</form>
<script type="text/javascript">
......
......@@ -3,7 +3,6 @@ var _=require("lodash");
var util=require("util");
// var blMapRender=require("./bl-map-render");
var blMapRender=require("../../../widgets/mapping-widget/mapping-widget").renderDialogPage;
var annotationsRender=require("./annotations-render");
var sanitize = global.bl.sanitize;
......@@ -156,10 +155,13 @@ module.exports.process=function(data,$,cb){
}
if(key.indexOf('/bl:templated/') > -1){
let splitParts = key.split('/bl:map/bl:templated/');
let relativeSlingPath = splitParts.join('/');
let templateValueParts = splitParts[0].split('/').concat(['bl:map', 'bl:templated', splitParts[1]]);
let mapAtRoot = key.indexOf('bl:map/bl:templated/') === 0;
let splitParts = key.split((mapAtRoot ? '' : '/') + 'bl:map/bl:templated/');
let relativeSlingPath = mapAtRoot ? splitParts[1] : splitParts.join('/');
let templateValueParts = (mapAtRoot ? [] : splitParts[0].split('/')).concat(['bl:map', 'bl:templated', splitParts[1]]);
let templateValue = _.get(slingData, templateValueParts);
//console.log('templated handling', {key, splitParts, relativeSlingPath, templateValueParts, templateValue});
_.set(slingData, relativeSlingPath.split('/'), templateValue);
templatedFields.push(relativeSlingPath);
}
......@@ -272,10 +274,9 @@ module.exports.process=function(data,$,cb){
var mapping=slingData ? blMapRender(blMaps, slingData['sling:resourceType']) : '';
var annotations = slingData ? annotationsRender(slingData, slingPath) : '';
var dialogContent = $.template({body:dialogBody, mapping, annotations, lock:inheritFrom?true:false, widgetConfigs:configs, dialogConfig: dialogConfig, title: dialogConfig.title, blMaps: blMaps});
var dialogContent = $.template({body:dialogBody, mapping, lock:inheritFrom?true:false, widgetConfigs:configs, dialogConfig: dialogConfig, title: dialogConfig.title, blMaps: blMaps});
res.send(dialogContent);
res.end();
}catch(err){
......@@ -288,4 +289,4 @@ module.exports.process=function(data,$,cb){
cb(null, false);
}
\ No newline at end of file
}
const _ = require('lodash');
const fs = require('fs');
var _path=require("path");
module.exports.process=function(data, $, cb){
var sc=$.sc;
var res=$.express.res;
var mainPath = "/" + $.page.action;
//handle virtual paths
if(/\/-/.test(mainPath)){
mainPath = mainPath.substring(0, mainPath.indexOf('/-'));
}
let tagsMode = /^\/etc\/tags/.test(mainPath);
let mediaMode = /^\/content\/(dam|media)\//.test(mainPath);
let routesMode = /^\/alt\/apps\//.test(mainPath);
let sendError = (errorMessage)=>{
res.end(errorMessage)
cb(null,false);
}
let sendSuccess = (html) => {
res.setHeader('Content-Type', "text/html; charset=utf-8");
res.end(html);
cb(null,false);
};
let loadDocumentationPath = (documentationPath, pageMode, success, fail) => {
fs.access(documentationPath, (accessError)=>{
if(accessError){
fail({exists: false});
}else{
//read in the file
fs.readFile(documentationPath, 'utf-8', (err, mdFile)=>{
if(err){
fail({exists: true});
}else{
var html=$.templates["doc-page"]({body : global.bl.marked(mdFile), blConfig:$.blConfig, includePreviewControls: pageMode, path : mainPath});
success(html);
}
});
}
});
};
if(sc.mode === 'author'){
if(tagsMode || mediaMode){
sendError("Documentation not supported here.");
}else{
if(routesMode){
const appsRoot =_path.join(global.bl.appRoot, "apps");
let documentationPath = appsRoot + mainPath.replace('/alt/apps/', '/').replace('/tools/', '/routes/tools/') + '/' + _.last(mainPath.split('/')) + '.md';
loadDocumentationPath(documentationPath, false, sendSuccess, (fail)=>{
if(fail.exists){
sendError(`Route '${mainPath}' : Error reading documentation file.`);
}else{
sendError(`Route '${mainPath}' has no documentation.`);
}
} );
}else{
let pageMode = mainPath.indexOf('jcr:content') < 0;
let slingPath = (pageMode ? mainPath + "/jcr:content" : mainPath) + ".1.json";
sc.getSling(slingPath, {leaveMangledNames: true},function(err, slingData){
if(err){
sendError("No page found. Documentation not supported here.")
}else{
let rtype=slingData["sling:resourceType"];
if(!rtype){
sendError("No resource type found. Documentation not supported here.");
}else{
let comp =$.componentRegistry.get(rtype);
if(!comp){
sendError(`Invalid resource type found: ${rtype} Documentation not supported here.`);
}else{
//does this type have an md file
let documentationPath = comp.path + '/' + _.