AEM CQ 56 - Add Scheduled Activations to Activate Later

Goal


In this post we are going to add the necessary logic to show scheduled activations of page or group of pages in the Activate Later dialog of siteadmin console (http://localhost:4502/siteadmin). Package is available for install, Source code and Demo video are available for download


Activate Later




Activate Later Extension 



Prerequisites


If you are new to CQ

1) Read this post on how to create a sample page component

2) Read this post on how to setup your IDE and create an OSGI component

Solution


1) There are two parts. First, code servlet to return the scheduled activations of page.

2) Add Scheduled Activations grid to the siteadmin console Activate Later dialog.

Create Servlet


1) The first step is creating a servlet and registering it as an OSGI component in CQ. Read this post on how to create an OSGI component for deploying to CQ.

2) Create servlet ScheduledActivationInstances and add the following code

package apps.mysample.scheduledactivations;

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/schactivation",
        methods = "GET",
        metatype = false,
        label = "Scheduled Activation Instances"
)
public class ScheduledActivationInstances extends SlingAllMethodsServlet {
    private static final Logger log = LoggerFactory.getLogger(ScheduledActivationInstances.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);
        }
    }
}

3) Deploy the servlet and check if its working by accessing url http://localhost:4502/bin/mycomponents/schactivation?path=/content/geometrixx/es. If the page /content/geometrixx/es has scheduled activations set, you should see the following response

{"data":[{"path":"/content/geometrixx/es","dt":"Fri, 15 Nov, 2013 12:24","id":"/etc/workflow/instances/2013-11-08/model_12548425989383","name":"Español","st":"Fri, 08 Nov, 2013 12:24","ini":"admin"},{"path":"/content/geometrixx/es","dt":"Sat, 07 Dec, 2013 16:17","id":"/etc/workflow/instances/2013-11-08/model_26521845578422","name":"Español","st":"Fri, 08 Nov, 2013 16:17","ini":"admin"}]}


Extend SiteAdmin UI


The next step is extending siteadmin console UI and add Activations grid to Activate Later dialog

1) Login to CRXDE Lite, create folder (nt:folder) /apps/schedactivations

2) Create clientlib (type cq:ClientLibraryFolder/apps/schedactivations/clientlib and set a property categories of String type to cq.widgets

3) Create file ( type nt:file ) /apps/schedactivations/clientlib/js.txt, add the following

                         schdactivations.js

4) Create file ( type nt:file ) /apps/schedactivations/clientlib/schdactivations.js, add the following code

CQ.Ext.ns("MyClientLib");

MyClientLib.SiteAdmin = {
    SA_GRID: "cq-siteadmin-grid",
    ACTIVATE_BUT: "Activate",
    ACTIVATE_LATER_BUT: "Activate Later...",
    ACTIVATE_LATER_DID: "cq-activate-later-dialog",
    ACTIVATIONS_GRID_ID_PREFIX: "myclientlib-scheduled-activations-grid",

    getGrid: function(config){
        var store = new CQ.Ext.data.Store({
            baseParams: config.storeBaseParams,
            proxy: new CQ.Ext.data.HttpProxy({
                "autoLoad":false,
                url: "/bin/mycomponents/schactivation",
                method: 'GET'
            }),
            reader: new CQ.Ext.data.JsonReader({
                root: 'data',
                fields: [
                    {name: 'id', mapping: 'id'},
                    {name: 'name', mapping: 'name'},
                    {name: 'path', mapping: 'path'},
                    {name: 'actDate', mapping: 'dt'},
                    {name: 'startDate', mapping: 'st'},
                    {name: 'initiator', mapping: 'ini'}
                ]
            })
        });

        store.load();

        return new CQ.Ext.grid.GridPanel({
            store: store,
            id: config.gridId,
            colModel: new CQ.Ext.grid.ColumnModel({
                defaults: {
                    width: 120,
                    sortable: true
                },
                columns: [
                    {id: 'name', header: 'Name', width: 80, dataIndex: 'name'},
                    {id: 'path', header: 'Path', width: 160, dataIndex: 'path'},
                    {id: 'actDate', width: 160, header: config.actColHeader, dataIndex: 'actDate'},
                    {id: 'startDate', header: 'Start Date', dataIndex: 'startDate'},
                    {id: 'initiator', header: 'Initiator', width: 80, dataIndex: 'initiator'}
                ]
            }),
            sm: new CQ.Ext.grid.RowSelectionModel(),
            tbar: [{
                xtype: "tbbutton",
                text: 'Terminate',
                disabled: true,
                tooltip: 'Terminate the selected workflows',
                handler: function(){
                    var commentBox = new CQ.Ext.form.TextArea({
                        xtype: 'textarea',
                        name:'terminateComment',
                        fieldLabel:CQ.I18n.getMessage('Comment')
                    });

                    var tConfig = {
                        xtype: 'dialog',
                        title:CQ.I18n.getMessage('Terminate Workflow'),
                        params: {"_charset_":"utf-8"},
                        items: {
                            xtype:'panel',
                            items:[commentBox,{
                                xtype: 'hidden',
                                name:'state',
                                value:'ABORTED'
                            }]
                        },
                        buttons:[{
                            "text": CQ.I18n.getMessage("OK"),
                            "handler": function() {
                                var sGrid = CQ.Ext.getCmp(config.gridId);

                                var sFunc = function(options, success, response) {
                                    if (!success) {
                                        CQ.Ext.Msg.alert(CQ.I18n.getMessage("Error"),
                                            CQ.I18n.getMessage("Termination of workflow failed"));
                                    }else{
                                        sGrid.getStore().reload();
                                    }
                                };

                                CQ.Ext.each(sGrid.getSelectionModel().getSelections(), function(selection){
                                    CQ.HTTP.post(selection.id,sFunc,{
                                            "state":"ABORTED",
                                            "_charset_":"utf-8",
                                            "terminateComment": commentBox.getValue()
                                        }
                                    );
                                });

                                this.close();
                            }
                        },CQ.Dialog.CANCEL ]
                    };

                    var tDialog = CQ.WCM.getDialog(tConfig);
                    tDialog.show();
                }
            }],
            width: 600,
            height: 350,
            frame: true,
            title: config.title,
            style: "margin:25px 0 0 0",
            listeners: {
                'click': function(){
                    var button = this.getTopToolbar().find('xtype','tbbutton')[0];
                    button.setDisabled(false);
                }
            }
        });
    },

    addGrid: function(grid, config){
        var toolBar = grid.getTopToolbar();

        var actBut = toolBar.find("text", config.topButtonName)[0];
        var actLaterBut = actBut.menu.find("text", config.laterButtonName);

        if(!actLaterBut || actLaterBut.length == 0){
            return;
        }

        actLaterBut[0].on('click', function(){
            var nextId = CQ.Util.createId(config.dialogIdPrefix);
            nextId = parseInt(nextId.substring(nextId.lastIndexOf("-") + 1), 10);

            var prevId = config.dialogIdPrefix + "-" + (nextId - 1);
            var dialog = CQ.Ext.getCmp(prevId);

            if(!dialog){
                return;
            }

            dialog.setWidth(700);
            dialog.setHeight(500);

            var panel = dialog.findBy(function(comp){
                return comp["jcr:primaryType"] == "cq:Panel";
            }, dialog);

            if(!panel || panel.length == 0){
                return;
            }

            panel = panel[0];
            var paths = "";

            CQ.Ext.each(grid.getSelectionModel().getSelections(), function(row){
                paths = paths+ row.id + ",";
            });

            var gConfig = {};

            gConfig.gridId = CQ.Util.createId(config.gridIdPrefix);
            gConfig.title = config.gridTitle;
            gConfig.actColHeader = config.actColHeader;
            gConfig.storeBaseParams = { path : paths.substr(0, paths.lastIndexOf(",")), type: config.gridType };

            panel.add(this.getGrid(gConfig));
            panel.doLayout();
        },this);
    },

    addScheduledActivationGrid: function(grid){
        var config = {};

        config.topButtonName = this.ACTIVATE_BUT;
        config.laterButtonName = this.ACTIVATE_LATER_BUT;
        config.dialogIdPrefix = this.ACTIVATE_LATER_DID;
        config.gridIdPrefix = this.ACTIVATIONS_GRID_ID_PREFIX;
        config.gridTitle = 'Pending Scheduled Activations';
        config.actColHeader = 'Activation Date';
        config.gridType = "ACTIVATE";

        this.addGrid(grid, config);
    }
};

(function(){
    if(window.location.pathname == "/siteadmin"){
        var INTERVAL = setInterval(function(){
            var s = MyClientLib.SiteAdmin;
            var grid = CQ.Ext.getCmp(s.SA_GRID);

            if(grid){
                clearInterval(INTERVAL);
                s.addScheduledActivationGrid(grid);
            }
        }, 250);
    }
})();


No comments:

Post a Comment