From 2830e6bd35084e42425f46d8025b016b9e4608f5 Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Thu, 5 Dec 2024 11:18:38 +0100 Subject: [PATCH 01/16] first pass on breaking up events --- example/make-example-events.js | 102 ++++++++++++++++++ package.json | 3 +- src/const.js | 3 +- src/openmct-yamcs.js | 10 +- src/providers/events.js | 35 ++++-- .../historical-telemetry-provider.js | 6 +- src/providers/messages.js | 5 +- src/providers/object-provider.js | 29 ++++- 8 files changed, 170 insertions(+), 23 deletions(-) create mode 100644 example/make-example-events.js diff --git a/example/make-example-events.js b/example/make-example-events.js new file mode 100644 index 00000000..1fc32816 --- /dev/null +++ b/example/make-example-events.js @@ -0,0 +1,102 @@ +const INSTANCE = "myproject"; +const URL = `http://localhost:8090/api/archive/${INSTANCE}/events`; + +const events = [ + // Pressure Events + { + type: "PRESSURE_ALERT", + message: "Pressure threshold exceeded", + severity: "CRITICAL", + source: "PressureModule", + sequenceNumber: 1, + extra: { + pressure: "150 PSI", + location: "Hydraulic System" + } + }, + { + type: "PRESSURE_WARNING", + message: "Pressure nearing critical level", + severity: "WARNING", + source: "PressureMonitor", + sequenceNumber: 2, + extra: { + pressure: "140 PSI", + location: "Hydraulic System" + } + }, + { + type: "PRESSURE_INFO", + message: "Pressure system check completed", + severity: "INFO", + source: "MaintenanceModule", + sequenceNumber: 3, + extra: { + checkType: "Routine Inspection", + duration: "10m" + } + }, + // Temperature Events + { + type: "TEMPERATURE_ALERT", + message: "Temperature threshold exceeded", + severity: "CRITICAL", + source: "TemperatureModule", + sequenceNumber: 4, + extra: { + temperature: "100°C", + location: "Engine Room" + } + }, + { + type: "TEMPERATURE_WARNING", + message: "Temperature nearing critical level", + severity: "WARNING", + source: "TemperatureMonitor", + sequenceNumber: 5, + extra: { + temperature: "95°C", + location: "Engine Room" + } + }, + { + type: "TEMPERATURE_INFO", + message: "Temperature system check completed", + severity: "INFO", + source: "MaintenanceModule", + sequenceNumber: 6, + extra: { + checkType: "Routine Inspection", + duration: "15m" + } + } +]; + +async function postEvent(event, delaySeconds) { + const eventTime = new Date(Date.now() + delaySeconds * 1000).toISOString(); + event.time = eventTime; + + try { + const response = await fetch(URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(event) + }); + + if (response.ok) { + console.log(`Event posted successfully: ${event.type}`); + } else { + console.error(`Failed to post event: ${event.type}. HTTP Status: ${response.status}`); + } + } catch (error) { + console.error(`Error posting event: ${event.type}.`, error); + } +} + +async function main() { + for (let i = 0; i < events.length; i++) { + await postEvent(events[i], i * 5); + } +} + +main(); \ No newline at end of file diff --git a/package.json b/package.json index 8e16c70c..f35c60ce 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "test:e2e:quickstart": "npm test --workspace tests/e2e/opensource -- --config=../playwright-quickstart.config.js --project=chromium tests/e2e/yamcs/", "test:e2e:quickstart:local": "npm test --workspace tests/e2e/opensource -- --config=../playwright-quickstart.config.js --project=local-chrome tests/e2e/yamcs/", "test:e2e:watch": "npm test --workspace tests/e2e/opensource -- --ui --config=../playwright-quickstart.config.js", - "wait-for-yamcs": "wait-on http-get://localhost:8090/ -v" + "wait-for-yamcs": "wait-on http-get://localhost:8090/ -v", + "make-example-events": "node ./example/make-example-events.js" }, "keywords": [ "openmct", diff --git a/src/const.js b/src/const.js index fb878326..7f1f6516 100644 --- a/src/const.js +++ b/src/const.js @@ -22,7 +22,8 @@ export const OBJECT_TYPES = { COMMANDS_OBJECT_TYPE: 'yamcs.commands', - EVENTS_OBJECT_TYPE: 'yamcs.events', + EVENTS_ROOT_OBJECT_TYPE: 'yamcs.events', + EVENT_SPECIFIC_OBJECT_TYPE: 'yamcs.event.specific', TELEMETRY_OBJECT_TYPE: 'yamcs.telemetry', IMAGE_OBJECT_TYPE: 'yamcs.image', STRING_OBJECT_TYPE: 'yamcs.string', diff --git a/src/openmct-yamcs.js b/src/openmct-yamcs.js index 04bb2bc8..7bff5a3a 100644 --- a/src/openmct-yamcs.js +++ b/src/openmct-yamcs.js @@ -186,9 +186,15 @@ export default function install( cssClass: 'icon-telemetry' }); - openmct.types.addType(OBJECT_TYPES.EVENTS_OBJECT_TYPE, { + openmct.types.addType(OBJECT_TYPES.ROOT_EVENTS_OBJECT_TYPE, { name: "Events", - description: "To view events", + description: "To view all events", + cssClass: "icon-generator-events" + }); + + openmct.types.addType(OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE, { + name: "Event", + description: "To view events from a specific source", cssClass: "icon-generator-events" }); diff --git a/src/providers/events.js b/src/providers/events.js index f9482bf8..aa1c7e09 100644 --- a/src/providers/events.js +++ b/src/providers/events.js @@ -21,21 +21,28 @@ *****************************************************************************/ import { OBJECT_TYPES, METADATA_TIME_KEY, SEVERITY_LEVELS } from "../const.js"; -export function createEventsObject(openmct, parentKey, namespace) { +export function createRootEventsObject(openmct, parentKey, namespace) { + const rootEventIdentifier = { + key: OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE, + namespace + }; + const rootEventObject = createEventObject(openmct, parentKey, namespace, rootEventIdentifier); + rootEventObject.composition = []; + + return rootEventObject; +} + +export function createEventObject(openmct, parentKey, namespace, identifier, name = 'Events', type = OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE) { const location = openmct.objects.makeKeyString({ key: parentKey, namespace }); - const identifier = { - key: OBJECT_TYPES.EVENTS_OBJECT_TYPE, - namespace - }; - const eventsObject = { + const baseEventObject = { identifier, location, - name: 'Events', - type: OBJECT_TYPES.EVENTS_OBJECT_TYPE, + name, + type, telemetry: { values: [ { @@ -96,7 +103,15 @@ export function createEventsObject(openmct, parentKey, namespace) { } }; - return eventsObject; + return baseEventObject; +} + +export async function getEventSources(openmct, url, instance, rootEventObject, namespace) { + const eventSourceURL = `${url}/api/archive/${instance}/events/sources`; + const eventSourcesReply = await fetch(eventSourceURL); + const eventSourcesJson = await eventSourcesReply.json(); + + return eventSourcesJson.sources; } export function eventShouldBeFiltered(event, options) { @@ -128,7 +143,7 @@ export function eventToTelemetryDatum(event) { } = event; return { - id: OBJECT_TYPES.EVENTS_OBJECT_TYPE, + id: OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE, severity, generationTime, receptionTime, diff --git a/src/providers/historical-telemetry-provider.js b/src/providers/historical-telemetry-provider.js index c08ed6cb..aaae670f 100644 --- a/src/providers/historical-telemetry-provider.js +++ b/src/providers/historical-telemetry-provider.js @@ -196,7 +196,7 @@ export default class YamcsHistoricalTelemetryProvider { } getLinkParamsSpecificToId(id) { - if (id === OBJECT_TYPES.EVENTS_OBJECT_TYPE) { + if (id === OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE || id === OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE) { return 'events'; } @@ -208,7 +208,7 @@ export default class YamcsHistoricalTelemetryProvider { } getResponseKeyById(id) { - if (id === OBJECT_TYPES.EVENTS_OBJECT_TYPE) { + if (id === OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE || id === OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE) { return 'event'; } @@ -224,7 +224,7 @@ export default class YamcsHistoricalTelemetryProvider { return []; } - if (id === OBJECT_TYPES.EVENTS_OBJECT_TYPE) { + if (id === OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE || id === OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE) { return results.map(event => eventToTelemetryDatum(event)); } diff --git a/src/providers/messages.js b/src/providers/messages.js index a0ef8cd8..3579e288 100644 --- a/src/providers/messages.js +++ b/src/providers/messages.js @@ -2,7 +2,8 @@ import {OBJECT_TYPES, DATA_TYPES, MDB_TYPE} from '../const.js'; const typeMap = { [OBJECT_TYPES.COMMANDS_OBJECT_TYPE]: DATA_TYPES.DATA_TYPE_COMMANDS, - [OBJECT_TYPES.EVENTS_OBJECT_TYPE]: DATA_TYPES.DATA_TYPE_EVENTS, + [OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE]: DATA_TYPES.DATA_TYPE_EVENTS, + [OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE]: DATA_TYPES.DATA_TYPE_EVENTS, [OBJECT_TYPES.TELEMETRY_OBJECT_TYPE]: DATA_TYPES.DATA_TYPE_TELEMETRY, [OBJECT_TYPES.STRING_OBJECT_TYPE]: DATA_TYPES.DATA_TYPE_TELEMETRY, [OBJECT_TYPES.IMAGE_OBJECT_TYPE]: DATA_TYPES.DATA_TYPE_TELEMETRY, @@ -76,7 +77,7 @@ function buildSubscribeMessages() { } function isEventType(type) { - return type === OBJECT_TYPES.EVENTS_OBJECT_TYPE; + return type === OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE || type === OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE; } function isAlarmType(type) { diff --git a/src/providers/object-provider.js b/src/providers/object-provider.js index 6dca9e9b..90b0b5f0 100644 --- a/src/providers/object-provider.js +++ b/src/providers/object-provider.js @@ -29,7 +29,7 @@ import { import { OBJECT_TYPES, NAMESPACE } from '../const.js'; import { createCommandsObject } from './commands.js'; -import { createEventsObject } from './events.js'; +import { createRootEventsObject, createEventObject, getEventSources } from './events.js'; import { getPossibleStatusesFromParameter, getRoleFromParameter, isOperatorStatusParameter } from './user/operator-status-parameter.js'; import { getMissionActionFromParameter, getPossibleMissionActionStatusesFromParameter, isMissionStatusParameter } from './mission-status/mission-status-parameter.js'; @@ -63,12 +63,9 @@ export default class YamcsObjectProvider { #initialize() { this.#createRootObject(); - const eventsObject = createEventsObject(this.openmct, this.key, this.namespace); const commandsObject = createCommandsObject(this.openmct, this.key, this.namespace); this.#addObject(commandsObject); - this.#addObject(eventsObject); this.rootObject.composition.push( - eventsObject.identifier, commandsObject.identifier ); @@ -222,8 +219,32 @@ export default class YamcsObjectProvider { this.#addParameterObject(parameter); }); + // fetch underlying events + await this.#createEvents(this.openmct, this.rootObject, this.key, this.namespace); + return this.dictionary; } + async #createEvents() { + const rootEventsObject = createRootEventsObject(this.openmct, this.key, this.namespace); + this.#addObject(rootEventsObject); + this.rootObject.composition.push(rootEventsObject.identifier); + + // Fetch child event names + const eventSourceNames = await getEventSources(this.openmct, this.url, this.instance, rootEventsObject, this.namespace); + console.debug('👶 Child events', eventSourceNames); + eventSourceNames.forEach(eventSourceName => { + const childEventKey = qualifiedNameToId(`${OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE}.${eventSourceName}`); + const childEventIdentifier = { + key: childEventKey, + namespace: this.namespace + }; + const childEventObject = createEventObject(this.openmct, rootEventsObject.identifier.key, this.namespace, childEventIdentifier, eventSourceName, OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE); + + this.#addObject(childEventObject); + + rootEventsObject.composition.push(childEventObject.identifier); + }); + } #getMdbUrl(operation, name = '') { return this.url + 'api/mdb/' + this.instance + '/' + operation + name; From 7a893ade98a1733c48ac6f00e449bf44e78f921c Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Thu, 5 Dec 2024 11:45:08 +0100 Subject: [PATCH 02/16] split up events --- src/openmct-yamcs.js | 2 +- src/providers/event-limit-provider.js | 4 ++- .../historical-telemetry-provider.js | 25 ++++++++++++++++--- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/openmct-yamcs.js b/src/openmct-yamcs.js index 7bff5a3a..c4345fd0 100644 --- a/src/openmct-yamcs.js +++ b/src/openmct-yamcs.js @@ -186,7 +186,7 @@ export default function install( cssClass: 'icon-telemetry' }); - openmct.types.addType(OBJECT_TYPES.ROOT_EVENTS_OBJECT_TYPE, { + openmct.types.addType(OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE, { name: "Events", description: "To view all events", cssClass: "icon-generator-events" diff --git a/src/providers/event-limit-provider.js b/src/providers/event-limit-provider.js index 3f616cbf..73c01cff 100644 --- a/src/providers/event-limit-provider.js +++ b/src/providers/event-limit-provider.js @@ -1,5 +1,7 @@ /* CSS classes for Yamcs parameter monitoring result values. */ +import { OBJECT_TYPES } from "../const"; + const SEVERITY_CSS = { 'WATCH': 'is-event--yellow', 'WARNING': 'is-event--yellow', @@ -65,6 +67,6 @@ export default class EventLimitProvider { } supportsLimits(domainObject) { - return domainObject.type.startsWith('yamcs.events'); + return domainObject.type === OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE || domainObject.type === OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE; } } diff --git a/src/providers/historical-telemetry-provider.js b/src/providers/historical-telemetry-provider.js index aaae670f..c2ec1e0d 100644 --- a/src/providers/historical-telemetry-provider.js +++ b/src/providers/historical-telemetry-provider.js @@ -66,6 +66,12 @@ export default class YamcsHistoricalTelemetryProvider { const id = domainObject.identifier.key; options.useRawValue = this.hasEnumValue(domainObject); + if (domainObject.type === OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE) { + const prefix = `${OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE}.`; + const eventSourceName = domainObject.identifier.key.replace(prefix, ''); + options.eventSource = eventSourceName; + } + options.isSamples = !this.isImagery(domainObject) && domainObject.type !== OBJECT_TYPES.AGGREGATE_TELEMETRY_TYPE && options.strategy === 'minmax'; @@ -169,6 +175,11 @@ export default class YamcsHistoricalTelemetryProvider { urlWithQueryParameters.searchParams.append('useRawValue', "true"); } + + if (options.eventSource) { + urlWithQueryParameters.searchParams.append('source', options.eventSource); + } + if (options.filters?.severity?.equals?.length) { // add a single minimum severity threshold filter // see https://docs.yamcs.org/yamcs-http-api/events/list-events/ @@ -196,7 +207,11 @@ export default class YamcsHistoricalTelemetryProvider { } getLinkParamsSpecificToId(id) { - if (id === OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE || id === OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE) { + if (id === OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE) { + return 'events'; + } + + if (id.startsWith(OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE)) { return 'events'; } @@ -208,7 +223,11 @@ export default class YamcsHistoricalTelemetryProvider { } getResponseKeyById(id) { - if (id === OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE || id === OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE) { + if (id === OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE) { + return 'event'; + } + + if (id.startsWith(OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE)) { return 'event'; } @@ -224,7 +243,7 @@ export default class YamcsHistoricalTelemetryProvider { return []; } - if (id === OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE || id === OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE) { + if (id === OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE || id.startsWith(OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE)) { return results.map(event => eventToTelemetryDatum(event)); } From 9fd88f8906b36646d8d80b539a00f49cabd9dbe8 Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Thu, 5 Dec 2024 14:53:24 +0100 Subject: [PATCH 03/16] commands implemented, but not limits --- src/const.js | 3 +- src/openmct-yamcs.js | 10 +++- src/providers/commands.js | 54 ++++++++++++++++--- src/providers/events.js | 6 +++ .../historical-telemetry-provider.js | 25 +++++++-- src/providers/messages.js | 5 +- src/providers/object-provider.js | 36 +++++++++---- 7 files changed, 112 insertions(+), 27 deletions(-) diff --git a/src/const.js b/src/const.js index 7f1f6516..c11bb64a 100644 --- a/src/const.js +++ b/src/const.js @@ -21,7 +21,8 @@ *****************************************************************************/ export const OBJECT_TYPES = { - COMMANDS_OBJECT_TYPE: 'yamcs.commands', + COMMANDS_ROOT_OBJECT_TYPE: 'yamcs.commands', + COMMANDS_QUEUE_OBJECT_TYPE: 'yamcs.commands.queue', EVENTS_ROOT_OBJECT_TYPE: 'yamcs.events', EVENT_SPECIFIC_OBJECT_TYPE: 'yamcs.event.specific', TELEMETRY_OBJECT_TYPE: 'yamcs.telemetry', diff --git a/src/openmct-yamcs.js b/src/openmct-yamcs.js index c4345fd0..fd58dec9 100644 --- a/src/openmct-yamcs.js +++ b/src/openmct-yamcs.js @@ -198,9 +198,15 @@ export default function install( cssClass: "icon-generator-events" }); - openmct.types.addType(OBJECT_TYPES.COMMANDS_OBJECT_TYPE, { + openmct.types.addType(OBJECT_TYPES.COMMANDS_QUEUE_OBJECT_TYPE, { + name: "Command Queue", + description: "To view command history in a specific queue", + cssClass: "icon-generator-events" // TODO: replace + }); + + openmct.types.addType(OBJECT_TYPES.COMMANDS_ROOT_OBJECT_TYPE, { name: "Commands", - description: "To view command history", + description: "To view the whole command history", cssClass: "icon-generator-events" // TODO: replace }); diff --git a/src/providers/commands.js b/src/providers/commands.js index 0f73ec18..b97d6c2a 100644 --- a/src/providers/commands.js +++ b/src/providers/commands.js @@ -23,21 +23,33 @@ import { OBJECT_TYPES, METADATA_TIME_KEY } from "../const.js"; import { flattenObjectArray } from "../utils.js"; -export function createCommandsObject(openmct, parentKey, namespace) { +export function createRootCommandsObject(openmct, parentKey, namespace) { + const rootCommandsIdentifier = { + key: OBJECT_TYPES.COMMANDS_ROOT_OBJECT_TYPE, + namespace + }; + const rootCommandsObject = createCommandObject(openmct, parentKey, namespace, rootCommandsIdentifier); + + return rootCommandsObject; +} + +export function createCommandObject(openmct, parentKey, namespace, identifier, queueName = null) { + const isRoot = queueName === null; const location = openmct.objects.makeKeyString({ key: parentKey, namespace }); - const identifier = { - key: OBJECT_TYPES.COMMANDS_OBJECT_TYPE, - namespace: namespace - }; + const name = isRoot ? 'Commands' : queueName; + const type = isRoot + ? OBJECT_TYPES.COMMANDS_ROOT_OBJECT_TYPE + : OBJECT_TYPES.COMMANDS_QUEUE_OBJECT_TYPE; + const commandObject = { identifier, location, - name: 'Commands', - type: OBJECT_TYPES.COMMANDS_OBJECT_TYPE, + name, + type, telemetry: { values: [ { @@ -164,9 +176,35 @@ export function createCommandsObject(openmct, parentKey, namespace) { } }; + if (isRoot) { + commandObject.composition = []; + } + return commandObject; } +export async function getCommandQueues(url, instance, processor = 'realtime') { + const commandQueuesURL = `${url}api/processors/${instance}/${processor}/queues`; + const response = await fetch(commandQueuesURL, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }); + + if (!response.ok) { + console.error(`🛑 Error fetching command queues: ${response.statusText}`); + + return []; + } + + const commandQueueJson = await response.json(); + const { queues } = commandQueueJson; + const queueNames = queues.map(queue => queue.name); + + return queueNames; +} + /** * Convert raw command data from YAMCS to a format which * can be consumed by Open MCT as telemetry. @@ -177,7 +215,7 @@ export function commandToTelemetryDatum(command) { const { generationTime, commandId, attr, assignments, id } = command; const { origin, sequenceNumber, commandName } = commandId; let datum = { - id: OBJECT_TYPES.COMMANDS_OBJECT_TYPE, + id: OBJECT_TYPES.COMMANDS_QUEUE_OBJECT_TYPE, generationTime, origin, sequenceNumber, diff --git a/src/providers/events.js b/src/providers/events.js index aa1c7e09..6301d27f 100644 --- a/src/providers/events.js +++ b/src/providers/events.js @@ -109,6 +109,12 @@ export function createEventObject(openmct, parentKey, namespace, identifier, nam export async function getEventSources(openmct, url, instance, rootEventObject, namespace) { const eventSourceURL = `${url}/api/archive/${instance}/events/sources`; const eventSourcesReply = await fetch(eventSourceURL); + if (!eventSourcesReply.ok) { + console.error(`🛑 Failed to fetch event sources: ${eventSourcesReply.statusText}`); + + return []; + } + const eventSourcesJson = await eventSourcesReply.json(); return eventSourcesJson.sources; diff --git a/src/providers/historical-telemetry-provider.js b/src/providers/historical-telemetry-provider.js index c2ec1e0d..0072036b 100644 --- a/src/providers/historical-telemetry-provider.js +++ b/src/providers/historical-telemetry-provider.js @@ -72,6 +72,12 @@ export default class YamcsHistoricalTelemetryProvider { options.eventSource = eventSourceName; } + if (domainObject.type === OBJECT_TYPES.COMMANDS_QUEUE_OBJECT_TYPE) { + const prefix = `${OBJECT_TYPES.COMMANDS_QUEUE_OBJECT_TYPE}.`; + const commandQueueName = domainObject.identifier.key.replace(prefix, ''); + options.commandQueue = commandQueueName; + } + options.isSamples = !this.isImagery(domainObject) && domainObject.type !== OBJECT_TYPES.AGGREGATE_TELEMETRY_TYPE && options.strategy === 'minmax'; @@ -175,11 +181,14 @@ export default class YamcsHistoricalTelemetryProvider { urlWithQueryParameters.searchParams.append('useRawValue', "true"); } - if (options.eventSource) { urlWithQueryParameters.searchParams.append('source', options.eventSource); } + if (options.commandQueue) { + urlWithQueryParameters.searchParams.append('queue', options.commandQueue); + } + if (options.filters?.severity?.equals?.length) { // add a single minimum severity threshold filter // see https://docs.yamcs.org/yamcs-http-api/events/list-events/ @@ -215,7 +224,11 @@ export default class YamcsHistoricalTelemetryProvider { return 'events'; } - if (id === OBJECT_TYPES.COMMANDS_OBJECT_TYPE) { + if (id === OBJECT_TYPES.COMMANDS_ROOT_OBJECT_TYPE) { + return 'commands'; + } + + if (id.startsWith(OBJECT_TYPES.COMMANDS_QUEUE_OBJECT_TYPE)) { return 'commands'; } @@ -231,7 +244,11 @@ export default class YamcsHistoricalTelemetryProvider { return 'event'; } - if (id === OBJECT_TYPES.COMMANDS_OBJECT_TYPE) { + if (id === OBJECT_TYPES.COMMANDS_ROOT_OBJECT_TYPE) { + return 'entry'; + } + + if (id.startsWith(OBJECT_TYPES.COMMANDS_QUEUE_OBJECT_TYPE)) { return 'entry'; } @@ -247,7 +264,7 @@ export default class YamcsHistoricalTelemetryProvider { return results.map(event => eventToTelemetryDatum(event)); } - if (id === OBJECT_TYPES.COMMANDS_OBJECT_TYPE) { + if (id === OBJECT_TYPES.COMMANDS_ROOT_OBJECT_TYPE || id.startsWith(OBJECT_TYPES.COMMANDS_QUEUE_OBJECT_TYPE)) { return results.map(command => commandToTelemetryDatum(command)); } diff --git a/src/providers/messages.js b/src/providers/messages.js index 3579e288..9b3f7fb1 100644 --- a/src/providers/messages.js +++ b/src/providers/messages.js @@ -1,7 +1,8 @@ import {OBJECT_TYPES, DATA_TYPES, MDB_TYPE} from '../const.js'; const typeMap = { - [OBJECT_TYPES.COMMANDS_OBJECT_TYPE]: DATA_TYPES.DATA_TYPE_COMMANDS, + [OBJECT_TYPES.COMMANDS_ROOT_OBJECT_TYPE]: DATA_TYPES.DATA_TYPE_COMMANDS, + [OBJECT_TYPES.COMMANDS_QUEUE_OBJECT_TYPE]: DATA_TYPES.DATA_TYPE_COMMANDS, [OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE]: DATA_TYPES.DATA_TYPE_EVENTS, [OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE]: DATA_TYPES.DATA_TYPE_EVENTS, [OBJECT_TYPES.TELEMETRY_OBJECT_TYPE]: DATA_TYPES.DATA_TYPE_TELEMETRY, @@ -86,7 +87,7 @@ function isAlarmType(type) { } function isCommandType(type) { - return type === OBJECT_TYPES.COMMANDS_OBJECT_TYPE; + return type === OBJECT_TYPES.COMMANDS_QUEUE_OBJECT_TYPE || type === OBJECT_TYPES.COMMANDS_ROOT_OBJECT_TYPE; } function isMdbChangesType(type) { diff --git a/src/providers/object-provider.js b/src/providers/object-provider.js index 90b0b5f0..28c5ea50 100644 --- a/src/providers/object-provider.js +++ b/src/providers/object-provider.js @@ -28,7 +28,7 @@ import { } from '../utils.js'; import { OBJECT_TYPES, NAMESPACE } from '../const.js'; -import { createCommandsObject } from './commands.js'; +import { createCommandObject, getCommandQueues, createRootCommandsObject } from './commands.js'; import { createRootEventsObject, createEventObject, getEventSources } from './events.js'; import { getPossibleStatusesFromParameter, getRoleFromParameter, isOperatorStatusParameter } from './user/operator-status-parameter.js'; import { getMissionActionFromParameter, getPossibleMissionActionStatusesFromParameter, isMissionStatusParameter } from './mission-status/mission-status-parameter.js'; @@ -63,12 +63,6 @@ export default class YamcsObjectProvider { #initialize() { this.#createRootObject(); - const commandsObject = createCommandsObject(this.openmct, this.key, this.namespace); - this.#addObject(commandsObject); - this.rootObject.composition.push( - commandsObject.identifier - ); - this.openmct.on('destroy', this.#unsubscribeFromAll); } @@ -219,11 +213,34 @@ export default class YamcsObjectProvider { this.#addParameterObject(parameter); }); - // fetch underlying events - await this.#createEvents(this.openmct, this.rootObject, this.key, this.namespace); + await this.#createCommands(); + + await this.#createEvents(); return this.dictionary; } + + async #createCommands() { + const rootCommandsObject = createRootCommandsObject(this.openmct, this.key, this.namespace); + this.#addObject(rootCommandsObject); + this.rootObject.composition.push( + rootCommandsObject.identifier + ); + const commandQueues = await getCommandQueues(this.url, this.instance); + commandQueues.forEach(commandQueueName => { + const queueKey = qualifiedNameToId(`${OBJECT_TYPES.COMMANDS_QUEUE_OBJECT_TYPE}.${commandQueueName}`); + const queueIdentifier = { + key: queueKey, + namespace: this.namespace + }; + const commandQueueObject = createCommandObject(this.openmct, rootCommandsObject.identifier.key, this.namespace, queueIdentifier, commandQueueName, OBJECT_TYPES.COMMANDS_QUEUE_OBJECT_TYPE); + + this.#addObject(commandQueueObject); + + rootCommandsObject.composition.push(commandQueueObject.identifier); + }); + } + async #createEvents() { const rootEventsObject = createRootEventsObject(this.openmct, this.key, this.namespace); this.#addObject(rootEventsObject); @@ -231,7 +248,6 @@ export default class YamcsObjectProvider { // Fetch child event names const eventSourceNames = await getEventSources(this.openmct, this.url, this.instance, rootEventsObject, this.namespace); - console.debug('👶 Child events', eventSourceNames); eventSourceNames.forEach(eventSourceName => { const childEventKey = qualifiedNameToId(`${OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE}.${eventSourceName}`); const childEventIdentifier = { From 62d07872c16747aa21aa5deb7ca76d46d2c5d101 Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Thu, 5 Dec 2024 15:04:00 +0100 Subject: [PATCH 04/16] commands work too --- src/providers/limit-provider.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/providers/limit-provider.js b/src/providers/limit-provider.js index a05e9490..3277f06c 100644 --- a/src/providers/limit-provider.js +++ b/src/providers/limit-provider.js @@ -93,6 +93,10 @@ export default class LimitProvider { } supportsLimits(domainObject) { + if (domainObject.type.startsWith('yamcs.commands')) { + return false; + } + return domainObject.type.startsWith('yamcs.'); } From 06d58ab4df8b6a6066ce179dfd71ca94f363337d Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Wed, 11 Dec 2024 11:33:54 +0100 Subject: [PATCH 05/16] add time strip to events --- example/index.js | 2 + example/make-example-events.js | 172 ++++++++++++++++----------------- 2 files changed, 88 insertions(+), 86 deletions(-) diff --git a/example/index.js b/example/index.js index 3b968793..fc274356 100644 --- a/example/index.js +++ b/example/index.js @@ -115,6 +115,8 @@ const openmct = window.openmct; openmct.install(openmct.plugins.FaultManagement()); openmct.install(openmct.plugins.BarChart()); + openmct.install(openmct.plugins.Timeline()); + openmct.install(openmct.plugins.EventTimestripPlugin()); // setup example display layout openmct.on('start', async () => { diff --git a/example/make-example-events.js b/example/make-example-events.js index 1fc32816..a46b0f9e 100644 --- a/example/make-example-events.js +++ b/example/make-example-events.js @@ -2,101 +2,101 @@ const INSTANCE = "myproject"; const URL = `http://localhost:8090/api/archive/${INSTANCE}/events`; const events = [ - // Pressure Events - { - type: "PRESSURE_ALERT", - message: "Pressure threshold exceeded", - severity: "CRITICAL", - source: "PressureModule", - sequenceNumber: 1, - extra: { - pressure: "150 PSI", - location: "Hydraulic System" + // Pressure Events + { + type: "PRESSURE_ALERT", + message: "Pressure threshold exceeded", + severity: "CRITICAL", + source: "PressureModule", + sequenceNumber: 1, + extra: { + pressure: "150 PSI", + location: "Hydraulic System" + } + }, + { + type: "PRESSURE_WARNING", + message: "Pressure nearing critical level", + severity: "WARNING", + source: "PressureMonitor", + sequenceNumber: 2, + extra: { + pressure: "140 PSI", + location: "Hydraulic System" + } + }, + { + type: "PRESSURE_INFO", + message: "Pressure system check completed", + severity: "INFO", + source: "MaintenanceModule", + sequenceNumber: 3, + extra: { + checkType: "Routine Inspection", + duration: "10m" + } + }, + // Temperature Events + { + type: "TEMPERATURE_ALERT", + message: "Temperature threshold exceeded", + severity: "CRITICAL", + source: "TemperatureModule", + sequenceNumber: 4, + extra: { + temperature: "100°C", + location: "Engine Room" + } + }, + { + type: "TEMPERATURE_WARNING", + message: "Temperature nearing critical level", + severity: "WARNING", + source: "TemperatureMonitor", + sequenceNumber: 5, + extra: { + temperature: "95°C", + location: "Engine Room" + } + }, + { + type: "TEMPERATURE_INFO", + message: "Temperature system check completed", + severity: "INFO", + source: "MaintenanceModule", + sequenceNumber: 6, + extra: { + checkType: "Routine Inspection", + duration: "15m" + } } - }, - { - type: "PRESSURE_WARNING", - message: "Pressure nearing critical level", - severity: "WARNING", - source: "PressureMonitor", - sequenceNumber: 2, - extra: { - pressure: "140 PSI", - location: "Hydraulic System" - } - }, - { - type: "PRESSURE_INFO", - message: "Pressure system check completed", - severity: "INFO", - source: "MaintenanceModule", - sequenceNumber: 3, - extra: { - checkType: "Routine Inspection", - duration: "10m" - } - }, - // Temperature Events - { - type: "TEMPERATURE_ALERT", - message: "Temperature threshold exceeded", - severity: "CRITICAL", - source: "TemperatureModule", - sequenceNumber: 4, - extra: { - temperature: "100°C", - location: "Engine Room" - } - }, - { - type: "TEMPERATURE_WARNING", - message: "Temperature nearing critical level", - severity: "WARNING", - source: "TemperatureMonitor", - sequenceNumber: 5, - extra: { - temperature: "95°C", - location: "Engine Room" - } - }, - { - type: "TEMPERATURE_INFO", - message: "Temperature system check completed", - severity: "INFO", - source: "MaintenanceModule", - sequenceNumber: 6, - extra: { - checkType: "Routine Inspection", - duration: "15m" - } - } ]; async function postEvent(event, delaySeconds) { - const eventTime = new Date(Date.now() + delaySeconds * 1000).toISOString(); - event.time = eventTime; + const eventTime = new Date(Date.now() + delaySeconds * 1000).toISOString(); + event.time = eventTime; - try { - const response = await fetch(URL, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(event) - }); + try { + const response = await fetch(URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(event) + }); - if (response.ok) { - console.log(`Event posted successfully: ${event.type}`); - } else { - console.error(`Failed to post event: ${event.type}. HTTP Status: ${response.status}`); + if (response.ok) { + console.log(`Event posted successfully: ${event.type}`); + } else { + console.error(`Failed to post event: ${event.type}. HTTP Status: ${response.status}`); + } + } catch (error) { + console.error(`Error posting event: ${event.type}.`, error); } - } catch (error) { - console.error(`Error posting event: ${event.type}.`, error); - } } async function main() { - for (let i = 0; i < events.length; i++) { - await postEvent(events[i], i * 5); - } + for (let i = 0; i < events.length; i++) { + await postEvent(events[i], i * 5); + } } -main(); \ No newline at end of file +main(); From 78d591288012fe93342ed90a01e559b64cb3aa71 Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Wed, 11 Dec 2024 17:20:52 +0100 Subject: [PATCH 06/16] alter plugin install --- example/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/example/index.js b/example/index.js index fc274356..ac8423fb 100644 --- a/example/index.js +++ b/example/index.js @@ -115,8 +115,9 @@ const openmct = window.openmct; openmct.install(openmct.plugins.FaultManagement()); openmct.install(openmct.plugins.BarChart()); - openmct.install(openmct.plugins.Timeline()); - openmct.install(openmct.plugins.EventTimestripPlugin()); + const timeLinePlugin = openmct.plugins.Timeline(); + openmct.install(timeLinePlugin); + openmct.install(openmct.plugins.EventTimestripPlugin(timeLinePlugin.eventBus)); // setup example display layout openmct.on('start', async () => { From dea693ff03cae125196be200dbfbb6af606e9bd7 Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Fri, 13 Dec 2024 10:35:00 +0100 Subject: [PATCH 07/16] ensure colored lines work --- example/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/index.js b/example/index.js index ac8423fb..86e4954e 100644 --- a/example/index.js +++ b/example/index.js @@ -117,7 +117,7 @@ const openmct = window.openmct; openmct.install(openmct.plugins.BarChart()); const timeLinePlugin = openmct.plugins.Timeline(); openmct.install(timeLinePlugin); - openmct.install(openmct.plugins.EventTimestripPlugin(timeLinePlugin.eventBus)); + openmct.install(openmct.plugins.EventTimestripPlugin(timeLinePlugin.extendedLinesBus)); // setup example display layout openmct.on('start', async () => { From 0173fb15426d3507047086611497cd110e8c35b3 Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Fri, 13 Dec 2024 12:35:47 +0100 Subject: [PATCH 08/16] add metadata for timestrip --- src/providers/commands.js | 5 ++++- src/providers/events.js | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/providers/commands.js b/src/providers/commands.js index b97d6c2a..316abc37 100644 --- a/src/providers/commands.js +++ b/src/providers/commands.js @@ -55,7 +55,10 @@ export function createCommandObject(openmct, parentKey, namespace, identifier, q { key: 'commandName', name: 'Command', - format: 'string' + format: 'string', + hints: { + label: 0 + } }, { key: 'utc', diff --git a/src/providers/events.js b/src/providers/events.js index 6301d27f..e1937d25 100644 --- a/src/providers/events.js +++ b/src/providers/events.js @@ -82,7 +82,10 @@ export function createEventObject(openmct, parentKey, namespace, identifier, nam { key: 'message', name: 'Message', - format: 'string' + format: 'string', + hints: { + label: 0 + } }, { key: 'type', From fc8fffe76a458d0bae79e3c7f75c1144fcb18658 Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Tue, 17 Dec 2024 20:03:19 +0100 Subject: [PATCH 09/16] add severity yamcs objects --- src/const.js | 1 + src/openmct-yamcs.js | 6 +++++ src/providers/event-limit-provider.js | 2 +- src/providers/events.js | 27 ++++++++++++++++++- .../historical-telemetry-provider.js | 24 +++++++++++------ src/providers/messages.js | 3 ++- src/providers/object-provider.js | 15 +++++++++-- 7 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/const.js b/src/const.js index c11bb64a..35183d16 100644 --- a/src/const.js +++ b/src/const.js @@ -25,6 +25,7 @@ export const OBJECT_TYPES = { COMMANDS_QUEUE_OBJECT_TYPE: 'yamcs.commands.queue', EVENTS_ROOT_OBJECT_TYPE: 'yamcs.events', EVENT_SPECIFIC_OBJECT_TYPE: 'yamcs.event.specific', + EVENT_SPECIFIC_SEVERITY_OBJECT_TYPE: 'yamcs.event.specific.severity', TELEMETRY_OBJECT_TYPE: 'yamcs.telemetry', IMAGE_OBJECT_TYPE: 'yamcs.image', STRING_OBJECT_TYPE: 'yamcs.string', diff --git a/src/openmct-yamcs.js b/src/openmct-yamcs.js index fd58dec9..b87fefa3 100644 --- a/src/openmct-yamcs.js +++ b/src/openmct-yamcs.js @@ -198,6 +198,12 @@ export default function install( cssClass: "icon-generator-events" }); + openmct.types.addType(OBJECT_TYPES.EVENT_SPECIFIC_SEVERITY_OBJECT_TYPE, { + name: "Event", + description: "To view events from a specific source with a specific severity or greater", + cssClass: "icon-generator-events" + }); + openmct.types.addType(OBJECT_TYPES.COMMANDS_QUEUE_OBJECT_TYPE, { name: "Command Queue", description: "To view command history in a specific queue", diff --git a/src/providers/event-limit-provider.js b/src/providers/event-limit-provider.js index 73c01cff..452f9801 100644 --- a/src/providers/event-limit-provider.js +++ b/src/providers/event-limit-provider.js @@ -67,6 +67,6 @@ export default class EventLimitProvider { } supportsLimits(domainObject) { - return domainObject.type === OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE || domainObject.type === OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE; + return [OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE, OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE, OBJECT_TYPES.EVENT_SPECIFIC_SEVERITY_OBJECT_TYPE].includes(domainObject.type); } } diff --git a/src/providers/events.js b/src/providers/events.js index e1937d25..fb70d208 100644 --- a/src/providers/events.js +++ b/src/providers/events.js @@ -109,7 +109,32 @@ export function createEventObject(openmct, parentKey, namespace, identifier, nam return baseEventObject; } -export async function getEventSources(openmct, url, instance, rootEventObject, namespace) { +export function createEventSeverityObjects(openmct, parentEventObject, namespace) { + const childSeverityObjects = []; + for (const severity of SEVERITY_LEVELS) { + const severityIdentifier = { + key: `${parentEventObject.identifier.key}.${severity}`, + namespace + }; + + const severityName = `${parentEventObject.name}: ${severity}`; + + const severityEventObject = createEventObject( + openmct, + parentEventObject.identifier.key, + namespace, + severityIdentifier, + severityName, + OBJECT_TYPES.EVENT_SPECIFIC_SEVERITY_OBJECT_TYPE + ); + + childSeverityObjects.push(severityEventObject); + } + + return childSeverityObjects; +} + +export async function getEventSources(url, instance) { const eventSourceURL = `${url}/api/archive/${instance}/events/sources`; const eventSourcesReply = await fetch(eventSourceURL); if (!eventSourcesReply.ok) { diff --git a/src/providers/historical-telemetry-provider.js b/src/providers/historical-telemetry-provider.js index 0072036b..4bb1d64a 100644 --- a/src/providers/historical-telemetry-provider.js +++ b/src/providers/historical-telemetry-provider.js @@ -54,8 +54,9 @@ export default class YamcsHistoricalTelemetryProvider { async request(domainObject, options) { options = { ...options }; + const isEvent = ([OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE, OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE, OBJECT_TYPES.EVENT_SPECIFIC_SEVERITY_OBJECT_TYPE].includes(domainObject.type)); this.standardizeOptions(options, domainObject); - if ((options.strategy === 'latest') && options.timeContext?.isRealTime()) { + if ((options.strategy === 'latest') && options.timeContext?.isRealTime() && !isEvent) { // Latest requested in realtime, use latest telemetry provider instead const mctDatum = await this.latestTelemetryProvider.requestLatest(domainObject); @@ -66,12 +67,16 @@ export default class YamcsHistoricalTelemetryProvider { const id = domainObject.identifier.key; options.useRawValue = this.hasEnumValue(domainObject); - if (domainObject.type === OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE) { + if ([OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE, OBJECT_TYPES.EVENT_SPECIFIC_SEVERITY_OBJECT_TYPE].includes(domainObject.type)) { const prefix = `${OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE}.`; const eventSourceName = domainObject.identifier.key.replace(prefix, ''); options.eventSource = eventSourceName; } + if (domainObject.type === OBJECT_TYPES.EVENT_SPECIFIC_SEVERITY_OBJECT_TYPE) { + options.filters.severity = domainObject.identifier.key.split('.')[1]; + } + if (domainObject.type === OBJECT_TYPES.COMMANDS_QUEUE_OBJECT_TYPE) { const prefix = `${OBJECT_TYPES.COMMANDS_QUEUE_OBJECT_TYPE}.`; const commandQueueName = domainObject.identifier.key.replace(prefix, ''); @@ -220,7 +225,9 @@ export default class YamcsHistoricalTelemetryProvider { return 'events'; } - if (id.startsWith(OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE)) { + if (id === OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE + || id.startsWith(OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE) + || id.startsWith(OBJECT_TYPES.EVENT_SPECIFIC_SEVERITY_OBJECT_TYPE)) { return 'events'; } @@ -236,11 +243,10 @@ export default class YamcsHistoricalTelemetryProvider { } getResponseKeyById(id) { - if (id === OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE) { - return 'event'; - } - if (id.startsWith(OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE)) { + if (id === (OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE) + || id.startsWith(OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE) + || id.startsWith(OBJECT_TYPES.EVENT_SPECIFIC_SEVERITY_OBJECT_TYPE)) { return 'event'; } @@ -260,7 +266,9 @@ export default class YamcsHistoricalTelemetryProvider { return []; } - if (id === OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE || id.startsWith(OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE)) { + if (id === OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE + || id.startsWith(OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE) + || id.startsWith(OBJECT_TYPES.EVENT_SPECIFIC_SEVERITY_OBJECT_TYPE)) { return results.map(event => eventToTelemetryDatum(event)); } diff --git a/src/providers/messages.js b/src/providers/messages.js index 9b3f7fb1..74ce4f37 100644 --- a/src/providers/messages.js +++ b/src/providers/messages.js @@ -4,6 +4,7 @@ const typeMap = { [OBJECT_TYPES.COMMANDS_ROOT_OBJECT_TYPE]: DATA_TYPES.DATA_TYPE_COMMANDS, [OBJECT_TYPES.COMMANDS_QUEUE_OBJECT_TYPE]: DATA_TYPES.DATA_TYPE_COMMANDS, [OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE]: DATA_TYPES.DATA_TYPE_EVENTS, + [OBJECT_TYPES.EVENT_SPECIFIC_SEVERITY_OBJECT_TYPE]: DATA_TYPES.DATA_TYPE_EVENTS, [OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE]: DATA_TYPES.DATA_TYPE_EVENTS, [OBJECT_TYPES.TELEMETRY_OBJECT_TYPE]: DATA_TYPES.DATA_TYPE_TELEMETRY, [OBJECT_TYPES.STRING_OBJECT_TYPE]: DATA_TYPES.DATA_TYPE_TELEMETRY, @@ -78,7 +79,7 @@ function buildSubscribeMessages() { } function isEventType(type) { - return type === OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE || type === OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE; + return [OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE, OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE, OBJECT_TYPES.EVENT_SPECIFIC_SEVERITY_OBJECT_TYPE].includes(type); } function isAlarmType(type) { diff --git a/src/providers/object-provider.js b/src/providers/object-provider.js index 28c5ea50..62df6c76 100644 --- a/src/providers/object-provider.js +++ b/src/providers/object-provider.js @@ -29,7 +29,7 @@ import { import { OBJECT_TYPES, NAMESPACE } from '../const.js'; import { createCommandObject, getCommandQueues, createRootCommandsObject } from './commands.js'; -import { createRootEventsObject, createEventObject, getEventSources } from './events.js'; +import { createRootEventsObject, createEventObject, getEventSources, createEventSeverityObjects } from './events.js'; import { getPossibleStatusesFromParameter, getRoleFromParameter, isOperatorStatusParameter } from './user/operator-status-parameter.js'; import { getMissionActionFromParameter, getPossibleMissionActionStatusesFromParameter, isMissionStatusParameter } from './mission-status/mission-status-parameter.js'; @@ -247,7 +247,7 @@ export default class YamcsObjectProvider { this.rootObject.composition.push(rootEventsObject.identifier); // Fetch child event names - const eventSourceNames = await getEventSources(this.openmct, this.url, this.instance, rootEventsObject, this.namespace); + const eventSourceNames = await getEventSources(this.url, this.instance); eventSourceNames.forEach(eventSourceName => { const childEventKey = qualifiedNameToId(`${OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE}.${eventSourceName}`); const childEventIdentifier = { @@ -258,6 +258,17 @@ export default class YamcsObjectProvider { this.#addObject(childEventObject); + const childSeverityObjects = []; + //const childSeverityObjects = createEventSeverityObjects(this.openmct, childEventObject, this.namespace); + childSeverityObjects.forEach(severityObject => { + this.#addObject(severityObject); + if (!childEventObject.composition) { + childEventObject.composition = []; + } + + childEventObject.composition.push(severityObject.identifier); + }); + rootEventsObject.composition.push(childEventObject.identifier); }); } From 7abac4820ff67607fb38adfd017e222bf56fd8d3 Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Wed, 18 Dec 2024 09:49:56 +0100 Subject: [PATCH 10/16] fix events keyword --- src/providers/historical-telemetry-provider.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/providers/historical-telemetry-provider.js b/src/providers/historical-telemetry-provider.js index 4bb1d64a..bcb413ac 100644 --- a/src/providers/historical-telemetry-provider.js +++ b/src/providers/historical-telemetry-provider.js @@ -244,10 +244,10 @@ export default class YamcsHistoricalTelemetryProvider { getResponseKeyById(id) { - if (id === (OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE) + if (id === (OBJECT_TYPES.EVENTS_ROOT_OBJECT_TYPE) || id.startsWith(OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE) || id.startsWith(OBJECT_TYPES.EVENT_SPECIFIC_SEVERITY_OBJECT_TYPE)) { - return 'event'; + return 'events'; } if (id === OBJECT_TYPES.COMMANDS_ROOT_OBJECT_TYPE) { From 51b58ccc8f12c3bcd7d6fe20fbfee63c7238688a Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Wed, 18 Dec 2024 10:03:12 +0100 Subject: [PATCH 11/16] add child severity objects to events --- src/providers/historical-telemetry-provider.js | 16 ++++++++++++++-- src/providers/object-provider.js | 3 +-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/providers/historical-telemetry-provider.js b/src/providers/historical-telemetry-provider.js index bcb413ac..996854d2 100644 --- a/src/providers/historical-telemetry-provider.js +++ b/src/providers/historical-telemetry-provider.js @@ -67,14 +67,26 @@ export default class YamcsHistoricalTelemetryProvider { const id = domainObject.identifier.key; options.useRawValue = this.hasEnumValue(domainObject); - if ([OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE, OBJECT_TYPES.EVENT_SPECIFIC_SEVERITY_OBJECT_TYPE].includes(domainObject.type)) { + if ([OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE].includes(domainObject.type)) { const prefix = `${OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE}.`; const eventSourceName = domainObject.identifier.key.replace(prefix, ''); options.eventSource = eventSourceName; } if (domainObject.type === OBJECT_TYPES.EVENT_SPECIFIC_SEVERITY_OBJECT_TYPE) { - options.filters.severity = domainObject.identifier.key.split('.')[1]; + if (!options.filters) { + options.filters = { + severity: { + equals: [] + } + }; + } + + const prefix = `${OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE}.`; + const prefixRemoved = domainObject.identifier.key.replace(prefix, ''); + const [eventSourceName, severity] = prefixRemoved.split('.'); + options.eventSource = eventSourceName; + options.filters.severity.equals = [severity]; } if (domainObject.type === OBJECT_TYPES.COMMANDS_QUEUE_OBJECT_TYPE) { diff --git a/src/providers/object-provider.js b/src/providers/object-provider.js index 62df6c76..2140e06a 100644 --- a/src/providers/object-provider.js +++ b/src/providers/object-provider.js @@ -258,8 +258,7 @@ export default class YamcsObjectProvider { this.#addObject(childEventObject); - const childSeverityObjects = []; - //const childSeverityObjects = createEventSeverityObjects(this.openmct, childEventObject, this.namespace); + const childSeverityObjects = createEventSeverityObjects(this.openmct, childEventObject, this.namespace); childSeverityObjects.forEach(severityObject => { this.#addObject(severityObject); if (!childEventObject.composition) { From ec2b64a6a6ba258533e5402bccc2bdbdbe60f557 Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Wed, 18 Dec 2024 10:20:44 +0100 Subject: [PATCH 12/16] add severity levels --- example/make-example-events.js | 38 ++++++++++++++----- .../historical-telemetry-provider.js | 14 +++---- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/example/make-example-events.js b/example/make-example-events.js index a46b0f9e..9f317298 100644 --- a/example/make-example-events.js +++ b/example/make-example-events.js @@ -2,7 +2,6 @@ const INSTANCE = "myproject"; const URL = `http://localhost:8090/api/archive/${INSTANCE}/events`; const events = [ - // Pressure Events { type: "PRESSURE_ALERT", message: "Pressure threshold exceeded", @@ -18,7 +17,7 @@ const events = [ type: "PRESSURE_WARNING", message: "Pressure nearing critical level", severity: "WARNING", - source: "PressureMonitor", + source: "PressureModule", sequenceNumber: 2, extra: { pressure: "140 PSI", @@ -29,14 +28,13 @@ const events = [ type: "PRESSURE_INFO", message: "Pressure system check completed", severity: "INFO", - source: "MaintenanceModule", + source: "PressureModule", sequenceNumber: 3, extra: { checkType: "Routine Inspection", duration: "10m" } }, - // Temperature Events { type: "TEMPERATURE_ALERT", message: "Temperature threshold exceeded", @@ -52,7 +50,7 @@ const events = [ type: "TEMPERATURE_WARNING", message: "Temperature nearing critical level", severity: "WARNING", - source: "TemperatureMonitor", + source: "TemperatureModule", sequenceNumber: 5, extra: { temperature: "95°C", @@ -61,13 +59,35 @@ const events = [ }, { type: "TEMPERATURE_INFO", - message: "Temperature system check completed", + message: "Temperature nominal", severity: "INFO", - source: "MaintenanceModule", + source: "TemperatureModule", sequenceNumber: 6, extra: { - checkType: "Routine Inspection", - duration: "15m" + temperature: "35°C", + location: "Life Support" + } + }, + { + type: "TEMPERATURE_INFO", + message: "Temperature nominal", + severity: "INFO", + source: "TemperatureModule", + sequenceNumber: 7, + extra: { + temperature: "30°C", + location: "Life Support" + } + }, + { + type: "TEMPERATURE_SEVERE", + message: "Temperature nominal", + severity: "SEVERE", + source: "TemperatureModule", + sequenceNumber: 8, + extra: { + temperature: "200°C", + location: "Engine Room" } } ]; diff --git a/src/providers/historical-telemetry-provider.js b/src/providers/historical-telemetry-provider.js index 996854d2..3ed424ac 100644 --- a/src/providers/historical-telemetry-provider.js +++ b/src/providers/historical-telemetry-provider.js @@ -74,19 +74,11 @@ export default class YamcsHistoricalTelemetryProvider { } if (domainObject.type === OBJECT_TYPES.EVENT_SPECIFIC_SEVERITY_OBJECT_TYPE) { - if (!options.filters) { - options.filters = { - severity: { - equals: [] - } - }; - } - const prefix = `${OBJECT_TYPES.EVENT_SPECIFIC_OBJECT_TYPE}.`; const prefixRemoved = domainObject.identifier.key.replace(prefix, ''); const [eventSourceName, severity] = prefixRemoved.split('.'); options.eventSource = eventSourceName; - options.filters.severity.equals = [severity]; + options.minimumSeverity = severity; } if (domainObject.type === OBJECT_TYPES.COMMANDS_QUEUE_OBJECT_TYPE) { @@ -202,6 +194,10 @@ export default class YamcsHistoricalTelemetryProvider { urlWithQueryParameters.searchParams.append('source', options.eventSource); } + if (options.minimumSeverity) { + urlWithQueryParameters.searchParams.append('severity', options.minimumSeverity); + } + if (options.commandQueue) { urlWithQueryParameters.searchParams.append('queue', options.commandQueue); } From 11c2d0286e2a58c05cc29c964102a13570bde5f9 Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Wed, 18 Dec 2024 11:28:13 +0100 Subject: [PATCH 13/16] add tests --- ...mple-events.js => make-example-events.mjs} | 9 ++- package.json | 2 +- tests/e2e/yamcs/timeline.e2e.spec.mjs | 61 +++++++++++++++++++ 3 files changed, 69 insertions(+), 3 deletions(-) rename example/{make-example-events.js => make-example-events.mjs} (94%) create mode 100644 tests/e2e/yamcs/timeline.e2e.spec.mjs diff --git a/example/make-example-events.js b/example/make-example-events.mjs similarity index 94% rename from example/make-example-events.js rename to example/make-example-events.mjs index 9f317298..aa8ab162 100644 --- a/example/make-example-events.js +++ b/example/make-example-events.mjs @@ -1,3 +1,5 @@ +import process from 'process'; + const INSTANCE = "myproject"; const URL = `http://localhost:8090/api/archive/${INSTANCE}/events`; @@ -113,10 +115,13 @@ async function postEvent(event, delaySeconds) { } } -async function main() { +export async function postAllEvents() { for (let i = 0; i < events.length; i++) { await postEvent(events[i], i * 5); } } -main(); +// If you still want to run it standalone +if (import.meta.url === `file://${process.argv[1]}`) { + postAllEvents(); +} diff --git a/package.json b/package.json index f35c60ce..6ddf2f6f 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "test:e2e:quickstart:local": "npm test --workspace tests/e2e/opensource -- --config=../playwright-quickstart.config.js --project=local-chrome tests/e2e/yamcs/", "test:e2e:watch": "npm test --workspace tests/e2e/opensource -- --ui --config=../playwright-quickstart.config.js", "wait-for-yamcs": "wait-on http-get://localhost:8090/ -v", - "make-example-events": "node ./example/make-example-events.js" + "make-example-events": "node ./example/make-example-events.mjs" }, "keywords": [ "openmct", diff --git a/tests/e2e/yamcs/timeline.e2e.spec.mjs b/tests/e2e/yamcs/timeline.e2e.spec.mjs new file mode 100644 index 00000000..9197020d --- /dev/null +++ b/tests/e2e/yamcs/timeline.e2e.spec.mjs @@ -0,0 +1,61 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +import { pluginFixtures, appActions } from 'openmct-e2e'; +import { postAllEvents } from '../../../example/make-example-events.mjs'; // Updated path and extension +const { test, expect } = pluginFixtures; +const { createDomainObjectWithDefaults, setStartOffset, setFixedTimeMode } = appActions; + +test.describe("Timeline Events in @yamcs", () => { + test.only('Can create a timeline with YAMCS events', async ({ page }) => { + // Go to baseURL + await page.goto("./", { waitUntil: "networkidle" }); + await page.getByLabel('Expand myproject folder').click(); + const eventsTreeItem = page.getByRole('treeitem', { name: /Events/ }); + const eventTimelineView = await createDomainObjectWithDefaults(page, { type: 'Time Strip' }); + + let objectPane = page.locator('.c-object-view'); + await eventsTreeItem.dragTo(objectPane); + await postAllEvents(); + + await setStartOffset(page, { startMins: '05' }); + await setFixedTimeMode(page); + + await page + .getByLabel(eventTimelineView.name) + .getByLabel(/Pressure threshold exceeded/) + .first() + .click(); + await page.getByRole('tab', { name: 'Event' }).click(); + + // ensure the event inspector has the the same event + await expect(page.getByText(/Pressure threshold exceeded/)).toBeVisible(); + + // await page.getByLabel('Expand Events yamcs.events').click(); + // await page.getByLabel('Expand PressureModule yamcs.').click(); + // const pressureModuleInfoTreeItem = page.getByRole('treeitem', { name: /PressureModule: info/ }); + // objectPane = page.locator('.c-object-view'); + // await pressureModuleInfoTreeItem.dragTo(objectPane); + + // await page.pause(); + }); +}); From d44606069730c91e1608b3d2a0de50b55a2e2b9e Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Wed, 18 Dec 2024 15:40:35 +0100 Subject: [PATCH 14/16] fleshed out events --- tests/e2e/yamcs/timeline.e2e.spec.mjs | 51 +++++++++++++++++++++------ 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/tests/e2e/yamcs/timeline.e2e.spec.mjs b/tests/e2e/yamcs/timeline.e2e.spec.mjs index 9197020d..62664f83 100644 --- a/tests/e2e/yamcs/timeline.e2e.spec.mjs +++ b/tests/e2e/yamcs/timeline.e2e.spec.mjs @@ -23,21 +23,21 @@ import { pluginFixtures, appActions } from 'openmct-e2e'; import { postAllEvents } from '../../../example/make-example-events.mjs'; // Updated path and extension const { test, expect } = pluginFixtures; -const { createDomainObjectWithDefaults, setStartOffset, setFixedTimeMode } = appActions; +const { createDomainObjectWithDefaults, setStartOffset, setEndOffset, setFixedTimeMode } = appActions; test.describe("Timeline Events in @yamcs", () => { - test.only('Can create a timeline with YAMCS events', async ({ page }) => { + test('Can create a timeline with YAMCS events', async ({ page }) => { // Go to baseURL await page.goto("./", { waitUntil: "networkidle" }); await page.getByLabel('Expand myproject folder').click(); const eventsTreeItem = page.getByRole('treeitem', { name: /Events/ }); const eventTimelineView = await createDomainObjectWithDefaults(page, { type: 'Time Strip' }); - - let objectPane = page.locator('.c-object-view'); + const objectPane = page.getByLabel(`${eventTimelineView.name} Object View`); await eventsTreeItem.dragTo(objectPane); await postAllEvents(); - await setStartOffset(page, { startMins: '05' }); + await setStartOffset(page, { startMins: '02' }); + await setEndOffset(page, { endMins: '02' }); await setFixedTimeMode(page); await page @@ -50,12 +50,41 @@ test.describe("Timeline Events in @yamcs", () => { // ensure the event inspector has the the same event await expect(page.getByText(/Pressure threshold exceeded/)).toBeVisible(); - // await page.getByLabel('Expand Events yamcs.events').click(); - // await page.getByLabel('Expand PressureModule yamcs.').click(); - // const pressureModuleInfoTreeItem = page.getByRole('treeitem', { name: /PressureModule: info/ }); - // objectPane = page.locator('.c-object-view'); - // await pressureModuleInfoTreeItem.dragTo(objectPane); + await page.getByLabel('Expand Events yamcs.events').click(); + await page.getByLabel('Expand PressureModule yamcs.').click(); + const pressureModuleInfoTreeItem = page.getByRole('treeitem', { name: /PressureModule: info/ }); + await pressureModuleInfoTreeItem.dragTo(objectPane); + + const pressureModuleCriticalTreeItem = page.getByRole('treeitem', { name: /PressureModule: critical/ }); + await pressureModuleCriticalTreeItem.dragTo(objectPane); + + // click on the event inspector tab + await page.getByRole('tab', { name: 'Event' }).click(); + + await expect(page.getByLabel('PressureModule: info Object').getByLabel(/Pressure system check completed/).first()).toBeVisible(); + await page.getByLabel('PressureModule: info Object').getByLabel(/Pressure system check completed/).first().click(); + // ensure the tooltip shows up + await expect( + page.getByRole('tooltip').getByText(/Pressure system check completed/) + ).toBeVisible(); + + // and that event appears in the inspector + await expect( + page.getByLabel('Inspector Views').getByText(/Pressure system check completed/) + ).toBeVisible(); + + // info statements should be hidden in critical severity + await expect(page.getByLabel('PressureModule: critical Object View').getByLabel(/Pressure system check/).first()).toBeHidden(); + await expect(page.getByLabel('PressureModule: critical Object View').getByLabel(/Pressure threshold exceeded/).first()).toBeVisible(); + await page.getByLabel('PressureModule: critical Object View').getByLabel(/Pressure threshold exceeded/).first().click(); + await expect(page.getByLabel('Inspector Views').getByText('Pressure threshold exceeded')).toBeVisible(); + await expect( + page.getByRole('tooltip').getByText(/Pressure threshold exceeded/) + ).toBeVisible(); - // await page.pause(); + // turn on extended lines + await page.getByLabel('Toggle extended event lines overlay for PressureModule: critical').click(); + const overlayLinesContainer = page.locator('.c-timeline__overlay-lines'); + await expect(overlayLinesContainer.locator('.c-timeline__event-line--extended').last()).toBeVisible(); }); }); From 2a80086017d45269684513a8ceb949a861e9bfb1 Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Mon, 6 Jan 2025 09:35:02 +0100 Subject: [PATCH 15/16] fix typo --- src/providers/events.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/providers/events.js b/src/providers/events.js index fb70d208..bf3f278d 100644 --- a/src/providers/events.js +++ b/src/providers/events.js @@ -135,7 +135,7 @@ export function createEventSeverityObjects(openmct, parentEventObject, namespace } export async function getEventSources(url, instance) { - const eventSourceURL = `${url}/api/archive/${instance}/events/sources`; + const eventSourceURL = `${url}api/archive/${instance}/events/sources`; const eventSourcesReply = await fetch(eventSourceURL); if (!eventSourcesReply.ok) { console.error(`🛑 Failed to fetch event sources: ${eventSourcesReply.statusText}`); From c983c0ace71a97eb2cf96b3afb716560313907f7 Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Mon, 6 Jan 2025 09:39:05 +0100 Subject: [PATCH 16/16] support older versions of YAMCS --- src/providers/events.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/providers/events.js b/src/providers/events.js index bf3f278d..96b34049 100644 --- a/src/providers/events.js +++ b/src/providers/events.js @@ -145,7 +145,14 @@ export async function getEventSources(url, instance) { const eventSourcesJson = await eventSourcesReply.json(); - return eventSourcesJson.sources; + if (eventSourcesJson.sources) { + return eventSourcesJson.sources; + } else if (eventSourcesJson.source) { + // backwards compatibility with older YAMCS versions that only have `source` key + return eventSourcesJson.source; + } else { + return []; + } } export function eventShouldBeFiltered(event, options) {