From 93192d8d14dbbb635e0163bc3d2164d1c062bd9f Mon Sep 17 00:00:00 2001 From: Will Gunter <74369489+wcgunter@users.noreply.github.com> Date: Thu, 6 Jul 2023 10:05:43 -0700 Subject: [PATCH] UI - Display custom input variables (#156) * Add input table, copy feature * Update history json download to include input vars * Show input variables on processes page * Update test to account for column changes * Update processestest to use datatables search * Add summary to input vars (ala history) * Add missing close carot on history page * Add copy button to input var column (processes pg) * update what is displayed before/after details tag * Update display on processes page, export fix * Move input vars table to below history table --- .../jpl/cws/scheduler/CwsProcessInstance.java | 13 +- .../cws/scheduler/InputVariableDetail.java | 14 ++ .../jpl/cws/service/CwsConsoleService.java | 55 +++++++- .../test/integration/ui/HistoryTestIT.java | 6 - .../test/integration/ui/ProcessesTestIT.java | 42 +++--- .../cws/test/integration/ui/WebTestIT.java | 1 - cws-ui/src/main/webapp/css/dashboard.css | 108 ++++++++++++++- cws-ui/src/main/webapp/images/copy.svg | 17 +++ install/cws-ui/history.ftl | 128 +++++++++++++----- install/cws-ui/processes.ftl | 124 ++++++++++------- 10 files changed, 393 insertions(+), 115 deletions(-) create mode 100644 cws-service/src/main/java/jpl/cws/scheduler/InputVariableDetail.java create mode 100644 cws-ui/src/main/webapp/images/copy.svg diff --git a/cws-service/src/main/java/jpl/cws/scheduler/CwsProcessInstance.java b/cws-service/src/main/java/jpl/cws/scheduler/CwsProcessInstance.java index ed7dfa96..16e18938 100644 --- a/cws-service/src/main/java/jpl/cws/scheduler/CwsProcessInstance.java +++ b/cws-service/src/main/java/jpl/cws/scheduler/CwsProcessInstance.java @@ -21,6 +21,8 @@ public class CwsProcessInstance { private Timestamp procStartTime; // from Camunda ACT_HI_PROCINST_ table private Timestamp procEndTime; // from Camunda ACT_HI_PROCINST_ table + + private String inputVariables; public CwsProcessInstance( String uuid, @@ -34,7 +36,8 @@ public CwsProcessInstance( String claimedByWorker, String startedByWorker, Timestamp procStartTime, - Timestamp procEndTime) { + Timestamp procEndTime, + String inputVariables) { super(); this.uuid = uuid; this.procDefKey = procDefKey; @@ -48,6 +51,7 @@ public CwsProcessInstance( this.startedByWorker = startedByWorker; this.procStartTime = procStartTime; this.procEndTime = procEndTime; + this.inputVariables = inputVariables; } public String getUuid() { @@ -82,6 +86,13 @@ public String getStatus() { public void setStatus(String status) { this.status = status; } + public void setInputVariables(String input) { + this.inputVariables = input; + } + + public String getInputVariables() { + return inputVariables; + } public String getInitiationKey() { return initiationKey; diff --git a/cws-service/src/main/java/jpl/cws/scheduler/InputVariableDetail.java b/cws-service/src/main/java/jpl/cws/scheduler/InputVariableDetail.java new file mode 100644 index 00000000..7f3b16a6 --- /dev/null +++ b/cws-service/src/main/java/jpl/cws/scheduler/InputVariableDetail.java @@ -0,0 +1,14 @@ +package jpl.cws.scheduler; + +import java.util.Date; + +public class InputVariableDetail { + + public String varName; + public String varValue; + + public InputVariableDetail(String name, String value) { + this.varName = name; + this.varValue = value; + } +} diff --git a/cws-service/src/main/java/jpl/cws/service/CwsConsoleService.java b/cws-service/src/main/java/jpl/cws/service/CwsConsoleService.java index 6ecfdfaa..bef32a67 100644 --- a/cws-service/src/main/java/jpl/cws/service/CwsConsoleService.java +++ b/cws-service/src/main/java/jpl/cws/service/CwsConsoleService.java @@ -3,6 +3,7 @@ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; +import java.io.Console; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; @@ -72,6 +73,7 @@ import jpl.cws.scheduler.CwsProcessInstance; import jpl.cws.scheduler.ExternalWorker; import jpl.cws.scheduler.HistoryDetail; +import jpl.cws.scheduler.InputVariableDetail; import jpl.cws.scheduler.LogHistory; import jpl.cws.scheduler.SchedulerQueueUtils; import jpl.cws.scheduler.Worker; @@ -610,6 +612,52 @@ public LogHistory getHistoryForProcess(String processInstanceId) { return history; } + public String getInputVariablesForProcess(String processInstanceId) { + List historyDetails = new ArrayList(); + getHistoryVarDetails(historyDetails, processInstanceId); + + String output = ""; + String before = ""; + String after = ""; + int putAllAfter = 0; + int count = 0; + + for (HistoryDetail historyDetail : historyDetails) { + if (historyDetail.type.equals("VarUpdate") && historyDetail.activity.equals(processInstanceId)) { + if (count > 3) { + putAllAfter = 1; + } + String message = historyDetail.message; + String varType = message.substring(message.indexOf("("), message.indexOf(")")+1); + String varName = message.substring(message.indexOf(")")+2); + varName = varName.substring(0, varName.indexOf("=")-1) + " " + varType; + String varValue = message.substring(message.indexOf("=")+2); + String temp = "
" + varName + ": " + varValue + "
" + + "

"; + if (varName.contains("workerId")) { + after = after + temp; + } else if (varName.contains("startedOnWorkerId")) { + after = after + temp; + putAllAfter = 1; + } else if (putAllAfter == 0) { + before = before + temp; + } else { + after = after + temp; + } + count++; + } + } + if (after.isEmpty()) { + output = before; + } else { + output = before + "
Show All" + after + "
"; + } + return output; + } + public List getExternalWorkersUiDTO() { List workers = new ArrayList(); @@ -1033,6 +1081,10 @@ public List getFilteredProcessInstancesCamunda(String superP String startedByWorker = (String) row.get("started_by_worker"); Timestamp procStartTime = (Timestamp) row.get("proc_start_time"); Timestamp procEndTime = (Timestamp) row.get("proc_end_time"); + String inputVars = ""; + if (procInstIdObj != null) { + inputVars = getInputVariablesForProcess(procInstIdObj.toString()); + } CwsProcessInstance instance = new CwsProcessInstance(uuidObj == null ? null : uuidObj.toString(), procDefKeyObj == null ? null : procDefKeyObj.toString(), procInstIdObj == null ? null : procInstIdObj.toString(), @@ -1042,7 +1094,8 @@ public List getFilteredProcessInstancesCamunda(String superP createdTimestampObj == null ? null : createdTimestampObj, updatedTimestampObj == null ? null : updatedTimestampObj, claimedByWorker == null ? null : claimedByWorker, startedByWorker == null ? null : startedByWorker, - procStartTime == null ? null : procStartTime, procEndTime == null ? null : procEndTime); + procStartTime == null ? null : procStartTime, procEndTime == null ? null : procEndTime, + inputVars); instances.add(instance); } diff --git a/cws-test/src/test/java/jpl/cws/test/integration/ui/HistoryTestIT.java b/cws-test/src/test/java/jpl/cws/test/integration/ui/HistoryTestIT.java index 94579758..4d322fbd 100644 --- a/cws-test/src/test/java/jpl/cws/test/integration/ui/HistoryTestIT.java +++ b/cws-test/src/test/java/jpl/cws/test/integration/ui/HistoryTestIT.java @@ -3,8 +3,6 @@ import static org.junit.Assert.assertTrue; import java.io.IOException; -import java.util.List; -import java.util.Set; import org.junit.Ignore; import org.junit.Test; @@ -12,9 +10,6 @@ import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.Keys; import org.openqa.selenium.WebElement; -import org.openqa.selenium.logging.LogEntries; -import org.openqa.selenium.logging.LogEntry; -import org.openqa.selenium.logging.LogType; import org.openqa.selenium.support.ui.Select; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -99,7 +94,6 @@ && findOnPage("Command 'rmdir Test' exit code: 0")) { System.out.println(e.toString()); scriptPass = false; } - screenShot("HistoryTestIT-runResultsTest"); assertTrue("Deployments Page Test reported unexpected success value (scriptPass="+scriptPass+")", scriptPass); } diff --git a/cws-test/src/test/java/jpl/cws/test/integration/ui/ProcessesTestIT.java b/cws-test/src/test/java/jpl/cws/test/integration/ui/ProcessesTestIT.java index b5e92170..fd3d3c69 100644 --- a/cws-test/src/test/java/jpl/cws/test/integration/ui/ProcessesTestIT.java +++ b/cws-test/src/test/java/jpl/cws/test/integration/ui/ProcessesTestIT.java @@ -8,6 +8,7 @@ import org.junit.Test; import org.openqa.selenium.By; +import org.openqa.selenium.Keys; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; @@ -69,30 +70,27 @@ public void runStatusCompleteTest() throws IOException { WebElement myTable = driver.findElement(By.tagName("table")); wait.until(ExpectedConditions.visibilityOfAllElementsLocatedBy(By.tagName("tr"))); List myRows = myTable.findElements(By.tagName("tr")); + + sleep(8000); log.info("Locating Test Processes Page from table rows and verifying that it completed."); - for (int i = 0; i < myRows.size(); i++) { - String row = myRows.get(i).getText(); - log.info(row); - - if (row.contains("test_processes_page")) { - log.info("Success, found proc def!"); - - wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("id('processes-table')/tbody/tr[\"+i+\"]"))); - //Looking at row index for test_proccesses_page and checking for a complete status - String status = driver.findElement(By.xpath("id('processes-table')/tbody/tr["+i+"]")).getText(); - - if (status.contains("complete")) { - log.info("Found status complete for procDef"); - scriptPass = true; - testCasesCompleted++; - break; - } else { - log.info("Fail."); - scriptPass = false; - } - } - } + waitForElementXPath("//div[@id=\'processes-table_filter\']/label/input"); + + driver.findElement(By.xpath("//div[@id=\'processes-table_filter\']/label/input")).click(); + driver.findElement(By.xpath("//div[@id=\'processes-table_filter\']/label/input")).sendKeys("test_snippets_page"); + driver.findElement(By.xpath("//div[@id=\'processes-table_filter\']/label/input")).sendKeys(Keys.ENTER); + + waitForElementID("processes-table"); + //selenium: check if "test_processes_page" is on the page + if (findOnPage("test_processes_page") && findOnPage("complete")) { + log.info("Success, found proc def!"); + log.info("Found status complete for procDef"); + scriptPass = true; + testCasesCompleted++; + } else { + log.info("Fail."); + scriptPass = false; + } log.info("------ END ProcessesTestIT:runStatusCompleteTest:runStatusCompleteTest ------"); } catch (Throwable e) { diff --git a/cws-test/src/test/java/jpl/cws/test/integration/ui/WebTestIT.java b/cws-test/src/test/java/jpl/cws/test/integration/ui/WebTestIT.java index a6e6fca5..c104ecd2 100644 --- a/cws-test/src/test/java/jpl/cws/test/integration/ui/WebTestIT.java +++ b/cws-test/src/test/java/jpl/cws/test/integration/ui/WebTestIT.java @@ -202,7 +202,6 @@ public void runHelloWorldTest() { // Wait for Finish sleep(90000); - if(findOnPage("completed")) { goToProcesses(); sleep(1000); diff --git a/cws-ui/src/main/webapp/css/dashboard.css b/cws-ui/src/main/webapp/css/dashboard.css index a799636b..9ca0ab03 100644 --- a/cws-ui/src/main/webapp/css/dashboard.css +++ b/cws-ui/src/main/webapp/css/dashboard.css @@ -272,4 +272,110 @@ div[class*="bar-"]{ } #logData th:nth-child(1){width: 100px} #logData th:nth-child(3){width: 100px} -.no-results{text-align: center; color:red; text-transform: uppercase;} \ No newline at end of file +.no-results{text-align: center; color:red; text-transform: uppercase;} + +.clipboard { + height: 15px; + width: auto; + opacity: 0.5; + transition: 0.3s; +} + +.clipboard:hover { + opacity: 1; +} + +.svgHolder { + float: right; +} + +.copy { + /* button */ + --button-bg: #ffffff00; + --button-hover-bg: #ffffff00; + --button-text-color: #ffffff00; + --button-hover-text-color: #ffffff00; + --button-border-radius: px; + --button-diameter: 25px; + --button-outline-width: 1px; + --button-outline-color: rgb(141, 141, 141); + /* tooltip */ + --tooltip-bg: #f4f3f3; + --toolptip-border-radius: 4px; + --tooltip-font-family: Menlo, Roboto Mono, monospace; + --tooltip-font-size: 12px; + --tootip-text-color: rgb(50, 50, 50); + --tooltip-padding-x: 7px; + --tooltip-padding-y: 7px; + --tooltip-offset: 8px; + --tooltip-transition-duration: 0.3s; +} + +.copy { + box-sizing: border-box; + width: var(--button-diameter); + height: var(--button-diameter); + border-radius: var(--button-border-radius); + background-color: var(--button-bg); + color: var(--button-text-color); + border: none; + cursor: pointer; + position: relative; + outline: none; + float: right; +} + +.tooltip { + position: absolute; + opacity: 0; + visibility: 0; + top: 0; + left: 50%; + transform: translateX(-50%); + white-space: nowrap; + color: var(--tootip-text-color); + background: var(--tooltip-bg); + padding: var(--tooltip-padding-y) var(--tooltip-padding-x); + border-radius: var(--toolptip-border-radius); + pointer-events: none; + transition: all var(--tooltip-transition-duration) cubic-bezier(0.68, -0.55, 0.265, 1.55); +} + +.tooltip::before { + content: attr(data-text-initial); +} + +.tooltip::after { + content: ""; + position: absolute; + bottom: calc(var(--tooltip-padding-y) / 2 * -1); + width: var(--tooltip-padding-y); + height: var(--tooltip-padding-y); + background: inherit; + left: 50%; + transform: translateX(-50%) rotate(45deg); + z-index: -999; + pointer-events: none; +} + +.copy svg { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + + +/* actions */ + +.copy:hover .tooltip, +.copy:focus:not(:focus-visible) .tooltip { + opacity: 1; + visibility: visible; + top: calc((100% + var(--tooltip-offset)) * -1); +} + +.copy:focus:not(:focus-visible) .tooltip::before { + content: attr(data-text-end); +} + diff --git a/cws-ui/src/main/webapp/images/copy.svg b/cws-ui/src/main/webapp/images/copy.svg new file mode 100644 index 00000000..4a13a7cc --- /dev/null +++ b/cws-ui/src/main/webapp/images/copy.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/install/cws-ui/history.ftl b/install/cws-ui/history.ftl index d13ad6dd..a4277297 100644 --- a/install/cws-ui/history.ftl +++ b/install/cws-ui/history.ftl @@ -13,6 +13,11 @@ + @@ -491,42 +550,40 @@ <#include "navbar.ftl"> -
+

History

- - - - - - - - - - - - - - - - - - - -
Process DefinitionUnknown
Process Instance IDUnknown
Start TimeN/A
End TimeN/A
DurationN/A
StatusUnknown
+ + + + + + + + + + + + + + + + + + + +
Process DefinitionUnknown
Process Instance IDUnknown
Start TimeN/A
End TimeN/A
DurationN/A
StatusUnknown
- -
- +
+ + + + + + + +
Input VariableValue
- +
@@ -565,7 +622,6 @@
- \ No newline at end of file diff --git a/install/cws-ui/processes.ftl b/install/cws-ui/processes.ftl index c0d0618d..9a660fde 100644 --- a/install/cws-ui/processes.ftl +++ b/install/cws-ui/processes.ftl @@ -31,6 +31,15 @@ #processes-table { font-size: 90%; } + summary::before { + margin-right: .5ch; + content: '▶️'; + transition: 0.2s; + } + + details[open] summary::before { + transform: rotate(90deg); + } @@ -119,10 +128,8 @@ - + - - @@ -130,6 +137,7 @@ + @@ -365,6 +373,7 @@ } } } + function viewHistory(procInstId) { @@ -455,9 +464,8 @@ table.row.add( $(""+ "" + - "" + - "" + - "" + + "" + ""+ "" + ""+ @@ -465,6 +473,7 @@ ""+ ""+ ""+ + ""+ "") ); } @@ -690,6 +699,10 @@ }); } + function copyInput(varValue) { + navigator.clipboard.writeText(varValue); + } + // -------------------------------------------------------------------------------- // Function fired when user clicks on "Retry Selected Failed to Start Rows..." in drop-down list // @@ -1278,80 +1291,97 @@ var startedOnWorker = ""; var workerIP = ""; var duration = ""; + var process_end = ""; + var inputVars = ""; + var inputVarsTemp = ""; if (data[8] !== "") { - startedOnWorker = data[8]; - startedOnWorker = startedOnWorker.substring(0, startedOnWorker.indexOf("
")); - - workerIP = data[8]; - //get everything after - workerIP = workerIP.substring(workerIP.indexOf("") + 4, workerIP.length); + startedOnWorker = data[6].substring(0, data[6].indexOf("
")); + workerIP = data[6].substring(data[6].indexOf("")+4, data[6].length); } else { - startedOnWorker = data[8]; - workerIP = ""; + startedOnWorker = data[6]; } - if (data[10] !== "") { - duration = data[10]; + if (data[8] !== "") { + duration = data[8]; //get everything after
but before duration = duration.substring(duration.indexOf("
") + 7, duration.indexOf("")); - } else { - duration = ""; + process_end = data[8].substring(0, data[8].indexOf("
")-1); } - thisProcJSON["definition_key"] = data[4]; - thisProcJSON["process_instance_id"] = data[5]; - thisProcJSON["status"] = data[6]; - thisProcJSON["initiator"] = data[3]; - thisProcJSON["schedule_queued_time"] = data[7]; + if (data[9] !== "") { + inputVarsTemp = data[9].replaceAll("
", ", "); + inputVarsTemp = inputVarsTemp.replaceAll("
Show All", ""); + while (inputVarsTemp.indexOf("") !== -1) { + inputVarsTemp = inputVarsTemp.substring(inputVarsTemp.indexOf("") + 3, inputVarsTemp.length); + inputVarsTemp = inputVarsTemp.replace("", "") + inputVars += inputVarsTemp.substring(0, inputVarsTemp.indexOf("")) + ", "; + } + inputVars = inputVars.substring(0, inputVars.length - 2); + } + + thisProcJSON["definition_key"] = data[2]; + thisProcJSON["process_instance_id"] = data[3]; + thisProcJSON["status"] = data[4]; + thisProcJSON["schedule_queued_time"] = data[5]; thisProcJSON["started_on_worker"] = startedOnWorker; thisProcJSON["worker_ip"] = workerIP; - thisProcJSON["process_start"] = data[9]; - thisProcJSON["process_end"] = data[10]; + thisProcJSON["process_start"] = data[7]; + thisProcJSON["process_end"] = process_end; thisProcJSON["duration"] = duration; + thisProcJSON["input_variables"] = inputVars; - processes[data[5]] = thisProcJSON; + processes[data[3]] = thisProcJSON; } ); } else { - dt.rows({selected:true, search:'applied'}).every( function ( rowIdx, tableLoop, rowLoop ) { + dt.rows({selected: true, search:'applied'}).every( function ( rowIdx, tableLoop, rowLoop ) { var data = this.data(); var thisProcJSON = {}; var startedOnWorker = ""; var workerIP = ""; var duration = ""; + var process_end = ""; + var inputVars = ""; + var inputVarsTemp = ""; if (data[8] !== "") { - startedOnWorker = data[8]; - startedOnWorker = startedOnWorker.substring(0, startedOnWorker.indexOf("
")); - - workerIP = data[8]; - //get everything after - workerIP = workerIP.substring(workerIP.indexOf("") + 4, workerIP.length); + startedOnWorker = data[6].substring(0, data[6].indexOf("
")); + workerIP = data[6].substring(data[6].indexOf("")+4, data[6].length); } else { - startedOnWorker = data[8]; - workerIP = ""; + startedOnWorker = data[6]; } - if (data[10] !== "") { - duration = data[10]; + if (data[8] !== "") { + duration = data[8]; //get everything after
but before duration = duration.substring(duration.indexOf("
") + 7, duration.indexOf("")); - } else { - duration = ""; + process_end = data[8].substring(0, data[8].indexOf("
")-1); + + } + + if (data[9] !== "") { + inputVarsTemp = data[9].replaceAll("
", ", "); + inputVarsTemp = inputVarsTemp.replaceAll("
Show All", ""); + while (inputVarsTemp.indexOf("") !== -1) { + inputVarsTemp = inputVarsTemp.substring(inputVarsTemp.indexOf("") + 3, inputVarsTemp.length); + inputVarsTemp = inputVarsTemp.replace("", "") + inputVars += inputVarsTemp.substring(0, inputVarsTemp.indexOf("")) + ", "; + } + inputVars = inputVars.substring(0, inputVars.length - 2); } - thisProcJSON["definition_key"] = data[4]; - thisProcJSON["process_instance_id"] = data[5]; - thisProcJSON["status"] = data[6]; - thisProcJSON["initiator"] = data[3]; - thisProcJSON["schedule_queued_time"] = data[7]; + thisProcJSON["definition_key"] = data[2]; + thisProcJSON["process_instance_id"] = data[3]; + thisProcJSON["status"] = data[4]; + thisProcJSON["schedule_queued_time"] = data[5]; thisProcJSON["started_on_worker"] = startedOnWorker; thisProcJSON["worker_ip"] = workerIP; - thisProcJSON["process_start"] = data[9]; - thisProcJSON["process_end"] = data[10]; + thisProcJSON["process_start"] = data[7]; + thisProcJSON["process_end"] = process_end; thisProcJSON["duration"] = duration; + thisProcJSON["input_variables"] = inputVars; - processes[data[5]] = thisProcJSON; + processes[data[3]] = thisProcJSON; } ); } jsonFile["processes"] = processes;
Select Initiator Definition Key Proc Inst ID StatusStarted on Worker Process Start Process EndInput Variables
HistorySubprocs"+ (res[i].initiationKey == undefined ? '' : res[i].initiationKey) + "History" + + "Subprocs"+ res[i].procDefKey +""+ (res[i].status == 'incident' ? ("" + procInstId + "") : procInstId) + ""+ res[i].status +""+ (res[i].startedByWorker == undefined ? '' : res[i].startedByWorker + workerIP) + ""+ procStartTime + ""+ procEndTime + procDuration + ""+ (res[i].inputVariables == undefined ? '' : res[i].inputVariables) + "