Building an Automation Dashboard for Salesforce Marketing Cloud (SFMC)
Part 1: Fetching the List of Automations
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <!-- Fetching the List of Automations from SFMC --> <script runat="server"> Platform.Load("Core", "1.1.1"); var prox = new Script.Util.WSProxy(); var cols = ["Name", "CustomerKey"]; var filter = { Property: "Status", SimpleOperator: "IN", Value: [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8] }; var res = prox.retrieve("Automation", cols, filter); if (res.Results.length > 0) { var statusObj = { "-1": "Error", "0": "BuildingError", "1": "Building", "2": "Ready", // ... (additional status mappings) }; Write('<table id="tableId"><tr><th colspan="2">Select the automation</th></tr>'); for (var i = 0; i < res.Results.length; i++) { var autoName = res.Results[i].Name; var ID = res.Results[i].CustomerKey; Write('<tr><td>' + autoName + '</td><td style="display:none;">' + ID + '</td></tr>'); } Write('</table>'); } </script> |
Part 2: Adding Click Handlers
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <!-- Adding Click Handlers to Automation List Table --> <script> function addRowHandlers() { var table = document.getElementById("tableId"); var rows = table.getElementsByTagName("tr"); for (i = 0; i < rows.length; i++) { var currentRow = table.rows[i]; var createClickHandler = function (row) { return function () { var cell = row.getElementsByTagName("td")[1]; var ai = cell.innerHTML; var url = 'https://yoursfmcsubdomain.com/Automation%20Dashboard?ai=' + ai; window.location.href = url; }; }; currentRow.onclick = createClickHandler(currentRow); } } window.onload = addRowHandlers(); </script> |
Part 3: Listing Automation Instances
We retrieve, and display instances of the selected automation based on its Customer Key obtained from the URL. Using WSProxy, we fetch instance details such as name, start time, completion time, status, and status message. The instances are presented in a table, and aggregate attributes representing the number of successes and failures are calculated but hidden from the end user.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | <!-- Listing Automation Instances of Selected Automation --> <script runat="server"> Platform.Load("Core", "1.1.1"); var prox = new Script.Util.WSProxy(); var automationKey = Variable.GetValue("@ai"); var cols = ["Name", "Status", "CompletedTime", "StatusMessage", "StartTime"]; if (automationKey !== null && automationKey !== "") { var filter = { Property: "CustomerKey", SimpleOperator: "EQUALS", Value: automationKey }; var res = prox.retrieve("AutomationInstance", cols, filter); var nerrors = 0; var nsuccess = 0; if (res.Results.length > 1) { Write('<table><tr><th colspan="2"></th></tr>'); for (var i = 0; i < res.Results.length; i++) { // Extracting instance details var autoName = res.Results[i].Name; var StartTime = res.Results[i].StartTime; var CompletedTime = res.Results[i].CompletedTime; var StatusMessage = res.Results[i].StatusMessage; // Counting successes and errors if (StatusMessage === 'Error') { nerrors++; } if (StatusMessage === 'Complete') { nsuccess++; } Write('<tr><td>' + autoName + '</td><td>' + StartTime + '</td><td>' + CompletedTime + '</td><td>' + StatusMessage + '</td></tr>'); } Write('</table>'); // Storing counts in hidden attributes Variable.SetValue("@nsuccess", nsuccess); Variable.SetValue("@nerrors", nerrors); } else { Write('<h1 align="center">No Instances found for this Automation</h1>'); } } else { Write('<h1 align="center">Select an automation from the left</h1>'); } </script> |
Part 4: Charting with Chart.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <!-- Charting Automation Instance Success and Failure Rates --> <script> var data = []; data[data.length] = document.getElementById("nsuccess").innerHTML; data[data.length] = document.getElementById("nerrors").innerHTML; new Chart(document.getElementById("pie-chart"), { type: 'pie', data: { labels: ["Completed", "Errored"], datasets: [{ backgroundColor: ["#82CD47", "#FB3569"], data: data }] }, options: { title: { display: false, text: 'Automation Performance Chart' } } }); </script> |
Part 5: Finalizing the Dashboard Layout
1 2 3 4 | <!-- Finalizing the Automation Dashboard Layout --> <div class="w3-light-grey w3-bar-block"> <input type="text" id="myInput" onkeyup="filterAutomations()" placeholder="Search for an automation.." title="Type in a name"> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <!-- Search Feature for Automations --> <script> function filterAutomations() { var input, filter, table, tr, td, i, txtValue; input = document.getElementById("myInput"); filter = input.value.toUpperCase(); table = document.getElementById("tableId"); tr = table.getElementsByTagName("tr"); for (i = 0; i < tr.length; i++) { td = tr[i].getElementsByTagName("td")[0]; if (td) { txtValue = td.textContent || td.innerText; if (txtValue.toUpperCase().indexOf(filter) > -1) { tr[i].style.display = ""; } else { tr[i].style.display = "none"; } } } } </script> |
By following these steps, you can create a comprehensive automation dashboard that enhances visibility into SFMC automation performance. The dashboard allows users to select specific automations, view their instances, and visualize success and failure rates over time.
Complete code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 | <!-- Title: Automation dashboard for developers in SFMC Description: This code is an automation dashboard for developers in Salesforce Marketing Cloud (SFMC). It is designed to be used on a cloud page and provides a user interface to display a list of automations on the left side and the instances of a selected automation on the right side, along with a pie chart showing the number of failed and successful runs. Developer name: Sravan Alaparthi Date: Version: Last modified: Last modified reason: --> <!-- Include Required Prerequisites --> <!-- The code includes CSS and JavaScript libraries for styling the tables (W3.css) and charting (Chart.js). These resources can be placed within SFMC to reduce external dependencies --> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> /* Set the table width to 100% */ .custom-table { width: 100%; border-collapse: collapse; /* Remove spacing between table cells */ } /* Set the first column width to 25% */ .custom-table th, .custom-table td { width: 25%; border: 1px solid #000; /* Optional: Add borders for visual clarity */ padding: 8px; text-align: center; } /* Set the second column width to 75% */ .custom-table td:nth-child(2) { width: 75%; } .sortable-header { cursor: pointer; text-decoration: underline; } .sortable-header:hover { color: blue; /* Change color on hover */ } /* Create two unequal columns that float next to each other */ * { box-sizing: border-box; } #myInput { background-image: url('/css/searchicon.png'); background-position: 10px 10px; background-repeat: no-repeat; width: 100%; font-size: 16px; padding: 12px 20px 12px 40px; border: 1px solid #ddd; margin-bottom: 12px; } </style> <link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css"> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script> <!-- AMPScript to fetch the selected automation ID from the URL parameter --> %%[ /* Declare Variables */ var @nsuccess, @nerrors, @ai /* Get Subscriber in content from URL */ Set @ai = RequestParameter("ai") ]%% <!-- Div to set table styles --> <div class="row w3-cell-row w3-table-all w3-hoverable"> <table class="custom-table"> <tr> <td> <!-- Code to divide the page into sections --> <!-- The left section with 25% width of the dashboard displays a list of automations using a table. Below SSJS retrieves the list of automations from SFMC using the WSProxy object. The automation names are displayed in the table rows --> <div class="w3-light-grey w3-bar-block"> <input type="text" id="myInput" onkeyup="filterAutomations()" placeholder="Search for an automation.." title="Type in a name"> <script runat="server"> Platform.Load("Core", "1.1.1"); var prox = new Script.Util.WSProxy(); var cols = ["Name", "CustomerKey"]; var filter = { Property: "Status", SimpleOperator: "IN", Value: [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8] }; var res = prox.retrieve("Automation", cols, filter); if (res.Results.length > 0) { var statusObj = { "-1": "Error", "0": "BuildingError", "1": "Building", "2": "Ready", "3": "Running", "4": "Paused", "5": "Stopped", "6": "Scheduled", "7": "Awaiting Trigger", "8": "InactiveTrigger" }; Write('<table id="atable" class="w3-table w3-bordered w3-striped"><tr><th colspan="2">Select the automation</th></tr>') for (var i = 0; i < res.Results.length; i++) { var autoName = res.Results[i].Name; var ID = res.Results[i].CustomerKey; var autoStatusNum = res.Results[i].Status; var autoStatus = statusObj[autoStatusNum]; Write('<tr><td>' + autoName + '</td><td style="display:none;">' + ID + '</td></tr>') } Write('</table>') } </script> </div> <!-- The right section of the dashboard displays the instances of the selected automation. It uses a table to show the instance details such as name, start time, completion time, status, and status message. --> </td> <td> <div> <!-- Charting canvas --> <canvas id="pie-chart" width="150" height="50"></canvas> <!-- below code retrieves the instances based on the selected automation ID and populates the table rows accordingly --> <div class="w3-light-grey w3-bar-block"> <script runat="server"> Platform.Load("Core", "1.1.1"); var prox = new Script.Util.WSProxy(); var automationkey = Variable.GetValue("@ai"); var cols = ["Name", "Status", "CompletedTime", "StatusMessage", "StartTime"]; if (automationkey !== null && automationkey !== "") { var filter = { Property: "CustomerKey", SimpleOperator: "EQUALS", Value: automationkey } var res = prox.retrieve("AutomationInstance", cols, filter); /* Variables to hold the number of successful runs and fails of an automation. These are calculated in the below for loop and written to HTML as hidden attributes. These attributes are read by client-side JavaScript and charted using Chart.js */ var nerrors = 0; var nsuccess = 0; if (res.Results.length > 1) { Write('<table id="aitable" class="w3-table w3-bordered w3-striped">'); Write('<tr>'); Write('<th onclick="sortTable(0)" class="sortable-header">Automation Name ⇅</th>'); Write('<th onclick="sortTable(1)" class="sortable-header">Start Time ⇅</th>'); Write('<th onclick="sortTable(2)" class="sortable-header">Completed Time ⇅</th>'); Write('<th onclick="sortTable(3)" class="sortable-header">Status Message ⇅</th>'); Write('</tr>'); for (var i = 0; i < res.Results.length; i++) { var autoName = res.Results[i].Name; var StartTime = res.Results[i].StartTime; var CompletedTime = res.Results[i].CompletedTime; var Status = res.Results[i].Status; var StatusMessage = res.Results[i].StatusMessage; if (StatusMessage == 'Error') { nerrors = nerrors + 1; } if (StatusMessage == 'Complete') { nsuccess = nsuccess + 1; } Write('<tr><td>' + autoName + '</td><td>' + StartTime + '</td><td>' + CompletedTime + '</td><td>' + StatusMessage + '</td></tr>'); } Write('</table>') Variable.SetValue("@nsuccess", nsuccess); Variable.SetValue("@nerrors", nerrors); } else { Write('<h1 align="center">No Instances found for this Automation</h1>'); } } else { Write('<h1 align="center">Select an automation from the left</h1>'); } </script> </div> </div> </td> </tr> </table> <!-- SSJS writes the number of automation success and failures to the DOM (not displayed to the user) so Client-side JS can read the same for Charting --> <div style="display:none;"> <p id="nsuccess">%%=v(@nsuccess)=%%</p> <p id="nerrors">%%=v(@nerrors)=%%</p> </div> </div> <!-- The JavaScript code for charting retrieves the values of the counters from the DOM and uses Chart.js to create a pie chart. The chart represents the number of completed and errored instances using different colors --> <script> var data = []; data[data.length] = document.getElementById("nsuccess").innerHTML; data[data.length] = document.getElementById("nerrors").innerHTML; new Chart(document.getElementById("pie-chart"), { type: 'pie', data: { labels: ["Completed", "Errored"], datasets: [{ backgroundColor: ["#82CD47", "#FB3569"], data: data }] }, options: { title: { display: false, text: 'Chart JS Pie Chart Example' } } }); /* The code includes a function addRowHandlers() that adds click handlers to the rows of the automation list table. When a row is clicked, the function retrieves the automation ID from the second cell of the row and appends this to the a URL. The page then refreshes to the new URL, displaying automation instances section for the automation */ function addRowHandlers() { var table = document.getElementById("atable"); var rows = table.getElementsByTagName("tr"); for (i = 0; i < rows.length; i++) { var currentRow = table.rows[i]; var createClickHandler = function (row) { return function () { var cell = row.getElementsByTagName("td")[1]; var ai = cell.innerHTML; console.log(ai); var url = 'https://yoursfmcsubdomain.com/Automation%20Dashboard?ai=' + ai; |
Enhancements:
Alphabetical Sorting of Automations
Automations are now sorted alphabetically by their names on the server side, ensuring an intuitive order when viewing the list.
Sorting is implemented using the localeCompare function in JavaScript, ensuring accurate, locale-aware string comparisons.
Exclusion of Backend Automations:
Backend-generated automations generated by journeys with names ending in ISO-formatted timestamps (e.g., - 2023-08-15T141247.914) are filtered out to declutter the dashboard.
A regular expression implemented in SSJS ensures these automations are excluded before rendering the list.
Key Changes to the Code
Sorting Automations
res.Results.sort(function (a, b) { return a.Name.localeCompare(b.Name); });
This ensures the list of automations appears in alphabetical order when the page loads, making it easier for developers to locate specific items.
Regex-Based Exclusion of Backend Automations
Automations with names ending in ISO-formatted timestamps are identified and excluded using a regular expression:var isoPattern = /-\s20\d{2}-\d{2}-\d{2}T\d{6}\.\d{3}$/; if (!isoPattern.test(autoName)) { Write('<tr data-id="' + ID + '"><td>' + autoName + '</td></tr>'); }
This ensures the dashboard only displays automations relevant to the user, removing unnecessary clutter caused by backend processes.
Final complete Code:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>SFMC Automation Dashboard</title> <style> .custom-table { width: 100%; border-collapse: collapse; } .custom-table th, .custom-table td { border: 1px solid #000; padding: 8px; text-align: center; } #myInput { width: 100%; font-size: 16px; padding: 12px; border: 1px solid #ddd; margin-bottom: 12px; } .sortable-header { cursor: pointer; text-decoration: underline; } .sortable-header:hover { color: blue; } </style> <link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css"> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script> </head> <body> %%[ var @ai, @nsuccess, @nerrors Set @ai = RequestParameter("ai") ]%% <div class="row w3-cell-row w3-table-all w3-hoverable"> <table class="custom-table"> <tr> <td style="width: 25%;"> <input type="text" id="myInput" onkeyup="filterAutomations()" placeholder="Search for an automation..."> <script runat="server"> Platform.Load("Core", "1.1.1"); var prox = new Script.Util.WSProxy(); var cols = ["Name", "CustomerKey"]; var filter = { Property: "Status", SimpleOperator: "IN", Value: [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8] }; var res = prox.retrieve("Automation", cols, filter); if (res.Results.length > 0) { // Sort automations alphabetically by name res.Results.sort(function (a, b) { return a.Name.localeCompare(b.Name); }); // Regex to exclude automations with ISO-like date suffixes var isoPattern = /-\s20\d{2}-\d{2}-\d{2}T\d{6}\.\d{3}$/; Write('<table id="atable" class="w3-table w3-bordered w3-striped"><tr><th>Automation Name</th></tr>'); for (var i = 0; i < res.Results.length; i++) { var autoName = res.Results[i].Name; var ID = res.Results[i].CustomerKey; // Exclude names matching the ISO-like date pattern if (!isoPattern.test(autoName)) { Write('<tr data-id="' + ID + '"><td>' + autoName + '</td></tr>'); } } Write('</table>'); } else { Write('<p>No automations found.</p>'); } </script> </td> <td style="width: 75%;"> <div> <canvas id="pie-chart" width="150" height="50"></canvas> <div> <script runat="server"> Platform.Load("Core", "1.1.1"); var prox = new Script.Util.WSProxy(); var automationkey = Variable.GetValue("@ai"); var cols = ["Name", "Status", "CompletedTime", "StatusMessage", "StartTime"]; if (automationkey) { var filter = { Property: "CustomerKey", SimpleOperator: "EQUALS", Value: automationkey }; var res = prox.retrieve("AutomationInstance", cols, filter); var nerrors = 0, nsuccess = 0; if (res.Results.length > 0) { Write('<table id="aitable" class="w3-table w3-bordered w3-striped"><tr><th>Automation Name</th><th>Start Time</th><th>Completed Time</th><th>Status Message</th></tr>'); for (var i = 0; i < res.Results.length; i++) { var instance = res.Results[i]; var autoName = instance.Name; var StartTime = instance.StartTime; var CompletedTime = instance.CompletedTime; var StatusMessage = instance.StatusMessage; if (StatusMessage === "Error") nerrors++; if (StatusMessage === "Complete") nsuccess++; Write('<tr><td>' + autoName + '</td><td>' + StartTime + '</td><td>' + CompletedTime + '</td><td>' + StatusMessage + '</td></tr>'); } Write('</table>'); } else { Write('<h3>No instances found for this automation.</h3>'); } Variable.SetValue("@nsuccess", nsuccess); Variable.SetValue("@nerrors", nerrors); } else { Write('<h3>Select an automation from the list.</h3>'); } </script> </div> </div> </td> </tr> </table> </div> <div style="display:none;"> <p id="nsuccess">%%=v(@nsuccess)=%%</p> <p id="nerrors">%%=v(@nerrors)=%%</p> </div> <script> // Attach click listeners to table rows dynamically document.addEventListener("DOMContentLoaded", function () { var rows = document.querySelectorAll("#atable tr[data-id]"); rows.forEach(function (row) { row.addEventListener("click", function () { var id = row.getAttribute("data-id"); selectAutomation(id); }); }); }); // Redirect to the URL with the selected automation ID function selectAutomation(id) { var url = window.location.href.split('?')[0] + "?ai=" + id; window.location.href = url; } // Filter automation table based on user input function filterAutomations() { var input = document.getElementById("myInput"); var filter = input.value.toUpperCase(); var table = document.getElementById("atable"); var tr = table.getElementsByTagName("tr"); for (var i = 0; i < tr.length; i++) { var td = tr[i].getElementsByTagName("td")[0]; if (td) { var txtValue = td.textContent || td.innerText; tr[i].style.display = txtValue.toUpperCase().indexOf(filter) > -1 ? "" : "none"; } } } // Create a pie chart using Chart.js var data = [ parseInt(document.getElementById("nsuccess").innerText) || 0, parseInt(document.getElementById("nerrors").innerText) || 0 ]; new Chart(document.getElementById("pie-chart"), { type: 'pie', data: { labels: ["Completed", "Errored"], datasets: [{ backgroundColor: ["#82CD47", "#FB3569"], data: data }] }, options: { title: { display: false } } }); </script> </body> </html>
No comments:
Post a Comment