Browse Source

feature: Cleanup execution dialog, and log display status. (#378)

* feature: Cleanup execution dialog, and log display status.

* fmt: Remove empty block

* cicd: Use ActionStatusDisplay in test

* bugfix: missed promise

* bugfix: missed promise

* bugfix: Didnt return getText on ActionStatusDisplay
James Read 1 năm trước cách đây
mục cha
commit
fab0264d9b

+ 1 - 3
integration-tests/lib/elements.js

@@ -28,13 +28,11 @@ export async function getRootAndWait() {
 }
 
 export async function requireExecutionDialogStatus (webdriver, expected) {
-  const domStatus = await webdriver.findElement(By.id('execution-dialog-status'))
-
   // It seems that webdriver will not give us text if domStatus is hidden (which it will be until complete)
   await webdriver.executeScript('window.executionDialog.domExecutionDetails.hidden = false')
 
   await webdriver.wait(new Condition('wait for action to be running', async function () {
-    const actual = await domStatus.getText()
+    const actual = await webdriver.executeScript('return window.executionDialog.domStatus.getText()')
 
     if (actual === expected) {
       return true

+ 5 - 16
webui.dev/index.html

@@ -56,7 +56,7 @@
 						<tr title = "untitled">
 							<th>Timestamp</th>
 							<th>Log</th>
-							<th>Exit Code</th>
+							<th>Status</th>
 						</tr>
 					</thead>
 					<tbody id = "logTableBody" />
@@ -127,27 +127,16 @@
 					<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M3 3h6v2H6.462l4.843 4.843l-1.415 1.414L5 6.367V9H3zm0 18h6v-2H6.376l4.929-4.928l-1.415-1.414L5 17.548V15H3zm12 0h6v-6h-2v2.524l-4.867-4.866l-1.414 1.414L17.647 19H15zm6-18h-6v2h2.562l-4.843 4.843l1.414 1.414L19 6.39V9h2z"/></svg>
 				</button>
 			</div>
-			<div id = "execution-dialog-basics" class = "padded-content">
-					<strong>Started: </strong><span id = "execution-dialog-datetime-started">unknown</span>
+			<div id = "execution-dialog-basics" class = "padded-content-sides">
+					<strong>Duration: </strong><span id = "execution-dialog-duration">unknown</span>
 			</div>
-			<div id = "execution-dialog-details" class = "padded-content">
-				<p>
-					<strong>Finished: </strong><span id = "execution-dialog-datetime-finished">unknown</span>
-				</p>
-				<p>
-					<strong>Exit Code: </strong><span id = "execution-dialog-exit-code">unknown</span>
-				</p>
+			<div id = "execution-dialog-details" class = "padded-content-sides">
 				<p>
 					<strong>Status: </strong><span id = "execution-dialog-status">unknown</span>
 				</p>
 			</div>
 
-			<div id = "execution-dialog-output">
-				<details id = "execution-dialog-output-details">
-					<summary class = "padded-content">Output</summary>
-					<div id = "execution-dialog-xterm" />
-				</details>
-			</div>
+			<div id = "execution-dialog-xterm"></div>
 
 			<div class = "buttons padded-content">
 				<button name = "kill" title = "Kill" id = "execution-dialog-kill-action">Kill</button>

+ 41 - 0
webui.dev/js/ActionStatusDisplay.js

@@ -0,0 +1,41 @@
+export class ActionStatusDisplay {
+  constructor (parentElement) {
+    this.exitCodeElement = document.createElement('span')
+    this.statusElement = document.createElement('span')
+    this.statusElement.innerText = 'unknown'
+
+    parentElement.innerText = ''
+    parentElement.appendChild(this.statusElement)
+    parentElement.appendChild(this.exitCodeElement)
+  }
+
+  getText () {
+    return this.statusElement.innerText
+  }
+
+  update (logEntry) {
+    this.statusElement.classList.remove(...this.statusElement.classList)
+
+    if (logEntry.executionFinished) {
+      this.statusElement.innerText = 'Completed'
+      this.exitCodeElement.innerText = ', Exit code: ' + logEntry.exitCode
+
+      if (logEntry.exitCode === 0) {
+        this.statusElement.classList.add('action-success')
+      } else if (logEntry.blocked) {
+        this.statusElement.innerText = 'Blocked'
+        this.statusElement.classList.add('action-blocked')
+        this.exitCodeElement.innerText = ''
+      } else if (logEntry.timedOut) {
+        this.statusElement.innerText = 'Timed out'
+        this.statusElement.classList.add('action-timeout')
+        this.exitCodeElement.innerText = ''
+      } else {
+        this.statusElement.classList.add('action-nonzero-exit')
+      }
+    } else {
+      this.statusElement.innerText = 'Still running...'
+      this.exitCodeElement.innerText = ''
+    }
+  }
+}

+ 37 - 44
webui.dev/js/ExecutionDialog.js

@@ -1,3 +1,5 @@
+import { ActionStatusDisplay } from './ActionStatusDisplay.js'
+
 // This ExecutionDialog is NOT a custom HTML element, but rather just picks up
 // the <dialog /> element out of index.html and just re-uses that - as only
 // one dialog can be shown at a time.
@@ -8,7 +10,6 @@ export class ExecutionDialog {
     this.domIcon = document.getElementById('execution-dialog-icon')
     this.domTitle = document.getElementById('execution-dialog-title')
     this.domOutput = document.getElementById('execution-dialog-xterm')
-    this.domOutputDetails = document.getElementById('execution-dialog-output-details')
     this.domOutputToggleBig = document.getElementById('execution-dialog-toggle-size')
     this.domOutputToggleBig.onclick = () => {
       this.toggleSize()
@@ -16,32 +17,28 @@ export class ExecutionDialog {
 
     this.domBtnKill = document.getElementById('execution-dialog-kill-action')
 
-    this.domDatetimeStarted = document.getElementById('execution-dialog-datetime-started')
-    this.domDatetimeFinished = document.getElementById('execution-dialog-datetime-finished')
-    this.domExitCode = document.getElementById('execution-dialog-exit-code')
-    this.domStatus = document.getElementById('execution-dialog-status')
+    this.domDuration = document.getElementById('execution-dialog-duration')
+    this.domStatus = new ActionStatusDisplay(document.getElementById('execution-dialog-status'))
 
     this.domExecutionBasics = document.getElementById('execution-dialog-basics')
     this.domExecutionDetails = document.getElementById('execution-dialog-details')
-    this.domExecutionOutput = document.getElementById('execution-dialog-output')
 
     window.terminal.open(this.domOutput)
   }
 
   showOutput () {
     this.domOutput.hidden = false
-    this.domOutputDetails.open = true
-    this.domExecutionOutput.hidden = false
+    this.domOutput.hidden = false
   }
 
   toggleSize () {
     if (this.dlg.classList.contains('big')) {
       this.dlg.classList.remove('big')
-      this.domOutputDetails.open = false
     } else {
       this.dlg.classList.add('big')
-      this.domOutputDetails.open = true
     }
+
+    window.terminal.fit.fit()
   }
 
   reset () {
@@ -54,10 +51,7 @@ export class ExecutionDialog {
 
     this.domIcon.innerText = ''
     this.domTitle.innerText = 'Waiting for result... '
-    this.domExitCode.innerText = '?'
-    this.domStatus.className = ''
-    this.domDatetimeStarted.innerText = ''
-    this.domDatetimeFinished.innerText = ''
+    this.domDuration.innerText = ''
 
     //    window.terminal.close()
 
@@ -68,12 +62,9 @@ export class ExecutionDialog {
     this.domExecutionBasics.hidden = false
 
     this.domExecutionDetails.hidden = true
-    this.domOutputDetails.open = false
 
     window.terminal.reset()
     window.terminal.fit.fit()
-
-    this.domExecutionOutput.hidden = true
   }
 
   show (actionButton) {
@@ -122,7 +113,7 @@ export class ExecutionDialog {
   executionTick () {
     this.executionSeconds++
 
-    this.domDatetimeStarted.innerText = this.executionSeconds + ' seconds ago'
+    this.updateDuration(this.executionSeconds + ' seconds ago', '')
   }
 
   hideEverythingApartFromOutput () {
@@ -158,50 +149,52 @@ export class ExecutionDialog {
     })
   }
 
+  updateDuration (started, finished) {
+    if (finished === '') {
+      this.domDuration.innerHTML = started
+    } else {
+      let delta = ''
+
+      try {
+        delta = (new Date(finished) - new Date(started)) / 1000
+        delta = new Intl.RelativeTimeFormat().format(delta, 'seconds').replace('in ', '').replace('ago', '')
+      } catch (e) {
+        console.warn('Failed to calculate delta', e)
+      }
+
+      this.domDuration.innerHTML = started + ' &rarr; ' + finished
+
+      if (delta !== '') {
+        this.domDuration.innerHTML += ' (' + delta + ')'
+      }
+    }
+  }
+
   renderExecutionResult (res) {
     this.res = res
 
     clearInterval(window.executionDialogTicker)
 
-    this.domExecutionOutput.hidden = false
+    this.domOutput.hidden = false
 
     if (!this.hideDetailsOnResult) {
       this.domExecutionDetails.hidden = false
-    } else {
-      this.domOutputDetails.open = true
     }
 
     this.executionTrackingId = res.logEntry.executionTrackingId
 
     this.domBtnKill.disabled = res.logEntry.executionFinished
 
-    if (res.logEntry.executionFinished) {
-      this.domStatus.innerText = 'Completed'
-      this.domStatus.classList.add('action-success')
-      this.domDatetimeFinished.innerText = res.logEntry.datetimeFinished
-
-      if (res.logEntry.timedOut) {
-        this.domExitCode.innerText = 'Timed out'
-        this.domStatus.classList.add('action-timeout')
-      } else if (res.logEntry.blocked) {
-        this.domStatus.innerText = 'Blocked'
-        this.domStatus.classList.add('action-blocked')
-      } else if (res.logEntry.exitCode !== 0) {
-        this.domStatus.innerText = 'Non-Zero Exit'
-        this.domStatus.classList.add('action-nonzero-exit')
-      } else {
-        this.domExitCode.innerText = res.logEntry.exitCode
-      }
-    } else {
-      this.domDatetimeFinished.innerText = 'Still running...'
-      this.domExitCode.innerText = 'Still running...'
-      this.domStatus.innerText = 'Still running...'
-    }
+    this.domStatus.update(res.logEntry)
 
     this.domIcon.innerHTML = res.logEntry.actionIcon
     this.domTitle.innerText = res.logEntry.actionTitle
 
-    this.domDatetimeStarted.innerText = res.logEntry.datetimeStarted
+    if (res.logEntry.executionFinished) {
+      this.updateDuration(res.logEntry.datetimeStarted, res.logEntry.datetimeFinished)
+    } else {
+      this.updateDuration(res.logEntry.datetimeStarted, 'Still running...')
+    }
 
     window.terminal.reset()
     window.terminal.write(res.logEntry.output, () => {

+ 4 - 11
webui.dev/js/marshaller.js

@@ -2,6 +2,7 @@ import './ActionButton.js' // To define action-button
 import { ExecutionDialog } from './ExecutionDialog.js'
 import { Terminal } from '@xterm/xterm'
 import { FitAddon } from '@xterm/addon-fit'
+import { ActionStatusDisplay } from './ActionStatusDisplay.js'
 
 /**
  * This is a weird function that just sets some globals.
@@ -510,22 +511,14 @@ export function marshalLogsJsonToHtml (json) {
     const tpl = document.getElementById('tplLogRow')
     const row = tpl.content.querySelector('tr').cloneNode(true)
 
-    let logTableExitCode = logEntry.exitCode
-
-    if (logEntry.exitCode === 0) {
-      logTableExitCode = 'OK'
-    }
-
-    if (logEntry.timedOut) {
-      logTableExitCode += ' (timed out)'
-    }
-
     row.querySelector('.timestamp').innerText = logEntry.datetimeStarted
     row.querySelector('.content').innerText = logEntry.actionTitle
     row.querySelector('.icon').innerHTML = logEntry.actionIcon
-    row.querySelector('.exit-code').innerText = logTableExitCode
     row.setAttribute('title', logEntry.actionTitle)
 
+    const exitCodeDisplay = new ActionStatusDisplay(row.querySelector('.exit-code'))
+    exitCodeDisplay.update(logEntry)
+
     row.querySelector('.content').onclick = () => {
       window.executionDialog.reset()
       window.executionDialog.show()

+ 12 - 16
webui.dev/style.css

@@ -17,10 +17,16 @@ dialog {
   padding: 0;
 }
 
+dialog[open] {
+  display: flex;
+  flex-direction: column;
+}
+
 dialog.big {
   max-width: 100vw;
   width: 100vw;
   height: 100vh;
+  max-height: 100dvh;
   border: none;
   margin: 0;
 }
@@ -226,11 +232,6 @@ details {
   flex-grow: 1;
 }
 
-details[open] {
-  margin-top: 1em;
-  display: block;
-}
-
 /* General Buttons */
 
 button,
@@ -471,24 +472,19 @@ div.display {
   font-size: small;
 }
 
-#execution-dialog-output {
-  display: flex;
-  flex-direction: row;
-  justify-content: start;
-}
-
 #execution-dialog-xterm {
-  overflow: auto;
-}
-
-.xterm .xterm-viewport {
-  overflow-y: auto !important;
+  flex-grow: 1;
 }
 
 .padded-content {
   padding: 1em;
 }
 
+.padded-content-sides {
+  padding-left: 1em;
+  padding-right: 1em;
+}
+
 .ta-left {
   text-align: left;
 }