Merge branch '7936-add-discrete-event-visualization' of github.com:nasa/openmct into 7936-add-discrete-event-visualization

This commit is contained in:
Scott Bell 2024-12-18 09:19:57 +01:00
commit 68fc3172a0
12 changed files with 122 additions and 42 deletions

View File

@ -74,7 +74,7 @@ test.describe('Event Timeline View', () => {
// count the event lines // count the event lines
const eventWrappersContainer = page.locator('.c-events-tsv__container'); const eventWrappersContainer = page.locator('.c-events-tsv__container');
const eventWrappers = eventWrappersContainer.locator('.c-events-tsv__event-wrapper'); const eventWrappers = eventWrappersContainer.locator('.c-events-tsv__event-line');
const expectedEventWrappersCount = 25; const expectedEventWrappersCount = 25;
await expect(eventWrappers).toHaveCount(expectedEventWrappersCount); await expect(eventWrappers).toHaveCount(expectedEventWrappersCount);
@ -104,7 +104,7 @@ test.describe('Event Timeline View', () => {
// count the extended lines // count the extended lines
const overlayLinesContainer = page.locator('.c-timeline__overlay-lines'); const overlayLinesContainer = page.locator('.c-timeline__overlay-lines');
const extendedLines = overlayLinesContainer.locator('.c-timeline__extended-line'); const extendedLines = overlayLinesContainer.locator('.c-timeline__event-line--extended');
const expectedCount = 25; const expectedCount = 25;
await expect(extendedLines).toHaveCount(expectedCount); await expect(extendedLines).toHaveCount(expectedCount);
}); });

View File

@ -25,7 +25,7 @@ export const SEVERITY_CSS = {
WARNING: 'is-event--yellow', WARNING: 'is-event--yellow',
DISTRESS: 'is-event--red', DISTRESS: 'is-event--red',
CRITICAL: 'is-event--red', CRITICAL: 'is-event--red',
SEVERE: 'is-event--red' SEVERE: 'is-event--purple'
}; };
const NOMINAL_SEVERITY = { const NOMINAL_SEVERITY = {

View File

@ -27,6 +27,8 @@
import { SEVERITY_CSS } from './EventLimitProvider.js'; import { SEVERITY_CSS } from './EventLimitProvider.js';
import messages from './transcript.json'; import messages from './transcript.json';
const DUR_MIN = 1000;
const DUR_MAX = 10000;
class EventTelemetryProvider { class EventTelemetryProvider {
constructor() { constructor() {
this.defaultSize = 25; this.defaultSize = 25;
@ -34,12 +36,17 @@ class EventTelemetryProvider {
generateData(firstObservedTime, count, startTime, duration, name) { generateData(firstObservedTime, count, startTime, duration, name) {
const millisecondsSinceStart = startTime - firstObservedTime; const millisecondsSinceStart = startTime - firstObservedTime;
const utc = startTime + count * duration; const randStartDelay = Math.max(DUR_MIN, Math.random() * DUR_MAX);
const utc = startTime + randStartDelay + count * duration;
const ind = count % messages.length; const ind = count % messages.length;
const message = messages[ind] + ' - [' + millisecondsSinceStart + ']'; const message = messages[ind] + ' - [' + millisecondsSinceStart + ']';
// pick a random severity level + 1 for an undefined level so we can do nominal // pick a random severity level + 1 for an undefined level so we can do nominal
const severity = const severity =
Object.keys(SEVERITY_CSS)[Math.floor(Math.random() * Object.keys(SEVERITY_CSS).length + 1)]; Math.random() > 0.4
? Object.keys(SEVERITY_CSS)[
Math.floor(Math.random() * Object.keys(SEVERITY_CSS).length + 1)
]
: undefined;
return { return {
name, name,
@ -58,7 +65,7 @@ class EventTelemetryProvider {
} }
subscribe(domainObject, callback) { subscribe(domainObject, callback) {
const duration = domainObject.telemetry.duration * 1000; const duration = domainObject.telemetry.duration * DUR_MIN;
const firstObservedTime = Date.now(); const firstObservedTime = Date.now();
let count = 0; let count = 0;
@ -83,7 +90,7 @@ class EventTelemetryProvider {
request(domainObject, options) { request(domainObject, options) {
let start = options.start; let start = options.start;
const end = Math.min(Date.now(), options.end); // no future values const end = Math.min(Date.now(), options.end); // no future values
const duration = domainObject.telemetry.duration * 1000; const duration = domainObject.telemetry.duration * DUR_MIN;
const size = options.size ? options.size : this.defaultSize; const size = options.size ? options.size : this.defaultSize;
const data = []; const data = [];
const firstObservedTime = options.start; const firstObservedTime = options.start;

View File

@ -38,7 +38,7 @@ import eventData from '../mixins/eventData.js';
const PADDING = 1; const PADDING = 1;
const CONTAINER_CLASS = 'c-events-tsv__container'; const CONTAINER_CLASS = 'c-events-tsv__container';
const NO_ITEMS_CLASS = 'c-events-tsv__no-items'; const NO_ITEMS_CLASS = 'c-events-tsv__no-items';
const EVENT_WRAPPER_CLASS = 'c-events-tsv__event-wrapper'; const EVENT_WRAPPER_CLASS = 'c-events-tsv__event-line';
const ID_PREFIX = 'wrapper-'; const ID_PREFIX = 'wrapper-';
const AXES_PADDING = 20; const AXES_PADDING = 20;
@ -403,6 +403,10 @@ export default {
} }
this.openmct.selection.select(selection, true); this.openmct.selection.select(selection, true);
}, },
getLimitClass(event) {
const limitEvaluation = this.limitEvaluator.evaluate(event, this.valueMetadata);
return limitEvaluation?.cssClass;
},
createEventWrapper(event) { createEventWrapper(event) {
const id = `${ID_PREFIX}${event.time}`; const id = `${ID_PREFIX}${event.time}`;
const eventWrapper = document.createElement('div'); const eventWrapper = document.createElement('div');
@ -413,7 +417,7 @@ export default {
const textToShow = event[this.titleKey]; const textToShow = event[this.titleKey];
eventWrapper.ariaLabel = textToShow; eventWrapper.ariaLabel = textToShow;
eventWrapper.addEventListener('mouseover', () => { eventWrapper.addEventListener('mouseover', () => {
this.showToolTip(textToShow, eventWrapper); this.showToolTip(textToShow, eventWrapper, event);
this.extendedLinesBus.updateHoverExtendEventLine(this.keyString, event.time); this.extendedLinesBus.updateHoverExtendEventLine(this.keyString, event.time);
}); });
eventWrapper.addEventListener('mouseleave', () => { eventWrapper.addEventListener('mouseleave', () => {
@ -421,8 +425,7 @@ export default {
this.extendedLinesBus.updateHoverExtendEventLine(this.keyString, null); this.extendedLinesBus.updateHoverExtendEventLine(this.keyString, null);
}); });
} }
const limitEvaluation = this.limitEvaluator.evaluate(event, this.valueMetadata); const limitClass = this.getLimitClass(event);
const limitClass = limitEvaluation?.cssClass;
if (limitClass) { if (limitClass) {
eventWrapper.classList.add(limitClass); eventWrapper.classList.add(limitClass);
event.limitClass = limitClass; event.limitClass = limitClass;
@ -451,12 +454,22 @@ export default {
}); });
} }
}, },
showToolTip(textToShow, referenceElement) { showToolTip(textToShow, referenceElement, event) {
const aClasses = ['c-events-tooltip'];
const limitClass = this.getLimitClass(event);
if (limitClass) {
aClasses.push(limitClass);
}
const showToLeft = false; // Temp, stubbed in
if (showToLeft) {
aClasses.push('--left');
}
this.tooltip = this.openmct.tooltips.tooltip({ this.tooltip = this.openmct.tooltips.tooltip({
toolTipText: textToShow, toolTipText: textToShow,
toolTipLocation: this.openmct.tooltips.TOOLTIP_LOCATIONS.RIGHT, toolTipLocation: this.openmct.tooltips.TOOLTIP_LOCATIONS.RIGHT,
parentElement: referenceElement, parentElement: referenceElement,
cssClasses: ['c-timeline-event-tooltip'] cssClasses: [aClasses.join(' ')]
}); });
} }
} }

View File

@ -1,3 +1,16 @@
@mixin styleEventLine($colorConst) {
background-color: $colorConst !important;
&:hover,
&[s-selected] {
box-shadow: rgba($colorConst, 0.5) 0 0 0px 4px;
transition: none;
z-index: 2;
}
}
@mixin styleEventLineExtended($colorConst) {
background-color: $colorConst !important;
}
.c-events-tsv { .c-events-tsv {
$m: $interiorMargin; $m: $interiorMargin;
overflow: hidden; overflow: hidden;
@ -11,21 +24,37 @@
top: $m; right: 0; bottom: $m; left: 0; top: $m; right: 0; bottom: $m; left: 0;
} }
&__event-wrapper { &__event-line {
// Wraps an individual event line // Wraps an individual event line
// Also holds the hover flyout element // Also holds the hover flyout element
$c: $colorEventLine; $c: $colorEventLine;
$lineW: $eventLineW; $lineW: $eventLineW;
$hitAreaW: 7px; $hitAreaW: 7px;
$m: $interiorMarginSm; $m: $interiorMarginSm;
background-color: rgba($c, 0.6);
cursor: pointer; cursor: pointer;
position: absolute; position: absolute;
display: flex; display: flex;
top: $m; bottom: $m; top: $m; bottom: $m;
transition: box-shadow 250ms ease-out;
width: $lineW; width: $lineW;
z-index: 1; z-index: 1;
@include styleEventLine($colorEventLine);
&.is-event {
&--purple {
@include styleEventLine($colorEventPurpleLine);
}
&--red {
@include styleEventLine($colorEventRedLine);
}
&--orange {
@include styleEventLine($colorEventOrangeLine);
}
&--yellow {
@include styleEventLine($colorEventYellowLine);
}
}
&:before { &:before {
// Extend hit area // Extend hit area
content: ''; content: '';
@ -36,17 +65,6 @@
width: $hitAreaW; width: $hitAreaW;
transform: translateX(($hitAreaW - $lineW) * -0.5); transform: translateX(($hitAreaW - $lineW) * -0.5);
} }
&.is-selected, // TODO: temp, remove this once we set selection correctly
&[s-selected],
&:hover {
z-index: 2;
background-color: $c;
&:before {
background-color: rgba($c, 0.4);
}
}
} }
&__no-items { &__no-items {
@ -64,3 +82,38 @@
top: 0; top: 0;
z-index: 2; z-index: 2;
} }
// Extended event lines
.c-timeline__event-line--extended {
@include abs();
transition: opacity 250ms ease-out;
width: $eventLineW;
opacity: 0.4;
&.--hilite {
opacity: 0.8;
transition: none;
}
@include styleEventLineExtended($colorEventLine);
&.is-event {
&--purple {
@include styleEventLineExtended($colorEventPurpleLine);
}
&--red {
@include styleEventLineExtended($colorEventRedLine);
}
&--orange {
@include styleEventLineExtended($colorEventOrangeLine);
}
&--yellow {
@include styleEventLineExtended($colorEventYellowLine);
}
}
}
.c-events-tooltip {
// Default to right of event line
border-radius: 0 !important;
//transform: translate(0, $interiorMargin);
}

View File

@ -24,18 +24,17 @@
<div <div
v-for="(lines, key) in extendedLinesPerKey" v-for="(lines, key) in extendedLinesPerKey"
:key="key" :key="key"
class="c-timeline__extended-line-container" class="c-timeline__event-line--extended-container"
> >
<div <div
v-for="(line, index) in lines" v-for="(line, index) in lines"
:id="line.id" :id="line.id"
:key="index" :key="index"
class="c-timeline__extended-line" class="c-timeline__event-line--extended"
:class="[ :class="[
line.limitClass, line.limitClass,
{ {
'c-timeline__extended-line-hovered': '--hilite': hoveredLineId && hoveredKeyString === key && line.id === hoveredLineId
hoveredLineId && hoveredKeyString === key && line.id === hoveredLineId
} }
]" ]"
:style="{ left: `${line.x + leftOffset}px`, height: `${height}px` }" :style="{ left: `${line.x + leftOffset}px`, height: `${height}px` }"

View File

@ -51,10 +51,4 @@
pointer-events: none; // Allows clicks to pass through pointer-events: none; // Allows clicks to pass through
z-index: 10; // Ensure it sits atop swimlanes z-index: 10; // Ensure it sits atop swimlanes
} }
&__extended-line {
position: absolute;
width: $eventLineW;
background-color: $colorEventLineExtended;
}
} }

View File

@ -443,6 +443,10 @@ $colorEventPurpleBg: #31204a;
$colorEventRedBg: #3c1616; $colorEventRedBg: #3c1616;
$colorEventOrangeBg: #3e2a13; $colorEventOrangeBg: #3e2a13;
$colorEventYellowBg: #3e3316; $colorEventYellowBg: #3e3316;
$colorEventPurpleLine: #9e36ff;
$colorEventRedLine: #ff2525;
$colorEventOrangeLine: #ff8800;
$colorEventYellowLine: #fdce22;
// Bubble colors // Bubble colors
$colorInfoBubbleBg: #dddddd; $colorInfoBubbleBg: #dddddd;

View File

@ -412,6 +412,10 @@ $colorEventPurpleBg: #31204a;
$colorEventRedBg: #3c1616; $colorEventRedBg: #3c1616;
$colorEventOrangeBg: #3e2a13; $colorEventOrangeBg: #3e2a13;
$colorEventYellowBg: #3e3316; $colorEventYellowBg: #3e3316;
$colorEventPurpleLine: #9e36ff;
$colorEventRedLine: #ff2525;
$colorEventOrangeLine: #ff8800;
$colorEventYellowLine: #fdce22;
// Bubble colors // Bubble colors
$colorInfoBubbleBg: #dddddd; $colorInfoBubbleBg: #dddddd;

View File

@ -428,6 +428,10 @@ $colorEventPurpleBg: #31204a;
$colorEventRedBg: #3c1616; $colorEventRedBg: #3c1616;
$colorEventOrangeBg: #3e2a13; $colorEventOrangeBg: #3e2a13;
$colorEventYellowBg: #3e3316; $colorEventYellowBg: #3e3316;
$colorEventPurpleLine: #9e36ff;
$colorEventRedLine: #ff2525;
$colorEventOrangeLine: #ff8800;
$colorEventYellowLine: #fdce22;
// Bubble colors // Bubble colors
$colorInfoBubbleBg: #dddddd; $colorInfoBubbleBg: #dddddd;

View File

@ -403,14 +403,18 @@ $colorLimitCyanFg: #d3faff;
$colorLimitCyanIc: #1795c0; $colorLimitCyanIc: #1795c0;
// Events // Events
$colorEventPurpleFg: #6433ff; $colorEventPurpleFg: #6f07ed;
$colorEventRedFg: #aa0000; $colorEventRedFg: #aa0000;
$colorEventOrangeFg: #b84900; $colorEventOrangeFg: #b84900;
$colorEventYellowFg: #867109; $colorEventYellowFg: #a98c04;
$colorEventPurpleBg: #ebe7fb; $colorEventPurpleBg: #ebe7fb;
$colorEventRedBg: #fcefef; $colorEventRedBg: #fcefef;
$colorEventOrangeBg: #ffece3; $colorEventOrangeBg: #ffece3;
$colorEventYellowBg: #fdf8eb; $colorEventYellowBg: #fdf8eb;
$colorEventPurpleLine: $colorEventPurpleFg;
$colorEventRedLine: $colorEventRedFg;
$colorEventOrangeLine: $colorEventOrangeFg;
$colorEventYellowLine: $colorEventYellowFg;
// Bubble colors // Bubble colors
$colorInfoBubbleBg: $colorMenuBg; $colorInfoBubbleBg: $colorMenuBg;

View File

@ -252,8 +252,6 @@ tr {
background-color: $colorEventYellowBg !important; background-color: $colorEventYellowBg !important;
color: $colorEventYellowFg !important; color: $colorEventYellowFg !important;
} }
&--no-style {
background-color: $colorBodyBg !important;
color: $colorBodyFg !important;
}
} }