Goal
Add a new column to SiteAdmin Search Panel. Here the Status column we add, shows the scheduled activations/deactivations of a page. Package Install available for download
Related
1) Read this post on how to add a column to siteadmin grid
2) Check this post for modifying a column in siteadmin search panel grid
Solution
It's a two step process, first register servlet to get the page scheduled tasks as json, and second, add necessary UI changes
Deploy Servlet
1) Create and deploy servlet apps.mysample.searchpanelcolumn.GetScheduledActions as OSGI bundle; to get the scheduled activations/deactivations of page(s) as json. Add the following code..
package apps.mysample.searchpanelcolumn;
import com.day.cq.workflow.exec.Workflow;
import com.day.cq.workflow.metadata.MetaDataMap;
import com.day.cq.workflow.status.WorkflowStatus;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.sling.SlingServlet;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.commons.json.io.JSONWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletException;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
@SlingServlet(
paths="/bin/mycomponents/schtasks",
methods = "GET",
metatype = false,
label = "Scheduled Activation Instances"
)
public class GetScheduledActions extends SlingAllMethodsServlet {
private static final Logger log = LoggerFactory.getLogger(GetScheduledActions.class);
private static SimpleDateFormat FORMATTER = new SimpleDateFormat("EEE, dd MMM, yyyy HH:mm");
private static final String ACTIVATE_MODEL = "/etc/workflow/models/scheduled_activation/jcr:content/model";
private static final String DEACTIVATE_MODEL = "/etc/workflow/models/scheduled_deactivation/jcr:content/model";
@Override
protected void doGet(final SlingHttpServletRequest request, final SlingHttpServletResponse response)
throws ServletException, IOException {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
JSONWriter jw = new JSONWriter(response.getWriter());
String pageStr = request.getParameter("path");
try{
jw.object();
if(StringUtils.isEmpty(pageStr)){
jw.key("error").value("page input required");
jw.endObject();
return;
}
String type = request.getParameter("type");
if(StringUtils.isEmpty(type)){
type = "ACTIVATE";
}
pageStr = pageStr.trim();
String[] pages = pageStr.split(",");
ResourceResolver resolver = request.getResourceResolver();
Resource resource = null;
WorkflowStatus wStatus = null;
List<Workflow> workflows = null;
Map<String, List<Map<String, String>>> retMap = new HashMap<String, List<Map<String, String>>>();
Map<String, String> map = null;
MetaDataMap mdMap = null;
String absTime = null, id = null, version = null;
List<Map<String, String>> list = null;
for(String page: pages){
if(StringUtils.isEmpty(page)){
continue;
}
resource = resolver.getResource(page);
if(resource == null){
continue;
}
wStatus = resource.adaptTo(WorkflowStatus.class);
workflows = wStatus.getWorkflows(false);
if(CollectionUtils.isEmpty(workflows)){
continue;
}
for (Workflow w: workflows) {
id = w.getWorkflowModel().getId();
if(type.equals("ACTIVATE") && !id.equals(ACTIVATE_MODEL)){
continue;
}else if(type.equals("DEACTIVATE") && !id.equals(DEACTIVATE_MODEL)){
continue;
}
list = retMap.get(page);
if(list == null){
list = new ArrayList<Map<String, String>>();
retMap.put(page, list);
}
map = new HashMap<String, String>();
list.add(map);
mdMap = w.getMetaDataMap();
absTime = mdMap.get("absoluteTime", String.class);
map.put("id", w.getId());
map.put("name", resource.getChild("jcr:content").adaptTo(ValueMap.class).get("jcr:title", String.class));
map.put("st",FORMATTER.format(w.getTimeStarted().getTime()));
map.put("ini", w.getInitiator());
map.put("type", id.equals(ACTIVATE_MODEL) ? "ACTIVATE" : "DEACTIVATE");
map.put("dt", FORMATTER.format(Long.parseLong(absTime)));
}
}
String path = null;
Iterator<Map<String, String>> itr = null;
jw.key("data").array();
for(Map.Entry<String, List<Map<String, String>>> entry : retMap.entrySet()){
list = entry.getValue();
path = entry.getKey();
itr = list.iterator();
while(itr.hasNext()){
jw.object();
jw.key("path").value(path);
for(Map.Entry<String, String> mEntry : itr.next().entrySet()){
jw.key(mEntry.getKey()).value(mEntry.getValue());
}
jw.endObject();
}
}
jw.endArray();
jw.endObject();
}catch(Exception e){
log.error("Error getting schedule activation instances",e);
throw new ServletException(e);
}
}
}
2) Login to CRXDE Lite, create folders /apps/searchpanelstatus, /apps/searchpanelstatus/install
3) Deploy bundle with apps.mysample.searchpanelcolumn.GetScheduledActions to /apps/searchpanelstatus/install
UI Changes
1) Create folder (nt:folder) /apps/searchpanelstatus/clientlibs
2) Create folder /apps/searchpanelstatus/clientlibs/myclientlib of type cq:ClientLibraryFolder with property categories of type String set to cq.widgets
3) Add file of type nt:file /apps/searchpanelstatus/clientlibs/myclientlib/js.txt with statement
addStatusColumn.js
4) Add file of type nt:file /apps/searchpanelstatus/clientlibs/myclientlib/addStatusColumn.js with following code...
CQ.Ext.ns("MyClientLib");
MyClientLib.SearchPanel = {
addStatusColumn: function(){
CQ.wcm.SiteAdminSearchPanel.COLUMNS["status"] = {
"header":CQ.I18n.getMessage("Status"),
"id":"status",
"dataIndex":"xxxxx", //some undefined, to workaround grid weird layout issue
"renderer": function(val, meta, rec) {
if(!rec.data.scheduledTasks){
return "";
}
var qtip = "<table class='qtip-table'><tr><th>" + CQ.I18n.getMessage("Task")
+ "</th><th>" + CQ.I18n.getMessage("Scheduled") + "</th><th>";
CQ.Ext.each(rec.data.scheduledTasks, function(t){
var iconCls = (t.type == 'ACTIVATE') ? "status status-scheduledtask-activation" :
"status status-scheduledtask-deactivation";
qtip = qtip + "<tr><td class='" + iconCls + "'></td><td>"
+ t.dt + " (" + t.ini + ")</td><td>";
});
qtip = qtip + "</table>";
return "<span class=\"status status-scheduledtask\" ext:qtip=\"" + qtip + "\"> </span>";
}
};
MyClientLib.SiteAdminSearchPanel = CQ.Ext.extend(CQ.wcm.SiteAdminSearchPanel, {
constructor: function(config) {
if (config.columns) {
config.columns.push({
"xtype" : "gridcolumn",
"usePredefined": "status"
});
}
MyClientLib.SiteAdminSearchPanel.superclass.constructor.call(this, config);
}
});
CQ.Ext.reg("siteadminsearchpanel", MyClientLib.SiteAdminSearchPanel);
},
addSchdTasksInStore: function(grid){
var store = grid.getStore();
store.on('load', function(s, recs){
var pages = "";
var updateRecs = {};
CQ.Ext.each(recs, function(r){
pages = pages + r.id + ",";
updateRecs[r.id] = r;
});
if(!pages){
return;
}
pages = pages.substr(0, pages.lastIndexOf(","));
$.ajax({
url: '/bin/mycomponents/schtasks',
dataType: "json",
type: 'GET',
async: false,
data: { "path" : pages, "type" : "ALL" },
success: function(data){
if(!data || !data["data"]){
return;
}
data = data["data"];
var rec;
CQ.Ext.each(data, function(d){
rec = updateRecs[d["path"]];
if(!rec.data.scheduledTasks){
rec.data.scheduledTasks = [];
}
rec.data.scheduledTasks.push(d);
});
}
});
grid.getView().refresh();
});
}
};
(function(){
MyClientLib.SearchPanel.addStatusColumn();
if(window.location.pathname == "/siteadmin"){
var SA_INTERVAL = setInterval(function(){
var grid = CQ.Ext.getCmp("cq-siteadminsearchpanel-grid");
if(grid && (grid.rendered == true)){
clearInterval(SA_INTERVAL);
MyClientLib.SearchPanel.addSchdTasksInStore(grid);
}
}, 250);
}
})();
5) Here is the component structure in CRXDE Lite