Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
eeb4f99
add missing licensing comment
davetsay Apr 15, 2024
31be2cb
use vue component naming convention
davetsay Apr 15, 2024
c8f8a09
script, template, style
davetsay Apr 16, 2024
f56a8a1
Revert "script, template, style"
davetsay Apr 16, 2024
ff605a0
create useTimeSystem composable
davetsay Apr 16, 2024
10457a5
Merge branch 'master' into time-conductor-options
davetsay Apr 17, 2024
eeda4c6
forgot to push useTimeSystem
davetsay Apr 17, 2024
d0a7763
extend useTimeSystem to return reactive formatters
davetsay Apr 17, 2024
a11fcc3
return reactive isUTCBased
davetsay Apr 17, 2024
5adc86c
add useTimeMode composable
davetsay Apr 17, 2024
15126fd
add useTimeBounds composable
davetsay Apr 17, 2024
e624821
add useClockOffsets composable
davetsay Apr 18, 2024
00c9d29
WIP change replication in time api calls to use composables
davetsay Apr 19, 2024
2c55945
missed file in commit
davetsay Apr 19, 2024
4f37d95
use composables for conductor mode
davetsay May 1, 2024
3788a13
create useClock composable
davetsay May 14, 2024
23e12fa
whoops. mistook prettier red squiggly for code not used red squiggly.
davetsay May 14, 2024
bd1c7d9
vue naming convention
davetsay May 14, 2024
852ee74
change composables to allow for timeContexts
davetsay May 16, 2024
11adbd2
change timeContexts to be reactive to objectPath
davetsay May 17, 2024
0776003
reactivity and non fixes
davetsay May 18, 2024
5dba73e
use `shallowRef` for clock object
davetsay May 21, 2024
1bb49ea
WIP: get independent time contexts working again
davetsay May 22, 2024
9a57792
vue component naming conventions
davetsay May 22, 2024
e479c91
WIP: mostly fixed independent time conductor
davetsay May 22, 2024
9e56a22
provide timecontext even if its the global/timeapi for shared components
davetsay Jun 5, 2024
0168f92
incorporate provided timecontext into independant time conductor comp…
davetsay Jun 6, 2024
a029982
better jsdoc message
davetsay Jun 6, 2024
0254367
fix missing clock on independent time mode change
davetsay Jun 6, 2024
0cbdbc6
switch to reactive formatter for zoom
davetsay Jun 6, 2024
f349390
use reactive timecontext in fixed inputs
davetsay Jun 6, 2024
1aa3097
use reactive clock props
davetsay Jun 11, 2024
1d77368
edit for clarity
davetsay Jul 3, 2024
7ace3d0
remove unused injection
davetsay Jul 3, 2024
f1cf12d
change ITC to use composables
davetsay Jul 3, 2024
b3c99ae
update to use composables
davetsay Jul 3, 2024
fbf145c
rename because need both datetime and time inputs
davetsay Jul 3, 2024
eb314a6
make message universal to all timeSystems
davetsay Jul 11, 2024
9924f53
only change time options if independent conductor enabled
davetsay Jul 11, 2024
9065158
make date time only for supporting time systems
davetsay Jul 11, 2024
e2092a7
fix css for bounds single inputs
davetsay Jul 12, 2024
3922ade
time formats can specify date/time delimiters
davetsay Jul 12, 2024
5037254
move history out of form
davetsay Aug 21, 2024
6db96df
handle toggle independent time
davetsay Aug 21, 2024
760ca40
Merge branch 'master' into time-conductor-options-4.0.0
davetsay Aug 22, 2024
211272c
v-if logic one component up
davetsay Aug 22, 2024
140184e
add back date selector
davetsay Nov 14, 2024
f91e8c9
Merge branch 'master' into time-conductor-options
davetsay Jul 19, 2025
9b1c26c
source maps
jvigliotta Aug 4, 2025
cf19a15
fix datetime fixed validation
davetsay Aug 5, 2025
8a20c7f
combine time composables into one
davetsay Aug 5, 2025
fde4350
fix single datetime fixed input validation
davetsay Aug 5, 2025
749dde7
correct jsdocs
davetsay Aug 5, 2025
936f61a
Merge branch 'time-conductor-options' into feature/parity
jvigliotta Aug 5, 2025
3c2ad27
debug
jvigliotta Aug 7, 2025
7c3ede1
debuggin
jvigliotta Aug 11, 2025
48bfcdb
adding the correlation telemetry plugin
jvigliotta Aug 28, 2025
2fb3545
Merge branch 'feature/parity' of https://github.com/nasa/openmct into…
jvigliotta Aug 28, 2025
fc0059f
this v-if is not in use
davetsay Sep 3, 2025
4fda26d
optional `formatDate` method can be defined to split date and time
davetsay Sep 4, 2025
0a2a719
fix split date time inputs test
davetsay Sep 5, 2025
7bccaf9
copy and paste into split date time inputs
davetsay Sep 5, 2025
d2f6730
fix validation and reporting of inputs on paste
davetsay Sep 8, 2025
a8f39f5
validate against same start and end fixed time bounds
davetsay Sep 8, 2025
f74ee90
better jsdocs
davetsay Sep 9, 2025
5127bcd
remove deprecated file
davetsay Sep 10, 2025
6d7ba83
vue component name linting
davetsay Sep 10, 2025
37dde0b
Merge branch 'master' into time-conductor-options
davetsay Sep 10, 2025
3cdb445
fix e2e test label locator
davetsay Sep 11, 2025
65947d7
more specific locator
davetsay Sep 11, 2025
bf9e328
pull parent from object path for locator field if it does not exist
jvigliotta Sep 18, 2025
fe0c414
more gooder
jvigliotta Sep 18, 2025
f597496
debuggin correlation telem plugin
jvigliotta Sep 18, 2025
00c3d0c
add domainObject names to the metadata names
jvigliotta Sep 18, 2025
40ca29d
trying to fix a weird issue crashing the browser (maybe a change watc…
jvigliotta Sep 18, 2025
2c8a305
trying to fix a weird issue crashing the browser (maybe a change watc…
jvigliotta Sep 18, 2025
d86c247
no luck with metadata name of object will have to look at this anothe…
jvigliotta Sep 18, 2025
710f3d7
mergin in latest time conductor changes
jvigliotta Sep 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .webpack/webpack.prod.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ export default merge(common, {
__OPENMCT_ROOT_RELATIVE__: '""'
})
],
devtool: 'source-map'
devtool: 'eval-source-map'
});
2 changes: 1 addition & 1 deletion e2e/appActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,7 @@ async function setFixedIndependentTimeConductorBounds(page, { start, end }) {
await page.getByLabel('Enable Independent Time Conductor').click();

// Bring up the time conductor popup
await page.getByLabel('Independent Time Conductor Settings').click();
await page.getByLabel('Independent Time Conductor Panel').click();
await expect(page.getByLabel('Time Conductor Options')).toBeInViewport();
await _setTimeBounds(page, start, end);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,9 @@ test.describe('Snapshot Container tests', () => {
await page.locator('.ptro-crp-el').click();
await page.locator('.ptro-text-tool-input').fill('...is there life on mars?');
// When working with Painterro, we need to check that the Apply button is hidden after clicking
await page.getByTitle('Apply').click();
await expect(page.getByTitle('Apply')).toBeHidden();
const painterroApplyButton = page.locator('.ptro-text-tool-buttons').getByTitle('Apply');
await painterroApplyButton.click();
await expect(painterroApplyButton).toBeHidden();

// Save and exit annotation window
await page.getByRole('button', { name: 'Save' }).click();
Expand All @@ -130,8 +131,9 @@ test.describe('Snapshot Container tests', () => {
await page.locator('.ptro-crp-el').click();
await page.locator('.ptro-text-tool-input').fill('...is there life on mars?');
// When working with Painterro, we need to check that the Apply button is hidden after clicking
await page.getByTitle('Apply').click();
await expect(page.getByTitle('Apply')).toBeHidden();
const painterroApplyButton = page.locator('.ptro-text-tool-buttons').getByTitle('Apply');
await painterroApplyButton.click();
await expect(painterroApplyButton).toBeHidden();

// Save and exit annotation window
await page.getByRole('button', { name: 'Save' }).click();
Expand Down
7 changes: 6 additions & 1 deletion src/api/forms/components/controls/LocatorField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<template>
<MctTree
:is-selector-tree="true"
:initial-selection="model.parent"
:initial-selection="initialSelection"
@tree-item-selection="handleItemSelection"
/>
</template>
Expand All @@ -43,6 +43,11 @@ export default {
}
},
emits: ['on-change'],
computed: {
initialSelection() {
return this.model.parent || this.model.value?.[0];
}
},
methods: {
handleItemSelection(item) {
const data = {
Expand Down
12 changes: 10 additions & 2 deletions src/api/time/IndependentTimeContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,16 @@ class IndependentTimeContext extends TimeContext {
return this.upstreamTimeContext.setMode(...arguments);
}

if (mode === MODES.realtime && this.activeClock === undefined) {
throw `Unknown clock. Has a clock been registered with 'addClock'?`;
if (mode === MODES.realtime) {
// TODO: This should probably happen up front in creating an independent time context
// TODO: not just in time every time setMode is called
if (this.activeClock === undefined) {
this.activeClock = this.globalTimeContext.getClock();
}

if (this.activeClock === undefined) {
throw `Unknown clock. Has a clock been registered with 'addClock'?`;
}
}

if (mode !== this.mode) {
Expand Down
20 changes: 10 additions & 10 deletions src/api/time/TimeAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,33 +135,33 @@ class TimeAPI extends GlobalTimeContext {

/**
* Get or set an independent time context which follows the TimeAPI timeSystem,
* but with different offsets for a given domain object
* @param {string} key The identifier key of the domain object these offsets are set for
* @param {ClockOffsets | TimeConductorBounds} value This maintains a sliding time window of a fixed width that automatically updates
* @param {key | string} clockKey the real time clock key currently in use
* but with different bounds for a given domain object
* @param {string} keyString The keyString identifier of the domain object these offsets are set for
* @param {TimeConductorBounds | ClockOffsets} boundsOrOffsets either bounds if in fixed mode, or offsets if in realtime mode
* @param {string} clockKey the key for the real time clock to use
*/
addIndependentContext(key, value, clockKey) {
let timeContext = this.getIndependentContext(key);
addIndependentContext(keyString, boundsOrOffsets, clockKey) {
let timeContext = this.getIndependentContext(keyString);

//stop following upstream time context since the view has its own
timeContext.resetContext();

if (clockKey) {
timeContext.setClock(clockKey);
timeContext.setMode(REALTIME_MODE_KEY, value);
timeContext.setMode(REALTIME_MODE_KEY, boundsOrOffsets);
} else {
timeContext.setMode(FIXED_MODE_KEY, value);
timeContext.setMode(FIXED_MODE_KEY, boundsOrOffsets);
}

// Also emit the mode in case it's different from the previous time context
timeContext.emit(TIME_CONTEXT_EVENTS.modeChanged, structuredClone(timeContext.getMode()));

// Notify any nested views to update, pass in the viewKey so that particular view can skip getting an upstream context
this.emit('refreshContext', key);
this.emit('refreshContext', keyString);

return () => {
//follow any upstream time context
this.emit('removeOwnContext', key);
this.emit('removeOwnContext', keyString);
};
}

Expand Down
6 changes: 3 additions & 3 deletions src/api/time/TimeContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,10 @@ class TimeContext extends EventEmitter {
valid: false,
message: 'Start and end must be specified as integer values'
};
} else if (bounds.start > bounds.end) {
} else if (bounds.start >= bounds.end) {
return {
valid: false,
message: 'Specified start date exceeds end bound'
message: 'Start bound must be less than end bound'
};
}

Expand Down Expand Up @@ -261,7 +261,7 @@ class TimeContext extends EventEmitter {
} else if (offsets.start >= offsets.end) {
return {
valid: false,
message: 'Specified start offset must be < end offset'
message: 'Start offset must be less than end offset'
};
}

Expand Down
4 changes: 2 additions & 2 deletions src/plugins/charts/scatter/ScatterPlotView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ export default {
const yAxisUnit = yAxisMetadata.units ? `(${yAxisMetadata.units})` : '';

return {
xAxisTitle: `${xAxisMetadata.name || ''} ${xAxisUnit}`,
yAxisTitle: `${yAxisMetadata.name || ''} ${yAxisUnit}`
xAxisTitle: `${xAxis || ''} ${xAxisUnit}`,
yAxisTitle: `${yAxis || ''} ${yAxisUnit}`
};
}
},
Expand Down
228 changes: 228 additions & 0 deletions src/plugins/correlationTelemetryPlugin/plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
const CORRELATOR_TYPE = 'telemetry.correlator';

export default function CorrelationTelemetryPlugin(openmct) {
// eslint-disable-next-line no-shadow
return function install(openmct) {
function getTelemetryObject(idString) {
return openmct.objects.get(idString);
}

function getTelemetry(object, options) {
return openmct.telemetry.request(object, options);
}

openmct.types.addType(CORRELATOR_TYPE, {
name: 'Correlation Telemetry',
description: `Combines telemetry from multiple sources to produce telemetry correlated by timestamp with a given time tolerance.`,
cssClass: 'icon-object',
creatable: true,
initialize: function (obj) {
obj.telemetry = {};
},
form: [
{
key: 'xSource',
name: 'X Axis Source',
control: 'locator',
required: true,
cssClass: 'grows'
},
{
key: 'ySource',
name: 'Y Axis Source',
control: 'locator',
required: true,
cssClass: 'grows'
}
]
});

openmct.telemetry.addProvider({
supportsMetadata: function (domainObject) {
return domainObject.type === CORRELATOR_TYPE;
},
getMetadata: function (domainObject) {
let metadata = {};
metadata.values = openmct.time.getAllTimeSystems().map(function (timeSystem, i) {
return {
name: timeSystem.name,
key: timeSystem.key,
source: timeSystem.source,
format: timeSystem.timeFormat,
hints: { domain: i }
};
});
metadata.values.push({
name: 'X',
key: 'x',
source: 'x',
hints: { xSource: 1, range: 1 }
});
metadata.values.push({
name: 'Y',
key: 'y',
source: 'y',
hints: { ySource: 1, range: 2 }
});
return metadata;
},
supportsRequest: function (domainObject) {
return domainObject.type === CORRELATOR_TYPE;
},
request: function (domainObject, options) {
let telemResults = {};
let telemObject;

const xSourceIdentifier = openmct.objects.makeKeyString(domainObject.xSource[0].identifier);
let xPromise = getTelemetryObject(xSourceIdentifier)
.then((object) => {
telemObject = object;
return getTelemetry(object, options);
})
.then((data) => {
let source = 'x';
telemResults[source] = {
object: telemObject
};
let metadata = openmct.telemetry.getMetadata(telemObject);
let valueMeta = metadata.valuesForHints(['range'])[0];
telemResults[source].coorelatorFormat = openmct.telemetry.getValueFormatter(valueMeta);
telemResults[source].coorelatorFormat = openmct.telemetry.getValueFormatter(valueMeta);
telemResults[source].timestampFormat = openmct.telemetry.getValueFormatter(
metadata.value(options.domain)
);
telemResults[source].data = data;
});

const ySourceIdentifier = openmct.objects.makeKeyString(domainObject.ySource[0].identifier);
let yPromise = getTelemetryObject(ySourceIdentifier)
.then((object) => {
telemObject = object;
return getTelemetry(object, options);
})
.then((data) => {
let source = 'y';
telemResults[source] = {
object: telemObject
};
let metadata = openmct.telemetry.getMetadata(telemObject);
let valueMeta = metadata.valuesForHints(['range'])[0];
telemResults[source].coorelatorFormat = openmct.telemetry.getValueFormatter(valueMeta);
telemResults[source].coorelatorFormat = openmct.telemetry.getValueFormatter(valueMeta);
telemResults[source].timestampFormat = openmct.telemetry.getValueFormatter(
metadata.value(options.domain)
);
telemResults[source].data = data;
});

return Promise.all([xPromise, yPromise]).then(function () {
let results = [];
let xByTime = telemResults.x.data.reduce(function (m, datum) {
m[telemResults.x.timestampFormat.parse(datum)] =
telemResults.x.coorelatorFormat.parse(datum);
return m;
}, {});
telemResults.y.data.forEach(function (datum) {
let timestamp = telemResults.y.timestampFormat.parse(datum);
if (xByTime[timestamp] !== undefined) {
let resultDatum = {
x: xByTime[timestamp],
y: telemResults.y.coorelatorFormat.parse(datum)
};
resultDatum[options.domain] = timestamp;
results.push(resultDatum);
}
});
return results;
});
},
supportsSubscribe: function (domainObject) {
return domainObject.type === CORRELATOR_TYPE;
},
subscribe: function (domainObject, callback) {
let telem = {};
let done = false;
let unsubscribes = [];

function sendUpdate() {
if (done) {
return;
}
if (!telem.y.latest || !telem.x.latest) {
return;
}
if (telem.y.latestTimestamp !== telem.x.latestTimestamp) {
return;
}
let datum = {
x: telem.x.coorelatorFormat.parse(telem.x.latest),
y: telem.y.coorelatorFormat.parse(telem.y.latest)
};
datum[openmct.time.timeSystem().key] = Math.max(
telem.x.latestTimestamp,
telem.y.latestTimestamp
);
delete telem.x.latest;
delete telem.y.latest;
delete telem.x.latestTimestamp;
delete telem.y.latestTimestamp;
callback(datum);
}

const xSourceIdentifier = openmct.objects.makeKeyString(domainObject.xSource[0].identifier);
getTelemetryObject(xSourceIdentifier).then(function (xObject) {
if (done) {
return;
}
telem.x = {
object: xObject
};
let metadata = openmct.telemetry.getMetadata(xObject);
let valueMeta = metadata.valuesForHints(['range'])[0];
telem.x.coorelatorFormat = openmct.telemetry.getValueFormatter(valueMeta);
telem.x.timestampFormat = openmct.telemetry.getValueFormatter(
metadata.value(openmct.time.timeSystem().key)
);
unsubscribes.push(
openmct.telemetry.subscribe(xObject, function (datum) {
telem.x.latest = datum;
telem.x.latestTimestamp = telem.x.timestampFormat.parse(datum);
requestAnimationFrame(sendUpdate);
})
);
});

const ySourceIdentifier = openmct.objects.makeKeyString(domainObject.ySource[0].identifier);
getTelemetryObject(ySourceIdentifier).then(function (yObject) {
if (done) {
return;
}
telem.y = {
object: yObject
};
let metadata = openmct.telemetry.getMetadata(yObject);
let valueMeta = metadata.valuesForHints(['range'])[0];
telem.y.coorelatorFormat = openmct.telemetry.getValueFormatter(valueMeta);
telem.y.timestampFormat = openmct.telemetry.getValueFormatter(
metadata.value(openmct.time.timeSystem().key)
);
unsubscribes.push(
openmct.telemetry.subscribe(yObject, function (datum) {
telem.y.latest = datum;
telem.y.latestTimestamp = telem.y.timestampFormat.parse(datum);
requestAnimationFrame(sendUpdate);
})
);
});

return function unsubscribe() {
done = true;
unsubscribes.forEach(function (u) {
u();
});
unsubscribes = undefined;
};
}
});
};
}
2 changes: 1 addition & 1 deletion src/plugins/plot/axis/XAxis.vue
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export default {
};
});
}

console.log('xKeyOptions', this.xKeyOptions, 'this.xAxis', this.xAxis);
this.xAxisLabel = this.xAxis.get('label');
this.selectedXKeyOptionKey =
this.xKeyOptions.length > 0 ? this.getXKeyOption(xAxisKey).key : xAxisKey;
Expand Down
Loading
Loading