Vous pouvez utiliser l’interface REST de QueryBuilder ou créer un service OSGi à l’aide de l’API QueryBuilder pour créer un rapport personnalisé.
-
Les données utilisées dans les rapports personnalisés doivent être disponibles dans Process Reporting. Pour garantir la disponibilité des données, planifiez une tâche cron ou utilisez l’option Synchronisation sur l’interface utilisateur de Process Reporting.
-
La requête URL (encapsulant la requête souhaitée) doit renvoyer un objet de résultats de requête correct. Pour créer une requête, vous pouvez utiliser l’interface REST de QueryBuilder pour créer un service OSGi à l’aide de l’API QueryBuilder. Vous pouvez créer des requêtes dynamiques ou statiques.
L’interface REST de CRX QueryBuilder expose la fonctionnalité QueryBuilder de Asset Share via une API Java et une API REST. Découvrez comment utiliser l’interface REST de CRX QueryBuilder, avant d’effectuer les étapes suivantes :
-
Créez une requête basée sur la structure du nœud de stockage et les propriétés de nœud de Process Reporting.
Vous pouvez définir des paramètres facultatifs pour spécifier le décalage, la limite, les accès, et les propriétés. Vous pouvez coder en dur les arguments pour les rapports statiques et récupérer les paramètres de l’interface utilisateur pour les rapports dynamiques.
Pour récupérer tous les noms de processus, la requête doit être :
http://[Serveur]:[Port]/lc/bin/querybuilder.json?exact=false&p.hits=selective&p.properties=pmProcessTitle&path=%2fcontent%2freporting%2fpm&property=pmNodeType&property.operation=equals&property.value=ProcessType&type=sling%3aFolder
Remarque :
Dans chaque requête, le paramètre de chemin d’accès pointe sur l’emplacement de stockage de CRX et les caractères sont placés dans une séquence d’échappement, conformément à la norme d’URL.
Pour créer un service à l’aide de l’API QueryBuilder, vous devez créer et déployer un bundle CQ OSGI et utiliser l’API Query Builder.
-
Ajoutez des attributs au groupe d’attributs (predicateGroup) nouvellement créé. Parmi les outils de construction d’attributs utiles figurent JcrBoolPropertyPredicateEvaluator, JcrPropertyPredicateEvaluator, RangePropertyPredicateEvaluator, DateRangePredicateEvaluator, and TypePredicateEvaluator.
Pour les rapports statiques, codez en dur les attributs. En revanche, pour les rapports dynamiques, récupérez les attributs de la requête.
L’exemple de code pour obtenir toutes les instances d’un processus est :
Predicate predicate; //Add the path Constraint predicate = new Predicate(PathPredicateEvaluator.PATH); predicate.set(PathPredicateEvaluator.PATH, "/content/reporting/pm"); // should point to the crx path being used to store data predicate.set(PathPredicateEvaluator.EXACT, "false"); predicateGroup.add(predicate); //type nt:unstructured predicate = new Predicate(TypePredicateEvaluator.TYPE); predicate.set(TypePredicateEvaluator.TYPE, "nt:unstructured"); predicateGroup.add(predicate); //NodeType: Process Instance predicate = new Predicate(JcrPropertyPredicateEvaluator.PROPERTY); predicate.set(JcrPropertyPredicateEvaluator.PROPERTY, "pmNodeType"); predicate.set(JcrPropertyPredicateEvaluator.OPERATION, JcrPropertyPredicateEvaluator.OP_EQUALS); predicate.set(JcrPropertyPredicateEvaluator.VALUE, "ProcessInstance"); predicateGroup.add(predicate); //processName predicate = new Predicate(JcrPropertyPredicateEvaluator.PROPERTY); predicate.set(JcrPropertyPredicateEvaluator.PROPERTY, "pmProcessName"); predicate.set(JcrPropertyPredicateEvaluator.OPERATION, JcrPropertyPredicateEvaluator.OP_EQUALS); predicate.set(JcrPropertyPredicateEvaluator.VALUE, processName); //processName variable stores the name of the process whose instances need to be searched predicateGroup.add(predicate);
-
Itérez le résultat et transformez les résultats au format souhaité. Le code pour envoyer les résultats au format CSV est :
Iterator<Node> iter = searchResult.getNodes(); while(iter.hasNext()) { Node node = iter.next(); row = new StringBuilder(); for (String property : includeProperties) { // the properties of the node which needs to be returned, or one can return all the properties too. try { row.append(node.getProperties(property).nextProperty().getString() + COMMA_SEPARATOR); } catch (NoSuchElementException e) { //Adding separator for no value row.append(COMMA_SEPARATOR); } catch (RepositoryException e) { e.printStackTrace(); } } row.deleteCharAt(row.lastIndexOf(COMMA_SEPARATOR)); row.append(NEW_LINE); out.write(row.toString().getBytes());
L’exemple de service suivant compte des instances d’un processus en COURS D’EXECUTION avec l’état TERMINE à la fin de chaque mois, trimestre et année.
package custom.reporting.service; import java.text.DateFormatSymbols; import java.util.Calendar; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import javax.jcr.Node; import javax.jcr.Session; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import com.day.cq.search.Predicate; import com.day.cq.search.PredicateGroup; import com.day.cq.search.Query; import com.day.cq.search.QueryBuilder; import com.day.cq.search.eval.JcrPropertyPredicateEvaluator; import com.day.cq.search.eval.PathPredicateEvaluator; import com.day.cq.search.eval.TypePredicateEvaluator; import com.day.cq.search.result.SearchResult; @Component(metatype = true, immediate = true, label = "PeriodicProcessVolume", description = "Service for supporting cutom reports pluggable to Process Reporting.") @Service(value = PeriodicProcessVolume.class) public class PeriodicProcessVolume { private static String[] monthNameList = new DateFormatSymbols().getMonths(); private static String[] quaterNameList = { "I", "II", "III", "IV" }; private final Map<Integer, Map<Integer, Long[]>> monthly = new HashMap<Integer, Map<Integer, Long[]>>(); private final Map<Integer, Map<Integer, Long[]>> quaterly = new HashMap<Integer, Map<Integer, Long[]>>(); private final Map<Integer, Long[]> yearly = new HashMap<Integer, Long[]>(); @Reference(referenceInterface = QueryBuilder.class) private QueryBuilder queryBuilder; private void addConstraints(PredicateGroup predicateGroup, String processName) { Predicate predicate; //Add the path Constraint predicate = new Predicate(PathPredicateEvaluator.PATH); predicate.set(PathPredicateEvaluator.PATH, "/content/reporting/pm"); predicate.set(PathPredicateEvaluator.EXACT, "false"); predicateGroup.add(predicate); //type nt:unstructured predicate = new Predicate(TypePredicateEvaluator.TYPE); predicate.set(TypePredicateEvaluator.TYPE, "nt:unstructured"); predicateGroup.add(predicate); //NodeType: Process Instance predicate = new Predicate(JcrPropertyPredicateEvaluator.PROPERTY); predicate.set(JcrPropertyPredicateEvaluator.PROPERTY, "pmNodeType"); predicate.set(JcrPropertyPredicateEvaluator.OPERATION, JcrPropertyPredicateEvaluator.OP_EQUALS); predicate.set(JcrPropertyPredicateEvaluator.VALUE, "ProcessInstance"); predicateGroup.add(predicate); //processName if (processName != null) { predicate = new Predicate(JcrPropertyPredicateEvaluator.PROPERTY); predicate.set(JcrPropertyPredicateEvaluator.PROPERTY, "pmProcessName"); predicate.set(JcrPropertyPredicateEvaluator.OPERATION, JcrPropertyPredicateEvaluator.OP_EQUALS); predicate.set(JcrPropertyPredicateEvaluator.VALUE, processName); predicateGroup.add(predicate); } } private Long[] setFrequency(Long[] frequency, int index) { if (frequency == null) { frequency = new Long[2]; frequency[0] = 0L; frequency[1] = 0L; } frequency[index] = frequency[index] + 1L; return frequency; } public void populateValues(Session session, String processName) { PredicateGroup predicateGroup = new PredicateGroup(); predicateGroup.setAllRequired(true); try { addConstraints(predicateGroup, processName); long batchSize = 10000L; long start = 0l; while (true) { Query query = queryBuilder.createQuery(predicateGroup, session); query.setStart(start); query.setHitsPerPage(batchSize); SearchResult searchResult = query.getResult(); Iterator<Node> itr = searchResult.getNodes(); long length = 0; while (itr.hasNext()) { length++; Node n = itr.next(); Calendar calender = n.getProperty("pmCreateTime").getDate(); String status = n.getProperty("pmStatus").getString(); int index = 0; if ("COMPLETE".equals(status)) { index = 1; } else if ("RUNNING".equals(status)) { index = 0; } else { continue; } int month = calender.get(Calendar.MONTH); int year = calender.get(Calendar.YEAR); int quater; if (month < 3) { quater = 1; } else if (month < 6) { quater = 2; } else if (month < 9) { quater = 3; } else { quater = 4; } Long frequency[]; Map<Integer, Long[]> yearMonthMap = this.monthly.get(year); if (yearMonthMap == null) { yearMonthMap = new HashMap<Integer, Long[]>(); } frequency = yearMonthMap.get(month); frequency = setFrequency(frequency, index); yearMonthMap.put(month, frequency); this.monthly.put(year, yearMonthMap); Map<Integer, Long[]> yearQuaterMap = this.quaterly.get(year); if (yearQuaterMap == null) { yearQuaterMap = new HashMap<Integer, Long[]>(); } frequency = yearQuaterMap.get(quater); frequency = setFrequency(frequency, index); yearQuaterMap.put(quater, frequency); this.quaterly.put(year, yearQuaterMap); frequency = this.yearly.get(year); frequency = setFrequency(frequency, index); this.yearly.put(year, frequency); } if (length < batchSize) { break; } else { start = start + batchSize; } } } catch (Exception e) { e.printStackTrace(); } } public Map<String, Long[]> getMonthly() { Map<String, Long[]> result = new LinkedHashMap<String, Long[]>(); SortedSet<Integer> years = new TreeSet<Integer>(monthly.keySet()); for (Integer year : years) { Map<Integer, Long[]> yearMonthMap = monthly.get(year); SortedSet<Integer> months = new TreeSet<Integer>(yearMonthMap.keySet()); for (Integer month : months) { String str = monthNameList[month] + " " + year; result.put(str, yearMonthMap.get(month)); } } return result; } public Map<String, Long[]> getQuaterly() { Map<String, Long[]> result = new LinkedHashMap<String, Long[]>(); SortedSet<Integer> years = new TreeSet<Integer>(quaterly.keySet()); for (Integer year : years) { Map<Integer, Long[]> quaterMonthMap = quaterly.get(year); SortedSet<Integer> quaters = new TreeSet<Integer>(quaterMonthMap.keySet()); for (Integer quater : quaters) { String str = quaterNameList[quater - 1] + " " + year; result.put(str, quaterMonthMap.get(quater)); } } return result; } public Map<Integer, Long[]> getYearly() { return yearly; } }
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <!-- ====================================================================== --> <!-- P R O J E C T D E S C R I P T I O N --> <!-- ====================================================================== --> <groupId>com.custom</groupId> <artifactId>sample-report-core</artifactId> <packaging>bundle</packaging> <name>PR Sample Report</name> <description>Bundle providing support for a custom report pluggable to process reporting.</description> <version>1</version> <!-- ====================================================================== --> <!-- B U I L D D E F I N I T I O N --> <!-- ====================================================================== --> <build> <plugins> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <version>2.3.7</version> <extensions>true</extensions> <configuration> <instructions> <Bundle-Category>sample-report</Bundle-Category> <Export-Package> custom.reporting.service.*; </Export-Package> </instructions> </configuration> </plugin> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-scr-plugin</artifactId> <version>1.11.0</version> <executions> <execution> <id>generate-scr-scrdescriptor</id> <goals> <goal>scr</goal> </goals> <configuration> <!-- Private service properties for all services. --> <properties> <service.vendor>Sample Report</service.vendor> </properties> </configuration> </execution> </executions> </plugin> </plugins> </build> <!-- ====================================================================== --> <!-- D E P E N D E N C I E S --> <!-- ====================================================================== --> <dependencies> <dependency> <groupId>com.day.cq</groupId> <artifactId>cq-search</artifactId> <version>5.6.4</version> </dependency> <dependency> <groupId>javax.jcr</groupId> <artifactId>jcr</artifactId> <version>2.0</version> </dependency> <dependency> <groupId>org.apache.felix</groupId> <artifactId>org.apache.felix.scr.annotations</artifactId> <version>1.9.0</version> </dependency> </dependencies> </project>
Pour créer une interface utilisateur distincte affichant les résultats, vous devez connaître les bases de Sling, savoir Créer un nœud CRX et fournir les droits d’accès appropriés.
<%@taglib prefix="sling" uri="http://sling.apache.org/taglibs/sling/1.0"%> <%request.setAttribute("silentAuthor", new Boolean(true));%> <%@include file="/libs/foundation/global.jsp"%> <%@ page import="java.util.Map, java.util.Set, com.adobe.idp.dsc.registry.service.ServiceRegistry, javax.jcr.Session, org.apache.sling.api.resource.ResourceResolver, custom.reporting.service.PeriodicProcessVolume"%> <% response.setContentType("text/html"); response.setCharacterEncoding("utf-8"); %><!DOCTYPE HTML> <html> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="/lc/apps/sample-report-process-reporting/custom-reports/periodicProcessVolume/style.css"> <title>REPORT Monthly / Qaterly / Yearly</title> <script type="text/javascript"> <% slingResponse.setCharacterEncoding("utf-8"); ResourceResolver resolver = slingRequest.getResourceResolver(); String processName = slingRequest.getParameter("processName"); Session session = resolver.adaptTo(Session.class); custom.reporting.service.PeriodicProcessVolume periodicProcessVolume = sling.getService(custom.reporting.service.PeriodicProcessVolume.class); periodicProcessVolume.populateValues(session, processName); if (processName == null) { processName = "All"; } %> var lineSeprator = "<td class='seprator'>----------------</td>"; var tableEnder = "<tr>" + lineSeprator + lineSeprator + lineSeprator + "</tr>"; var tableColHeader = "<td class='colHead colNum'>Running</td>"; tableColHeader += "<td class='colHead colNum'>Complete</td></tr>"; tableColHeader += tableEnder; var monthly = "<table><tr><td class='colHead colStr'>Month</td>"; monthly += tableColHeader; <% Map<String, Long[]> monthlyMap = periodicProcessVolume.getMonthly(); Set<String> monthKeys = monthlyMap.keySet(); for (String key: monthKeys) { Long[] frequencies = monthlyMap.get(key); %> monthly += "<tr><td class='colStr'> <%= key %> </td>"; monthly += "<td class='colNum'> <%= frequencies[0] %> </td>"; monthly += "<td class='colNum'> <%= frequencies[1] %> </td></tr>"; <% } %> monthly += tableEnder; var quaterly = "<table><tr><td class='colHead colStr'>Quater</td>"; quaterly += tableColHeader; <% Map<String, Long[]> quaterMap = periodicProcessVolume.getQuaterly(); Set<String> quaterKeys = quaterMap.keySet(); for (String key: quaterKeys) { Long[] frequencies = quaterMap.get(key); %> quaterly += "<tr><td class='colStr'> <%= key %> </td>"; quaterly += "<td class='colNum'> <%= frequencies[0] %> </td>"; quaterly += "<td class='colNum'> <%= frequencies[1] %> </td></tr>"; <% } %> quaterly += tableEnder; var yearly = "<table><tr><td class='colHead colStr'>Year</td>"; yearly += tableColHeader; <% Map<Integer, Long[]> yearMap = periodicProcessVolume.getYearly(); Set<Integer> yearKeys = yearMap.keySet(); for (Integer key: yearKeys) { Long[] frequencies = yearMap.get(key); %> yearly += "<tr><td class='colStr'> <%= key %> </td>"; yearly += "<td class='colNum'> <%= frequencies[0] %> </td>"; yearly += "<td class='colNum'> <%= frequencies[1] %> </td></tr>"; <% } %> yearly += tableEnder; function reloadFrame(value) { if (value === '-1') { window.location = "/lc/content/process-reporting-runtime/custom-reports/periodicProcessVolume.html"; } else { window.location = "/lc/content/process-reporting-runtime/custom-reports/periodicProcessVolume.html?processName=" + value; } } function populateTable(selection) { if (selection === 0) { document.getElementById('tableHeading').innerHTML = 'Monthly'; document.getElementById('volumeTable').innerHTML = monthly; } else if (selection === 1) { document.getElementById('tableHeading').innerHTML = 'Quaterly'; document.getElementById('volumeTable').innerHTML = quaterly; } else { document.getElementById('tableHeading').innerHTML = 'Yearly'; document.getElementById('volumeTable').innerHTML = yearly; } } function fetchProcesses() { var xmlhttp = new XMLHttpRequest(), request = ''; xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { var responseText, response, items, hits = [], responseSize = 0, processName, selectedIndex = 0, comboBox; responseText = xmlhttp.responseText; if (responseText !== undefined && responseText !== null) { response = JSON.parse(responseText); responseSize = response.results; hits = response.hits; } items = "<option value='-1'>All</option>"; for(var i = 0; i < responseSize; i++) { processName = hits[i].pmProcessTitle; if (processName === '<%= processName %>') { selectedIndex = i + 1; } items += "<option value='" + processName + "'>" + processName + "</option>" } comboBox = document.getElementById('processSelection'); comboBox.innerHTML = items; comboBox.selectedIndex = selectedIndex; } }; request = "/lc/bin/querybuilder.json?"; request += "exact=false&"; request += "p.hits=selective&"; request += "p.properties=pmProcessTitle&"; request += "path=%2fcontent%2freporting%2fpm&"; request += "property=pmNodeType&"; request += "property.operation=equals&"; request += "property.value=ProcessType&"; request += "type=sling%3aFolder"; xmlhttp.open("POST", request, true); xmlhttp.setRequestHeader("Content-type","application/json"); xmlhttp.send(); } </script> </head> <body onLoad="fetchProcesses();populateTable(0);"> Process: <select id="processSelection" onchange="reloadFrame(this.value);"></select>     Period Interval: <select name="periodSelection" onchange="populateTable(this.selectedIndex);"> <option value="1">Monthly</option> <option value="2">Quaterly</option> <option value="3">Yearly</option> </select> <br> <br> <br> <br> <div class="inline"> Process:   <b><%= processName %></b>     Period:   </div> <b> <div id="tableHeading" class="inline"> </div> </b> <br><br> <div id="volumeTable"> </div> </body> </html>
Intégration d’une interface utilisateur de rapport dans l’interface utilisateur de Process Reporting existante
Pour créer une interface utilisateur distincte affichant les résultats, vous devez connaître les bases de Sling, savoir Créer un nœud CRX et fournir les droits d’accès appropriés.
-
Créez une interface utilisateur distincte telle que décrite dans la section Créer une interface utilisateur distincte.
-
Créez un nœud enfantnt:unstructured au niveau du nœud /content/process-reporting-runtime/custom-reports pour chaque port enfichable.
- id - Indique le numéro d’identification unique du rapport.
- nom - Indique le nom du rapport. Le nom s’affiche dans l’interface utilisateur.
- lien - Spécifie le lien relatif vers le rendu de l’interface utilisateur distincte. Le lien est créé à l’étape 1.
- description - Spécifie la description d’une ligne du rapport. Vous pouvez laisser le champ de description vide.
- icône - Spécifie l’image pour représenter graphiquement le rapport. Vous pouvez laisser le champ d’icône vide.
Propriétés de nœud
-
L’interface utilisateur de rapport est intégrée à l’interface utilisateur Process Reporting. Après avoir intégré l’interface utilisateur, l’interface utilisateur mise à jour ressemble aux images suivantes :
Interface utilisateur des rapports personnalisés nouvellement ajoutés
Ecran des résultats des rapports personnalisés
Importez le package sample-report-pkg-1.zip pour intégrer des rapports personnalisés et l’interface utilisateur décrits dans l’article dans l’interface utilisateur de Process Management.
Telechargement