Tech Tips

Content Fragments in Depth. AEM Workflow Management. Part 3.1

In our previous article, we shared Video and Event schema models and Brightcove in AEM implementation. After we’ve defined the Event and Video models and have synced the video data, it’s time to let authors manage their Event content fragments and pages in a well-structured AEM workflow. At the point where an Event CF is created, authors will be able to start the process of rolling it out, publishing, page creation, etc.

Starting Point of AEM Workflow with Content Fragments

A new “Custom Rollout” option is being added to the CF console

If we select any CF and click “Custom Rollout” – the following popup will appear. Each ticked checkbox will run a specific workflow for the selected path. Here we collect all the data to be passed on to the Workflow Starter, which will run all the needed workflows.

And the corresponding code: /apps/dam/gui/content/assets/.content.xml

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    jcr:primaryType="cq:Page">
    <jcr:content jcr:primaryType="cq:PageContent">
        <actions jcr:primaryType="nt:unstructured">
            <selection jcr:primaryType="nt:unstructured">
                <cf-workflow
                    granite:class=""
                    granite:rel="cq-damadmin-admin-actions-workflow-cf-activator"
                    jcr:primaryType="nt:unstructured"
                    sling:orderBefore="cf-proxy-page"
                    sling:resourceType="granite/ui/components/coral/foundation/collection/action"
                    icon="globe"
                    target=".cq-damadmin-admin-childpages"
                    text="Custom Rollout"
                    variant="actionBar">
                    <granite:data
                        jcr:primaryType="nt:unstructured"
                        foundation-mode-group="cq-damadmin-admin-childpages"
                        foundation-mode-value="default"
                        href="/mnt/overlay/dam/gui/content/assets/cf-rollout-dialog.html"/>
                </cf-workflow>
            </selection>
        </actions>
    </jcr:content>
    <cf-rollout-dialog
        granite:id="cfm-rollout-dialog"
        granite:rel="cq-damadmin-admin-actions-rollout"
        jcr:mixinTypes="[]"
        jcr:primaryType="nt:unstructured"
        jcr:title="Rollout"
        sling:resourceType="granite/ui/components/coral/foundation/dialog"
        width="650">
        <items jcr:primaryType="nt:unstructured">
            <clientlibs
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/includeclientlibs"
                categories="[<b>com.site.authoring.dialog.cf.rollout</b>,com.site.authoring.dialog.shared.ui.lib]"/>
            <container
                granite:class="ps-cf-rollout-workflow-form"
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/form"
                style="vertical">
                <items jcr:primaryType="nt:unstructured">
                    <languagesContainer
                        jcr:primaryType="nt:unstructured"
                        jcr:title="Regions"
                        sling:resourceType="granite/ui/components/coral/foundation/form/fieldset">
                        <items jcr:primaryType="nt:unstructured">
                            <languages
                                granite:class="cf-rollout-languages-field"
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/coral/foundation/form/select"
                                emptyOption="{Boolean}false"
                                fieldLabel="Rollout to Regions"
                                multiple="{Boolean}true"
                                name="./languages"
                                required="{Boolean}true">
                                <datasource
                                    jcr:primaryType="nt:unstructured"
                                    sling:resourceType="<b>site-com/components/datasource/cf-site-locales</b>"/>
                            </languages>                         
                        </items>
                    </languagesContainer>
                    <steps
                        granite:class="cf-rollout-dialog-steps-container"
                        jcr:primaryType="nt:unstructured"
                        jcr:title="Steps"
                        sling:resourceType="granite/ui/components/coral/foundation/form/fieldset">
                        <items jcr:primaryType="nt:unstructured">
                            <alert
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/coral/foundation/alert"
                                text="The workflow can be run several times for the same Content Fragment. In most cases all steps should be selected. Please only unselect a step if you have a specific reason."
                                variant="info"
                                granite:class="hero-warn-message"
                                size="S"/>
                            <rolloutCF
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/coral/foundation/form/checkbox"
                                checked="{Boolean}true"
                                fieldDescription="When selected, workflow rolls out selected CF Model and all CF models referenced in it."
                                name="./rolloutCFEnabled"
                                text="Rollout Content Fragments"
                                tooltipPosition="left"
                                uncheckedValue="false"
                                value="true"
                                wrapperClass="tooltip-fixed-200 cf-rollout-dialog-rollout-cf-field"/>
                            <publishCF
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/coral/foundation/form/checkbox"
                                checked="{Boolean}true"
                                fieldDescription="When selected, workflow publishes selected CF Model and all CF models referenced in it."
                                name="./publishCFEnabled"
                                text="Publish Content Fragments"
                                tooltipPosition="left"
                                uncheckedValue="false"
                                value="true"
                                wrapperClass="tooltip-fixed-200 cf-rollout-dialog-publish-cf-field"/>
                            <createPage
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/coral/foundation/form/checkbox"
                                checked="{Boolean}true"
                                fieldDescription="For Event CF, if Video reference is empty, workflow creates proxy pages with Event v2 template; if Event CF has a reference to Video, workflow creates Video proxy pages."
                                name="./createPageEnabled"
                                text="Create and Rollout Proxy Pages"
                                tooltipPosition="left"
                                uncheckedValue="false"
                                value="true"
                                wrapperClass="tooltip-fixed-200 cf-rollout-dialog-create-page-field"/>
                            <publishPage
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/coral/foundation/form/checkbox"
                                checked="{Boolean}true"
                                fieldDescription="When selected, workflow publishes proxy pages."
                                name="./publishEnabled"
                                text="Publish Proxy Page and Clear Cache"
                                tooltipPosition="left"
                                uncheckedValue="false"
                                value="true"
                                wrapperClass="tooltip-fixed-200 cf-rollout-dialog-publish-page-field"/>
                        </items>
                    </steps>
                    <log
                        jcr:primaryType="nt:unstructured"
                        jcr:title="Log"
                        sling:resourceType="granite/ui/components/coral/foundation/form/fieldset">
                        <items jcr:primaryType="nt:unstructured">
                            <alertBarConainer
                                granite:class="cf-rollout-alert-bar-container"
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/coral/foundation/container">
                                <items jcr:primaryType="nt:unstructured"/>
                            </alertBarConainer>
                            <progressBarConainer
                                granite:class="cf-rollout-progress-bar-container"
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/coral/foundation/container"
                                text=" / ">
                                <items jcr:primaryType="nt:unstructured"/>
                            </progressBarConainer>
                        </items>
                    </log>
                </items>
            </container>
        </items>
        <footer jcr:primaryType="nt:unstructured">
            <cancel
                granite:id="cf-rollout-dialog-cancel-btn"
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/button"
                text="Cancel">
                <parentConfig
                    jcr:primaryType="nt:unstructured"
                    close="{Boolean}true"/>
            </cancel>
            <rollout
                granite:id="cf-rollout-dialog-rollout-btn"
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/button"
                text="Rollout"
                variant="primary">
                <parentConfig
                    jcr:primaryType="nt:unstructured"
                    close="{Boolean}false"/>
            </rollout>
        </footer>
    </cf-rollout-dialog>
</jcr:root>
See more See less

Locales List Data Source

A custom datasource to show valid locales in the Locales dropdown.

package com.wcm.site.servlets.datasource;
 
import com.adobe.granite.ui.components.ds.DataSource;
import com.adobe.granite.ui.components.ds.EmptyDataSource;
import com.adobe.granite.ui.components.ds.SimpleDataSource;
import com.adobe.granite.ui.components.ds.ValueMapResource;
import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.dam.commons.util.DamLanguageUtil;
import org.apache.commons.collections4.iterators.TransformIterator;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.*;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.api.wrappers.ValueMapDecorator;
import org.apache.sling.servlets.annotations.SlingServletResourceTypes;
import org.osgi.service.component.annotations.Component;
 
import javax.annotation.Nonnull;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;
import java.text.Collator;
import java.util.*;
 
@Component(service = Servlet.class)
@SlingServletResourceTypes(
        resourceTypes = "<b>site-com/components/datasource/cf-site-locales</b>",
        methods = HttpConstants.METHOD_GET
)
public class CFLocalesDataSource extends SlingSafeMethodsServlet {
 
    @Override
    protected void doGet(@Nonnull SlingHttpServletRequest slingRequest, @Nonnull SlingHttpServletResponse slingResponse)
            throws ServletException, IOException {
 
        String suffix = slingRequest.getRequestPathInfo().getSuffix();
        ResourceResolver resourceResolver = slingRequest.getResourceResolver();
        Resource languageRes = null;
        if (suffix != null) {
            languageRes = slingRequest.getResourceResolver().getResource(suffix);
        }
 
        DataSource ds;
        if (languageRes ==  null || ResourceUtil.isNonExistingResource(languageRes)) {
            ds = EmptyDataSource.instance();
        } else {
 
            List<Pair<String, String>> languageRoots = new ArrayList<>();
 
            for (Resource languageRoot : DamLanguageUtil.getLanguageRoots(resourceResolver, languageRes.getPath())) {
                String languageCode = languageRoot.getName();
                languageRoots.add(new ImmutablePair<>(
                        languageRoot.getName(),
                        DamLanguageUtil.getLanguageDisplayName(resourceResolver, languageCode)
                ));
            }
 
            final Collator languageCollector = Collator.getInstance(slingRequest.getLocale());
 
            Collections.sort(languageRoots, (item1, item2) ->
                    languageCollector.compare(item1.getRight(), item2.getRight()));
 
 
            final ResourceResolver resolver = resourceResolver;
 
            ds = new SimpleDataSource(new TransformIterator<>(languageRoots.iterator(), pair -> {
                ValueMap vm = new ValueMapDecorator(new HashMap<>());
                vm.put("value", pair.getKey());
                vm.put("text", pair.getValue());
 
                return new ValueMapResource(resolver, new ResourceMetadata(), JcrConstants.NT_UNSTRUCTURED, vm);
            }));
        }
 
        slingRequest.setAttribute(DataSource.class.getName(), ds);
    }
 
}
See more See less

A Custom Client Library: Building Out AEM Workflows

Here’s a simple js code to gather all the checkboxes and data provided in the popup and send it to the AEM workflow Starter. The whole AEM workflow consists of the following steps (any step can be skipped):

  1. CF rollout
  2. CF validation
  3. Proxy page creation
  4. Proxy page rollout
  5. CF replication
  6. Proxy page replication

A new clientlib will be placed here: “apps/site-com/clientlibs/authoring/assets-cf-rollout-dialog”clientlib.js:

(function(document, $) {
    "use strict";
 
    var DIALOG_ID = "cfm-rollout-dialog";
    var DIALOG_SELECTOR = "#" + DIALOG_ID;
    var CLOSE_BUTTONS = DIALOG_SELECTOR + " #cf-rollout-dialog-cancel-btn, " + DIALOG_SELECTOR + " .coral3-Dialog-closeButton";
    var ROLLOUT_BUTTON_SELECTOR = "#cf-rollout-dialog-rollout-btn";
    var LANGUAGES_SELECTOR = DIALOG_SELECTOR + " .cf-rollout-languages-field";
    var PROGRESS_BAR_CONTAINER_SELECTOR = ".cf-rollout-progress-bar-container";
    var PROGRESS_BAR_FIELD_CLASS = "cf-rollout-progress-bar-field";
    var ROLLOUT_WORKFLOW_SERVICE_SELECTOR = ".rollout-workflow.json";
    var ALERT_BAR_CONTAINER_SELECTOR = ".cf-rollout-alert-bar-container";
    var ROLLOUT_INFO_MESSAGE_CLASS = "cf-rollout-message";
    var ROLLOUT_INFO_MESSAGE_SELECTOR = "." + ROLLOUT_INFO_MESSAGE_CLASS;
 
    var ROLLOUT_CF_VALIDATION_ISSUES_MESSAGE = "Failed to process the following Content Fragment instances: \r\n\r\n";
    var ROLLOUT_CF_VALIDATION_INVALID_ISSUE_MESSAGE = "Invalid Content Fragment references are identified. Please check provided content fragment references";
    var ROLLOUT_CF_VALIDATION_PROXY_PAGE_NOT_FOUND_ERROR_MESSAGE = "Proxy page was not found";
    var ROLLOUT_CF_VALIDATION_SUCCESS_MESSAGE = "Content Fragments are successfully rolled out";
 
    var NO_CF_TO_ROLLOUT_MESSAGE = "No Content Fragments to rollout";
    var NO_PAGE_TO_ROLLOUT_MESSAGE = "No Proxy Pages to rollout";
 
    var ROLLOUT_PAGE_STATUS_MESSAGE = "Rolling out proxy page...";
    var PUBLISH_PAGE_STATUS_MESSAGE = "Publishing proxy page & clearing cache...";
 
    var ROLLOUT_AND_PUBLISHED_SUCCESS_MESSAGE = "The following pages have been rolled out and published successfully.";
    var ROLLOUT_SUCCESS_MESSAGE = "The following pages have been rolled out successfully.";
    var ROLLOUT_CF_REFERENCES_STATUS_MESSAGE = "Rolling out referenced Content Fragment models...";
    var PUBLISH_CF_REFERENCES_STATUS_MESSAGE = "Publishing referenced Content Fragment models...";
 
    var ROLLOUT_AND_PUBLISH_CF_STATUS_MESSAGE = "Rolling out target Content Fragment model...";
    var ROLLOUT_CF_STATUS_MESSAGE = "Rolling out target Content Fragment model...";
    var PUBLISH_CF_STATUS_MESSAGE = "Publishing Content Fragment models...";
 
    var WORKFLOW_ROLLOUT_FAILED_MSG = "Rollout workflow failed.";
    var CONTROLLER_CF_SUFFIX = ".cf-controller.json";
    var ROLLOUT_WORKFLOW_PAGE_MODEL_ID = "/var/workflow/models/site/rollout-proxy-page-workflow";
    var ROLLOUT_WORKFLOW_CF_MODEL_ID = "/var/workflow/models/site/site-cf-rollout-workflow";
    var REPLICATE_WORKFLOW_CF_MODEL_ID = "/var/workflow/models/site/site-cf-replicate-workflow";
    var REPLICATE_WORKFLOW_PAGE_MODEL_ID = "/var/workflow/models/site/replicate-proxy-page-workflow";
 
    var CREATE_PROXY_PAGE_CMD = "create_proxy_page";
    var GET_PROXY_PAGE_CMD = "get_proxy_page";
    var CF_REFERENCES_CMD = "cf_references";
    var CF_POST_VALIDATION_CMD = "cf_post_validation";
    var state = {};
 
    var Steps = {
        rolloutCF: function(path, updateUI) {
            var deferred = jQuery.Deferred();
            var skipReplication = !Controller.getCheckboxValue(".cf-rollout-dialog-publish-cf-field");
            var skipRollout = !Controller.getCheckboxValue(".cf-rollout-dialog-rollout-cf-field");
 
            if (skipRollout && skipReplication) {
                deferred.resolve();
                return deferred.promise();
            }
 
            if (updateUI) {
                var message = ROLLOUT_AND_PUBLISH_CF_STATUS_MESSAGE;
                if (skipRollout) {
                    message = PUBLISH_CF_STATUS_MESSAGE;
                }
                if (skipReplication) {
                    message = ROLLOUT_CF_STATUS_MESSAGE;
                }
                UI.updateProgressBar(message, true);
            }
 
            var params = {
                action: "start",
                type: "cf",
                skipReplicationAndFlush: skipReplication,
                skipRollout: skipRollout,
                regions: Controller.getRegions(),
                modelId: ROLLOUT_WORKFLOW_CF_MODEL_ID
            };
 
            Controller.executeCMD(path + ROLLOUT_WORKFLOW_SERVICE_SELECTOR, jQuery.param(params), true, deferred, "rolloutCFStorage");
            return deferred.promise();
        },
 
        rolloutCFReferences: function(path) {
            var deferred = jQuery.Deferred();
            var self = this;
            var skipReplication = !Controller.getCheckboxValue(".cf-rollout-dialog-publish-cf-field");
            var skipRollout = !Controller.getCheckboxValue(".cf-rollout-dialog-rollout-cf-field");
 
            if (skipRollout && skipReplication ) {
                deferred.resolve();
                return deferred.promise();
            }
 
            var message = ROLLOUT_CF_REFERENCES_STATUS_MESSAGE;
            if (skipRollout) {
                message = PUBLISH_CF_REFERENCES_STATUS_MESSAGE;
            }
            if (skipReplication) {
                message = ROLLOUT_CF_REFERENCES_STATUS_MESSAGE;
            }
 
            UI.updateProgressBar(message, true);
            var cfReferencesDeferred = jQuery.Deferred();
            var params = {
                action: CF_REFERENCES_CMD
            };
 
            Controller.executeCMD(path + CONTROLLER_CF_SUFFIX, jQuery.param(params), false, cfReferencesDeferred, "cfReferencesStorage");
 
            $.when(cfReferencesDeferred)
                .then(function() {
 
                    if (state["cfReferencesStorage"] && state["cfReferencesStorage"].cfReferences && state["cfReferencesStorage"].cfReferences.length > 0) {
                        var promises = [];
                        var cfReferences = state["cfReferencesStorage"].cfReferences;
 
                        for (var i = 0; i < cfReferences.length; i++) {
                            promises.push(self.rolloutCF(cfReferences[i]));
                        }
                        $.when.apply($, promises)
                            .done(function() {
                                deferred.resolve()
                            })
                            .fail(function() {
                                deferred.reject()
                            })
                    } else {
                        deferred.resolve();
                    }
                });
            return deferred.promise();
        },
 
        getOrCreateProxyPageStep: function(path) {
            var deferred = jQuery.Deferred();
            var skipReplication = !Controller.getCheckboxValue(".cf-rollout-dialog-publish-page-field");
            var skipRollout = !Controller.getCheckboxValue(".cf-rollout-dialog-create-page-field");
 
            if (skipReplication && skipRollout) {
                deferred.resolve();
                return deferred.promise();
            }
 
            UI.updateProgressBar("Resolving proxy page...", true);
            var params = {
                action: !skipRollout ? CREATE_PROXY_PAGE_CMD : GET_PROXY_PAGE_CMD
            };
 
            Controller.executeCMD(path + CONTROLLER_CF_SUFFIX, jQuery.param(params), false, deferred, "proxyPageStorage");
            return deferred.promise();
        },
 
        reportCFStatus: function(path) {
            var skipReplication = !Controller.getCheckboxValue(".cf-rollout-dialog-publish-cf-field");
            var skipRollout = !Controller.getCheckboxValue(".cf-rollout-dialog-rollout-cf-field");
 
            if (skipReplication && skipRollout) {
                return;
            }
 
            var currentState = state["cfPostValidation"];
            if (currentState && currentState.invalidCfReferences && currentState.invalidCfReferences.length > 0) {
                if (currentState.invalidCfReferences.join("\r\n").indexOf("null/") !== -1) {
                    UI.handleAlertBarMessage(ROLLOUT_CF_VALIDATION_INVALID_ISSUE_MESSAGE, null, Coral.Alert.variant.WARNING, true);
                } else {
                    UI.handleAlertBarMessage(ROLLOUT_CF_VALIDATION_ISSUES_MESSAGE + currentState.invalidCfReferences.join("\r\n"), null, Coral.Alert.variant.WARNING);
                }
            } else {
                if (!skipRollout) {
                    UI.handleAlertBarMessage(ROLLOUT_CF_VALIDATION_SUCCESS_MESSAGE, null, Coral.Alert.variant.SUCCESS, true);
                }
            }
        },
 
        cfValidation: function(path) {
            var deferred = jQuery.Deferred();
            var deferredValidation = jQuery.Deferred();
 
            var params = {
                action: CF_POST_VALIDATION_CMD,
                regions: Controller.getRegions()
            };
 
            Controller.executeCMD(path + CONTROLLER_CF_SUFFIX, jQuery.param(params), false, deferredValidation, "cfPostValidation");
            $.when(deferredValidation.promise()).done(function(){
                Steps.reportCFStatus();
                deferred.resolve();
            }).fail(function(){
                deferred.resolve();
            });
 
            return deferred.promise();
        },
 
        rolloutProxyPageWorkflowStep: function(proxyPagePath, disableReporting) {
            var deferred = jQuery.Deferred();
            var skipReplication = !Controller.getCheckboxValue(".cf-rollout-dialog-publish-page-field");
            var skipRollout = !Controller.getCheckboxValue(".cf-rollout-dialog-create-page-field");
 
            var params = {
                action: "start",
                skipReplicationAndFlush: skipReplication,
                skipRollout: skipRollout,
                regions: Controller.getRegions(),
                modelId: ROLLOUT_WORKFLOW_PAGE_MODEL_ID
            };
 
            var message = ROLLOUT_PAGE_STATUS_MESSAGE;
            if (skipRollout) {
                message = PUBLISH_PAGE_STATUS_MESSAGE;
            }
            if (skipReplication) {
                message = ROLLOUT_PAGE_STATUS_MESSAGE;
            }
            UI.updateProgressBar(message, true);
            Controller.executeCMD(proxyPagePath + ROLLOUT_WORKFLOW_SERVICE_SELECTOR, jQuery.param(params), true, deferred, "rolloutPageStorage", disableReporting);
            return deferred.promise();
        },
 
        rolloutProxyPagesWorkflowStep: function(path) {
            var deferred = jQuery.Deferred();
            var self = this;
            var skipRollout = !Controller.getCheckboxValue(".cf-rollout-dialog-create-page-field");
            var pageFlowNeeded = !skipRollout || Controller.getCheckboxValue(".cf-rollout-dialog-publish-page-field");
 
            if (pageFlowNeeded) {
                if (!state["proxyPageStorage"] || state["proxyPageStorage"].path === "") {
 
                    UI.handleAlertBarMessage(ROLLOUT_CF_VALIDATION_PROXY_PAGE_NOT_FOUND_ERROR_MESSAGE, null, Coral.Alert.variant.ERROR, true);
                    deferred.reject();
                } else {
                    var proxyPagePath = state["proxyPageStorage"].path;
                    if (!state["proxyPageStorage"].intermediatePageRequired) {
                        return self.rolloutProxyPageWorkflowStep(proxyPagePath);
                    } else {
                        var parentPage = proxyPagePath.substr(0, proxyPagePath.lastIndexOf("/"));
                        $.when(self.rolloutProxyPageWorkflowStep(parentPage, true))
                            .then(function() {
                                return self.rolloutProxyPageWorkflowStep(proxyPagePath)
                            })
                            .done(function() {
                                deferred.resolve()
                            })
                            .fail(function() {
                                deferred.reject()
                            })
                    }
                }
            } else {
                deferred.resolve();
            }
            return deferred.promise();
        },
 
        replicateCFsStep: function(path) {
          var deferred = jQuery.Deferred();
          var skipReplication = !Controller.getCheckboxValue(".cf-rollout-dialog-publish-cf-field");
 
            if (!state["rolloutCFStorage"] || !state["rolloutCFStorage"].workflowIds || state["rolloutCFStorage"].workflowIds.size === 0) {
              UI.handleAlertBarMessage(NO_CF_TO_ROLLOUT_MESSAGE, null, Coral.Alert.variant.WARNING, true);
              deferred.resolve();
            } else {
              UI.updateProgressBar(PUBLISH_CF_STATUS_MESSAGE, true);
              var workflowIds = Array.from(state["rolloutCFStorage"].workflowIds).join(",");
              var useRawPaths = state["rolloutCFStorage"].useRawPaths;
 
              var params = {
                action: "start",
                type: "cf",
                skipReplicationAndFlush: skipReplication,
                workflowIds: workflowIds,
                useRawPaths: useRawPaths,
                modelId: REPLICATE_WORKFLOW_CF_MODEL_ID
              };
 
              Controller.executeCMD(path + ROLLOUT_WORKFLOW_SERVICE_SELECTOR, jQuery.param(params), true, deferred, "replicateCFStorage");
 
              if (!skipReplication) {
                return deferred.promise();
              } else {
                var nopDeferred = jQuery.Deferred();
                nopDeferred.resolve();
                return nopDeferred.promise();
              }
            }
          return deferred.promise();
        },
 
        replicateProxyPagesStep: function(path) {
          var deferred = jQuery.Deferred();
          var skipReplication = !Controller.getCheckboxValue(".cf-rollout-dialog-publish-page-field");
 
          if (!state["rolloutPageStorage"] || !state["rolloutPageStorage"].workflowIds || state["rolloutPageStorage"].workflowIds.size == 0) {
            if (!skipReplication) {
                UI.handleAlertBarMessage(NO_PAGE_TO_ROLLOUT_MESSAGE, null, Coral.Alert.variant.WARNING, true);
            }
            deferred.resolve();
          } else {
            var workflowIds = Array.from(state["rolloutPageStorage"].workflowIds).join(",");
            var useRawPaths = state["rolloutPageStorage"].useRawPaths;
 
            if (!skipReplication) {
                UI.updateProgressBar(PUBLISH_PAGE_STATUS_MESSAGE, true);
            }
 
            var params = {
              action: "start",
              skipReplicationAndFlush: skipReplication,
              workflowIds: workflowIds,
              useRawPaths: useRawPaths,
              modelId: REPLICATE_WORKFLOW_PAGE_MODEL_ID
            };
 
            Controller.executeCMD(path + ROLLOUT_WORKFLOW_SERVICE_SELECTOR, jQuery.param(params), true, deferred, "replicateProxyStorage");
            if (!skipReplication) {
                return deferred.promise();
            } else {
                var nopDeferred = jQuery.Deferred();
                nopDeferred.resolve();
                return nopDeferred.promise();
            }
          }
          return deferred.promise();
        }
 
    };
 
    var UI = {
        updateProgressBar: function(text, inProgress, noDelay) {
            var delay = 300;
            if (noDelay) {
                delay = 100;
            }
 
            setTimeout(function() {
                var progress = new Coral.Progress().set({
                    label: {
                        innerHTML: text
                    },
                    labelPosition: "bottom",
                    size: "L",
                    indeterminate: inProgress
                });
                $(PROGRESS_BAR_CONTAINER_SELECTOR).empty();
                $(PROGRESS_BAR_CONTAINER_SELECTOR).append(progress);
                $(progress).addClass(PROGRESS_BAR_FIELD_CLASS);
            }, delay);
 
        },
 
        initLogElements: function() {
            $(ROLLOUT_INFO_MESSAGE_SELECTOR).remove();
            UI.updateProgressBar("Not Started", false, true);
        },
 
        updatePopupMessage: function(message, elementClass, variant) {
            if (!variant) {
                variant = Coral.Alert.variant.INFO;
            }
 
            var rolloutAlert = new Coral.Alert();
            rolloutAlert.content = new Coral.Alert.Content();
 
            $(rolloutAlert.content).html(message);
 
            $(ALERT_BAR_CONTAINER_SELECTOR).append(rolloutAlert);
            $(rolloutAlert).get(0).variant = variant;
            $(rolloutAlert).addClass(ROLLOUT_INFO_MESSAGE_CLASS)
        },
 
        handleAlertBarMessage: function(message, button, variant, disableTextAreaWrapper) {
            if (!variant) {
                variant = Coral.Alert.variant.ERROR;
            }
            var finalMsg = disableTextAreaWrapper ? message : "<textarea class='rollout-alert-textarea' variant='quiet' readonly is='coral-textarea'>" + message + "</textarea>";
 
            this.updatePopupMessage(finalMsg, ROLLOUT_INFO_MESSAGE_CLASS, variant);
        },
 
        disableDialogButtons: function(disabled) {
            $(ROLLOUT_BUTTON_SELECTOR).prop("disabled", disabled);
            $(LANGUAGES_SELECTOR).prop("disabled", disabled);
            $(DIALOG_SELECTOR + " coral-checkbox").prop("disabled", disabled);
            $(CLOSE_BUTTONS).prop("disabled", disabled);
        }
    };
 
    var Controller = {
        rolloutAssets: function(e) {
            state = {};
            UI.initLogElements();
            UI.disableDialogButtons(true);
            UI.updateProgressBar("Starting...", true, true);
 
            var $selections = $(".foundation-selections-item");
            var url = $selections.data("foundationCollectionItemId");
 
            $.when(Steps.rolloutCFReferences(url))
                .then(function() {
                    return Steps.rolloutCF(url, true);
                })
                .then(function() {
                    return Steps.cfValidation(url, true);
                })
                .then(function() {
                    return Steps.getOrCreateProxyPageStep(url);
                })
                .then(function() {
                    return Steps.rolloutProxyPagesWorkflowStep(url);
                })
                .then(function() {
                  return Steps.replicateCFsStep(url);
                })
                .then(function() {
                  return Steps.replicateProxyPagesStep(url);
                })
                .then(function() {
                    console.log("finished")
                })
                .done(function() {
                    UI.updateProgressBar("Done", false);
                    UI.disableDialogButtons(false);
                })
                .fail(function() {
                    UI.updateProgressBar("Failed", false);
                    UI.disableDialogButtons(false);
                });
        },
 
        executeCMD: function(path, params, statusCheckNeeded, deferred, stateStorage, disableReporting) {
            $.ajax({
                url: path + "?" + params,
                async: true
            }).done(function(data) {
                if (data.message) {
                    if (!disableReporting) {
 
                        if (data.status === "ABORTED") {
                            deferred.reject();
                            UI.handleAlertBarMessage(data.message,null, Coral.Alert.variant.ERROR, true);
                        } else {
                            UI.handleAlertBarMessage(data.message);
                        }
                    }
                    return;
                }
 
                var workflowId = data.workflowId;
                var iterations = 0;
 
                if (statusCheckNeeded) {
                    if (!workflowId) {
                        deferred.reject();
                        return;
                    }
                    var statusCheckInterval = setInterval(statusChecker, 3000);
                    state[statusCheckInterval] = false;
                } else {
                    if (stateStorage) {
                        state[stateStorage] = data;
                    }
                    deferred.resolve();
                }
 
                function statusChecker() {
                    iterations++;
                    if (iterations >= 240) {
                        state[statusCheckInterval] = true;
                        clearInterval(statusCheckInterval);
                        deferred.reject();
                        return;
                    }
 
                    $.ajax(path + ROLLOUT_WORKFLOW_SERVICE_SELECTOR + "?action=status&workflowId=" + workflowId).done(function(data) {
                        if (state[statusCheckInterval]) {
                            return;
                        }
                        var variant = Coral.Alert.variant.WARNING;
                        var message = data.message ? data.message.replace(/(?:\r\n|\r|\n)/g, "\r\n") : "";
                        message = message.replace(ROLLOUT_AND_PUBLISHED_SUCCESS_MESSAGE, ROLLOUT_SUCCESS_MESSAGE);
 
                        if (data.status === "ABORTED") {
                            variant = Coral.Alert.variant.ERROR;
                            message = WORKFLOW_ROLLOUT_FAILED_MSG + message;
                            deferred.reject();
                        }
                        if (data.status === "SUCCESS" && data.message) {
                            saveWFData(stateStorage, workflowId, data);
                            deferred.resolve();
                            variant = Coral.Alert.variant.SUCCESS;
 
                        }
                        if (data.status === "COMPLETED" && data.message) {
                            saveWFData(stateStorage, workflowId, data);
                            deferred.resolve();
                            variant = Coral.Alert.variant.WARNING;
                        }
                        if (data.message) {
                            if (!disableReporting) {
                                UI.handleAlertBarMessage(message, null, variant);
                            }
                            state[statusCheckInterval] = true;
                            clearInterval(statusCheckInterval);
                            return;
                        }
                        if (data.status === "COMPLETED" && !data.message) {
                            state[statusCheckInterval] = true;
                            clearInterval(statusCheckInterval);
                            saveWFData(stateStorage, workflowId, data);
                            deferred.resolve();
                        }
                    });
                }
 
                function saveWFData(statusStorage, workflowId, data) {
                  state[statusStorage] = state[statusStorage] || {};
                  state[statusStorage].useRawPaths = data.useRawPaths;
                  state[statusStorage].workflowIds = state[statusStorage].workflowIds || new Set();
                  state[statusStorage].workflowIds.add(workflowId);
                }
            })
        },
 
        getRegions: function() {
            return document.querySelector(LANGUAGES_SELECTOR).values.join(";");
        },
 
        getCheckboxValue: function(selector) {
            return $(selector + " [type='checkbox']").prop("checked");
        },
 
        init: function() {
            var self = this;
            $(ROLLOUT_BUTTON_SELECTOR).on("click", function(e) {
                var api = $(LANGUAGES_SELECTOR).adaptTo("foundation-validation");
                if (api) {
                    if (api.checkValidity()) {
                        self.rolloutAssets(e);
                    }
                    api.updateUI();
                }
            });
 
            UI.initLogElements();
        }
    };
 
    Controller.init();
})(document, Granite.$);
See more See less

In the next part, we’ll continue with the AEM Workflow Starter Process as well as the subsequent processes that will be triggered. If you need professional AEM development and consulting services, we’d be happy to hear from you!

Author: Iryna Ason