-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(ui): use page components in test runs and profiling runs
- Loading branch information
Showing
7 changed files
with
417 additions
and
196 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
function formatTimestamp(/** @type number */ timestamp) { | ||
if (!timestamp) { | ||
return '--'; | ||
} | ||
|
||
const date = new Date(timestamp); | ||
const months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]; | ||
const hours = date.getHours(); | ||
const minutes = date.getMinutes(); | ||
return `${months[date.getMonth()]} ${date.getDate()}, ${hours % 12}:${String(minutes).padStart(2, '0')} ${hours / 12 > 1 ? 'PM' : 'AM'}`; | ||
} | ||
|
||
function formatDuration(/** @type string */ duration) { | ||
if (!duration) { | ||
return '--'; | ||
} | ||
|
||
const { hour, minute, second } = duration.split(':'); | ||
let formatted = [ | ||
{ value: Number(hour), unit: 'h' }, | ||
{ value: Number(minute), unit: 'm' }, | ||
{ value: Number(second), unit: 's' }, | ||
].map(({ value, unit }) => value ? `${value}${unit}` : '') | ||
.join(' '); | ||
|
||
return formatted.trim() || '< 1s'; | ||
} | ||
|
||
export { formatTimestamp, formatDuration }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
160 changes: 160 additions & 0 deletions
160
testgen/ui/components/frontend/js/pages/profiling_runs.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
/** | ||
* @typedef Properties | ||
* @type {object} | ||
* @property {array} items | ||
*/ | ||
import van from '../van.min.js'; | ||
import { Tooltip } from '../van-tooltip.js'; | ||
import { SummaryBar } from '../components/summary_bar.js'; | ||
import { Link } from '../components/link.js'; | ||
import { Button } from '../components/button.js'; | ||
import { Streamlit } from '../streamlit.js'; | ||
import { wrapProps } from '../utils.js'; | ||
import { formatTimestamp, formatDuration } from '../display_utils.js'; | ||
|
||
const { div, span, i } = van.tags; | ||
|
||
const ProfilingRuns = (/** @type Properties */ props) => { | ||
window.testgen.isPage = true; | ||
|
||
const profilingRunItems = van.derive(() => { | ||
let items = []; | ||
try { | ||
items = JSON.parse(props.items?.val); | ||
} catch { } | ||
Streamlit.setFrameHeight(44 + 84.5 * items.length); | ||
return items; | ||
}); | ||
const columns = ['20%', '20%', '20%', '40%']; | ||
|
||
return div( | ||
() => div( | ||
{ class: 'table' }, | ||
div( | ||
{ class: 'table-header flex-row' }, | ||
span( | ||
{ style: `flex: ${columns[0]}` }, | ||
'Start Time | Table Group', | ||
), | ||
span( | ||
{ style: `flex: ${columns[1]}` }, | ||
'Status | Duration', | ||
), | ||
span( | ||
{ style: `flex: ${columns[2]}` }, | ||
'Schema', | ||
), | ||
span( | ||
{ style: `flex: ${columns[3]}` }, | ||
'Hygiene Issues', | ||
), | ||
), | ||
profilingRunItems.val.map(item => ProfilingRunItem(item, columns)), | ||
), | ||
); | ||
} | ||
|
||
const ProfilingRunItem = (item, /** @type string[] */ columns) => { | ||
return div( | ||
{ class: 'table-row flex-row' }, | ||
div( | ||
{ style: `flex: ${columns[0]}` }, | ||
div(formatTimestamp(item.start_time)), | ||
div( | ||
{ class: 'text-caption mt-1' }, | ||
item.table_groups_name, | ||
), | ||
), | ||
div( | ||
{ class: 'flex-row', style: `flex: ${columns[1]}` }, | ||
div( | ||
ProfilingRunStatus(item), | ||
div( | ||
{ class: 'text-caption mt-1' }, | ||
formatDuration(item.duration), | ||
), | ||
), | ||
item.status === 'Running' && item.process_id ? Button(wrapProps({ | ||
type: 'stroked', | ||
label: 'Cancel Run', | ||
style: 'width: auto; height: 32px; color: var(--purple); margin-left: 16px;', | ||
onclick: () => Streamlit.sendData({ | ||
event: 'RunCanceled', | ||
profiling_run: item, | ||
_id: Math.random(), // Forces on_change component handler to be triggered on every click | ||
}), | ||
})) : null, | ||
), | ||
div( | ||
{ style: `flex: ${columns[2]}` }, | ||
div(item.schema_name), | ||
div( | ||
{ | ||
class: 'text-caption mt-1 mb-1', | ||
style: item.status === 'Complete' && !item.column_ct ? 'color: var(--red);' : '', | ||
}, | ||
`${item.table_ct || 0} tables, ${item.column_ct || 0} columns`, | ||
), | ||
item.column_ct ? Link(wrapProps({ | ||
label: 'View results', | ||
href: 'profiling-runs:results', | ||
params: { 'run_id': item.profiling_run_id }, | ||
underline: true, | ||
right_icon: 'chevron_right', | ||
})) : null, | ||
), | ||
div( | ||
{ style: `flex: ${columns[3]}` }, | ||
item.anomaly_ct ? SummaryBar(wrapProps({ | ||
items: [ | ||
{ label: 'Definite', value: item.anomalies_definite_ct, color: 'red' }, | ||
{ label: 'Likely', value: item.anomalies_likely_ct, color: 'orange' }, | ||
{ label: 'Possible', value: item.anomalies_possible_ct, color: 'yellow' }, | ||
{ label: 'Dismissed', value: item.anomalies_dismissed_ct, color: 'grey' }, | ||
], | ||
height: 10, | ||
width: 300, | ||
})) : '--', | ||
item.anomaly_ct ? Link(wrapProps({ | ||
label: `View ${item.anomaly_ct} issues`, | ||
href: 'profiling-runs:hygiene', | ||
params: { 'run_id': item.profiling_run_id }, | ||
underline: true, | ||
right_icon: 'chevron_right', | ||
style: 'margin-top: 8px;', | ||
})) : null, | ||
), | ||
); | ||
} | ||
|
||
function ProfilingRunStatus(/** @type object */ item) { | ||
const attributeMap = { | ||
Running: { label: 'Running', color: 'blue' }, | ||
Complete: { label: 'Completed', color: '' }, | ||
Error: { label: 'Error', color: 'red' }, | ||
Cancelled: { label: 'Canceled', color: 'purple' }, | ||
}; | ||
const attributes = attributeMap[item.status] || { label: 'Unknown', color: 'grey' }; | ||
return span( | ||
{ | ||
class: 'flex-row', | ||
style: `color: var(--${attributes.color});`, | ||
}, | ||
attributes.label, | ||
() => { | ||
const tooltipError = van.state(false); | ||
return item.status === 'Error' && item.log_message ? i( | ||
{ | ||
class: 'material-symbols-rounded text-secondary ml-1 profiling-runs--info', | ||
style: 'position: relative; font-size: 16px;', | ||
onmouseenter: () => tooltipError.val = true, | ||
onmouseleave: () => tooltipError.val = false, | ||
}, | ||
'info', | ||
Tooltip({ text: item.log_message, show: tooltipError }), | ||
) : null; | ||
}, | ||
); | ||
} | ||
|
||
export { ProfilingRuns }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
/** | ||
* @typedef Properties | ||
* @type {object} | ||
* @property {array} items | ||
*/ | ||
import van from '../van.min.js'; | ||
import { Tooltip } from '../van-tooltip.js'; | ||
import { SummaryBar } from '../components/summary_bar.js'; | ||
import { Link } from '../components/link.js'; | ||
import { Button } from '../components/button.js'; | ||
import { Streamlit } from '../streamlit.js'; | ||
import { wrapProps } from '../utils.js'; | ||
import { formatTimestamp, formatDuration } from '../display_utils.js'; | ||
|
||
const { div, span, i } = van.tags; | ||
|
||
const TestRuns = (/** @type Properties */ props) => { | ||
window.testgen.isPage = true; | ||
|
||
const testRunItems = van.derive(() => { | ||
let items = []; | ||
try { | ||
items = JSON.parse(props.items?.val); | ||
} catch { } | ||
Streamlit.setFrameHeight(44 + 60.5 * items.length); | ||
return items; | ||
}); | ||
const columns = ['30%', '20%', '50%']; | ||
|
||
return div( | ||
() => div( | ||
{ class: 'table' }, | ||
div( | ||
{ class: 'table-header flex-row' }, | ||
span( | ||
{ style: `flex: ${columns[0]}` }, | ||
'Start Time | Table Group | Test Suite', | ||
), | ||
span( | ||
{ style: `flex: ${columns[1]}` }, | ||
'Status | Duration', | ||
), | ||
span( | ||
{ style: `flex: ${columns[2]}` }, | ||
'Results Summary', | ||
), | ||
), | ||
testRunItems.val.map(item => TestRunItem(item, columns)), | ||
), | ||
); | ||
} | ||
|
||
const TestRunItem = (item, /** @type string[] */ columns) => { | ||
return div( | ||
{ class: 'table-row flex-row' }, | ||
div( | ||
{ style: `flex: ${columns[0]}` }, | ||
Link(wrapProps({ | ||
label: formatTimestamp(item.test_starttime), | ||
href: 'test-runs:results', | ||
params: { 'run_id': item.test_run_id }, | ||
underline: true, | ||
})), | ||
div( | ||
{ class: 'text-caption mt-1' }, | ||
`${item.table_groups_name} > ${item.test_suite}`, | ||
), | ||
), | ||
div( | ||
{ class: 'flex-row', style: `flex: ${columns[1]}` }, | ||
div( | ||
TestRunStatus(item), | ||
div( | ||
{ class: 'text-caption mt-1' }, | ||
formatDuration(item.duration), | ||
), | ||
), | ||
item.status === 'Running' && item.process_id ? Button(wrapProps({ | ||
type: 'stroked', | ||
label: 'Cancel Run', | ||
style: 'width: auto; height: 32px; color: var(--purple); margin-left: 16px;', | ||
onclick: () => Streamlit.sendData({ | ||
event: 'RunCanceled', | ||
test_run: item, | ||
_id: Math.random(), // Forces on_change component handler to be triggered on every click | ||
}), | ||
})) : null, | ||
), | ||
div( | ||
{ style: `flex: ${columns[2]}` }, | ||
item.test_ct ? SummaryBar(wrapProps({ | ||
items: [ | ||
{ label: 'Passed', value: item.passed_ct, color: 'green' }, | ||
{ label: 'Warning', value: item.warning_ct, color: 'yellow' }, | ||
{ label: 'Failed', value: item.failed_ct, color: 'red' }, | ||
{ label: 'Error', value: item.error_ct, color: 'brown' }, | ||
{ label: 'Dismissed', value: item.dismissed_ct, color: 'grey' }, | ||
], | ||
height: 10, | ||
width: 300, | ||
})) : '--', | ||
), | ||
); | ||
} | ||
|
||
function TestRunStatus(/** @type object */ item) { | ||
const attributeMap = { | ||
Running: { label: 'Running', color: 'blue' }, | ||
Complete: { label: 'Completed', color: '' }, | ||
Error: { label: 'Error', color: 'red' }, | ||
Cancelled: { label: 'Canceled', color: 'purple' }, | ||
}; | ||
const attributes = attributeMap[item.status] || { label: 'Unknown', color: 'grey' }; | ||
return span( | ||
{ | ||
class: 'flex-row', | ||
style: `color: var(--${attributes.color});`, | ||
}, | ||
attributes.label, | ||
() => { | ||
const tooltipError = van.state(false); | ||
return item.status === 'Error' && item.log_message ? i( | ||
{ | ||
class: 'material-symbols-rounded text-secondary ml-1', | ||
style: 'position: relative; font-size: 16px;', | ||
onmouseenter: () => tooltipError.val = true, | ||
onmouseleave: () => tooltipError.val = false, | ||
}, | ||
'info', | ||
Tooltip({ text: item.log_message, show: tooltipError }), | ||
) : null; | ||
}, | ||
); | ||
} | ||
|
||
export { TestRuns }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// Code modified from vanjs-ui | ||
// https://www.npmjs.com/package/vanjs-ui | ||
// https://cdn.jsdelivr.net/npm/[email protected]/dist/van-ui.nomodule.js | ||
|
||
import van from './van.min.js'; | ||
const { div, span } = van.tags; | ||
|
||
const toStyleStr = (style) => Object.entries(style).map(([k, v]) => `${k}: ${v};`).join(""); | ||
|
||
const Tooltip = ({ text, show, backgroundColor = '#333D', fontColor = 'white', fadeInSec = 0.3, tooltipClass = '', tooltipStyleOverrides = {}, triangleClass = '', triangleStyleOverrides = {}, }) => { | ||
const tooltipStylesStr = toStyleStr({ | ||
width: 'max-content', | ||
'min-width': '100px', | ||
'max-width': '400px', | ||
visibility: 'hidden', | ||
'background-color': backgroundColor, | ||
color: fontColor, | ||
'text-align': 'center', | ||
padding: '5px', | ||
'border-radius': '5px', | ||
position: 'absolute', | ||
'z-index': 1, | ||
bottom: '125%', | ||
left: '50%', | ||
transform: 'translateX(-50%)', | ||
opacity: 0, | ||
transition: `opacity ${fadeInSec}s`, | ||
'font-size': '14px', | ||
'font-family': `'Roboto', 'Helvetica Neue', sans-serif`, | ||
'text-wrap': 'wrap', | ||
...tooltipStyleOverrides, | ||
}); | ||
const triangleStylesStr = toStyleStr({ | ||
width: 0, | ||
height: 0, | ||
'margin-left': '-5px', | ||
'border-left': '5px solid transparent', | ||
'border-right': '5px solid transparent', | ||
'border-top': '5px solid #333', | ||
position: 'absolute', | ||
bottom: '-5px', | ||
left: '50%', | ||
...triangleStyleOverrides, | ||
}); | ||
const dom = span({ class: tooltipClass, style: tooltipStylesStr }, text, div({ class: triangleClass, style: triangleStylesStr })); | ||
van.derive(() => show.val ? | ||
(dom.style.opacity = '1', dom.style.visibility = 'visible') : | ||
(dom.style.opacity = '0', dom.style.visibility = 'hidden')); | ||
return dom; | ||
}; | ||
|
||
export { Tooltip }; |
Oops, something went wrong.