Skip to content

Commit 81c3aef

Browse files
authored
Merge branch 'master' into VIPERGC-1002-operator-polls
2 parents 0949928 + 1e6f04f commit 81c3aef

10 files changed

Lines changed: 137 additions & 26 deletions

File tree

e2e/tests/functional/plugins/comps/comps.e2e.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ test.describe('Comps', () => {
6363
// Check that expressions are marked invalid
6464
await page.getByLabel('Reference Name Input for a').fill('b');
6565
await page.getByText('Current Output').click();
66-
await expect(page.getByText('Invalid: Undefined symbol a')).toBeVisible();
66+
await expect(page.getByText('Undefined variables: a')).toBeVisible();
6767

6868
// Check that test data works
6969
await page.getByPlaceholder('Enter an expression').fill('b*2');

src/plugins/comps/components/CompsView.vue

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
:aria-label="`Reference Name Input for ${parameter.name}`"
7676
type="text"
7777
class="c-input--md"
78-
@change="updateParameters"
78+
@input="updateParameters"
7979
/>
8080
<div v-else class="--em">{{ parameter.name }}</div>
8181
<span class="c-test-datum__string">=</span>
@@ -135,7 +135,7 @@
135135
:aria-label="`Sample Size for ${parameter.name}`"
136136
type="number"
137137
class="c-input--sm c-comps__value"
138-
@change="updateParameters"
138+
@input="updateParameters"
139139
/>
140140
</div>
141141

@@ -155,7 +155,7 @@
155155
:aria-label="`Reference Test Value for ${parameter.name}`"
156156
type="text"
157157
class="c-input--md c-comps__value"
158-
@change="updateTestValue(parameter)"
158+
@input="updateTestValue(parameter)"
159159
/>
160160
</div>
161161
</div>
@@ -175,7 +175,8 @@
175175
v-model="expression"
176176
class="c-comps__expression-value"
177177
placeholder="Enter an expression"
178-
@change="updateExpression"
178+
@input="updateExpression"
179+
@blur="handleExpressionBlur"
179180
></textarea>
180181
<div v-else>
181182
<div class="c-comps__expression-value" aria-label="Expression">
@@ -184,13 +185,19 @@
184185
</div>
185186
<span
186187
v-if="expression && expressionOutput"
187-
class="icon-alert-triangle c-comps__expression-msg --bad"
188+
:class="[
189+
'c-comps__expression-msg',
190+
expressionBlurResult === 'invalid' ? '--bad-strong' : '--bad'
191+
]"
188192
>
189-
Invalid: {{ expressionOutput }}
193+
{{ expressionOutput }}
190194
</span>
191195
<span
192196
v-else-if="expression && !expressionOutput && isEditing"
193-
class="c-comps__expression-msg --good"
197+
:class="[
198+
'c-comps__expression-msg',
199+
expressionBlurResult === 'valid' ? '--good-strong' : '--good'
200+
]"
194201
>
195202
Expression valid
196203
</span>
@@ -199,12 +206,15 @@
199206
</template>
200207

201208
<script setup>
202-
import { evaluate } from 'mathjs';
209+
import { all, create } from 'mathjs';
203210
import { inject, onBeforeMount, onBeforeUnmount, ref, watch } from 'vue';
204211
205212
import ObjectPathString from '../../../ui/components/ObjectPathString.vue';
206213
import CompsManager from '../CompsManager';
207214
215+
const config = {};
216+
const math = create(all, config);
217+
208218
const openmct = inject('openmct');
209219
const domainObject = inject('domainObject');
210220
const compsManagerPool = inject('compsManagerPool');
@@ -215,6 +225,7 @@ const testDataApplied = ref(false);
215225
const parameters = ref(null);
216226
const expression = ref(null);
217227
const expressionOutput = ref(null);
228+
const expressionBlurResult = ref(null); // 'valid' | 'invalid' | null
218229
const outputFormat = ref(null);
219230
const parameterValueOptionsMap = ref({});
220231
const telemetryObjectsMap = ref({});
@@ -295,6 +306,11 @@ onBeforeMount(async () => {
295306
expression.value = compsManager.getExpression();
296307
outputFormat.value = compsManager.getOutputFormat();
297308
applyTestData();
309+
310+
// Set initial blur state if expression is invalid on load
311+
if (expressionOutput.value) {
312+
expressionBlurResult.value = 'invalid';
313+
}
298314
});
299315
300316
onBeforeUnmount(() => {
@@ -387,6 +403,7 @@ function toggleTestData() {
387403
}
388404
389405
function updateExpression() {
406+
expressionBlurResult.value = null; // Reset blur evaluation when typing
390407
openmct.objects.mutate(domainObject, `configuration.comps.expression`, expression.value);
391408
compsManager.setDomainObject(domainObject);
392409
applyTestData();
@@ -428,7 +445,7 @@ function applyTestData() {
428445
}
429446
430447
try {
431-
const testOutput = evaluate(expression.value, scope);
448+
const testOutput = validateAndEvaluateExpression(expression.value, scope);
432449
const formattedData = getValueFormatter().format(testOutput);
433450
currentTestOutput.value = formattedData;
434451
expressionOutput.value = null;
@@ -438,6 +455,33 @@ function applyTestData() {
438455
}
439456
}
440457
458+
function validateAndEvaluateExpression(evalExpression, scope) {
459+
const parsed = math.parse(evalExpression);
460+
const dependencies = parsed
461+
.filter((node) => node.isSymbolNode || node.isFunctionNode)
462+
.map((node) => node.name);
463+
464+
const uniqueDeps = Array.from(new Set(dependencies));
465+
466+
// check if any variables are predefined mathjs functions or constants
467+
// then check if any variables are defined in the scope
468+
// anything else is considered undefined and will throw an error
469+
const undefinedVars = uniqueDeps.filter((dep) => {
470+
if (math[dep] !== undefined) {
471+
return false;
472+
}
473+
if (Object.prototype.hasOwnProperty.call(scope, dep)) {
474+
return false;
475+
}
476+
return true;
477+
});
478+
479+
if (undefinedVars.length > 0) {
480+
throw new Error(`Undefined variables: ${undefinedVars.join(', ')}`);
481+
}
482+
return math.evaluate(evalExpression, scope);
483+
}
484+
441485
function telemetryProcessor(data) {
442486
if (testDataApplied.value) {
443487
return;
@@ -463,4 +507,17 @@ function reload() {
463507
function clearData() {
464508
currentCompOutput.value = null;
465509
}
510+
511+
function handleExpressionBlur() {
512+
applyTestData();
513+
514+
// Set blur result based on expressionOutput after evaluation
515+
if (expressionOutput.value) {
516+
expressionBlurResult.value = 'invalid';
517+
} else if (expression.value && !expressionOutput.value) {
518+
expressionBlurResult.value = 'valid';
519+
} else {
520+
expressionBlurResult.value = null;
521+
}
522+
}
466523
</script>

src/plugins/comps/components/comps.scss

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,6 @@
1919
* this source code distribution or the Licensing information page available
2020
* at runtime from the About dialog for additional information.
2121
*****************************************************************************/
22-
@mixin expressionMsg($fg, $bg) {
23-
$op: 0.4;
24-
color: rgba($fg, $op * 1.5);
25-
background: rgba($bg, $op);
26-
}
27-
2822
.c-comps {
2923
display: flex;
3024
flex-direction: column;
@@ -126,25 +120,49 @@
126120
}
127121

128122
&__expression-msg {
129-
@include expressionMsg($colorOkFg, $colorOk);
130-
border-radius: $basicCr;
123+
border-radius: $smallCr;
131124
display: flex; // Creates hanging indent from :before icon
125+
align-items: center;
132126
padding: $interiorMarginSm $interiorMarginLg $interiorMarginSm $interiorMargin;
127+
margin-top: $interiorMarginSm;
133128
max-width: max-content;
129+
opacity: 0.8;
134130

135131
&:before {
136-
content: $glyph-icon-check;
137132
font-family: symbolsfont;
133+
font-size: 0.85em;
138134
margin-right: $interiorMarginSm;
139135
}
140136

137+
&.--good,
141138
&.--bad {
142-
@include expressionMsg($colorErrorFg, $colorError);
139+
$t: 500ms;
140+
transition: background $t, color $t;
141+
}
143142

143+
&.--good,
144+
&.--good-strong {
145+
&:before {
146+
content: $glyph-icon-check;
147+
}
148+
}
149+
150+
&.--bad,
151+
&.--bad-strong {
144152
&:before {
145153
content: $glyph-icon-alert-triangle;
146154
}
147155
}
156+
157+
&.--good-strong {
158+
color: $colorOkFg;
159+
background: rgba($colorOk, 0.5);
160+
}
161+
162+
&.--bad-strong {
163+
color: $colorAlertFg;
164+
background: rgba($colorAlert, 0.5);
165+
}
148166
}
149167

150168
.--em {

src/styles/_constants-espresso.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ $colorCommand: #3693bd;
170170
$colorCommandFg: #fff;
171171
$colorInfo: #198290;
172172
$colorInfoFg: #fff;
173-
$colorOk: #33cc33;
173+
$colorOk: #1f851f;
174174
$colorOkFg: #fff;
175175
$colorFilterBg: #44449c;
176176
$colorFilterFg: #8984e9;

src/styles/_constants.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,4 +338,4 @@ $bg-icon-telemetry-aggregate: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns
338338
$bg-icon-trash: url("data:image/svg+xml;charset=UTF-8,%3csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='512px' height='512px' viewBox='0 0 512 512' enable-background='new 0 0 512 512' xml:space='preserve'%3e%3cpath d='M416,64h-96.18V32c0-17.6-14.4-32-32-32h-64c-17.6,0-32,14.4-32,32v32H96c-52.8,0-96,36-96,80s0,80,0,80h32v192 c0,52.8,43.2,96,96,96h256c52.8,0,96-43.2,96-96V224h32c0,0,0-36,0-80S468.8,64,416,64z M160,416H96V224h64V416z M288,416h-64V224 h64V416z M416,416h-64V224h64V416z'/%3e%3c/svg%3e");
339339
$bg-icon-eye-open: url("data:image/svg+xml;charset=UTF-8,%3csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 512 512' style='enable-background:new 0 0 512 512;' xml:space='preserve'%3e%3cstyle type='text/css'%3e .st0%7bfill:%2300A14B;%7d %3c/style%3e%3ctitle%3eicon-eye-open-v2%3c/title%3e%3cg%3e%3cpath class='st0' d='M256,58.2c-122.9,0-226.1,84-255.4,197.8C29.9,369.7,133.1,453.8,256,453.8s226.1-84,255.4-197.8 C482.1,142.3,378.9,58.2,256,58.2z M414.6,294.2c-11.3,17.2-25.3,32.4-41.5,45.2c-16.4,12.9-34.5,22.8-54,29.7 c-20.2,7.1-41.4,10.7-63,10.7s-42.9-3.6-63-10.7c-19.5-6.9-37.7-16.9-54-29.7c-16.2-12.8-30.2-27.9-41.5-45.2 c-7.9-12-14.4-24.8-19.3-38.2c5-13.4,11.5-26.2,19.3-38.2c11.3-17.2,25.3-32.4,41.5-45.2c16.4-12.9,34.5-22.8,54-29.7 c20.2-7.1,41.4-10.7,63-10.7s42.9,3.6,63,10.7c19.5,6.9,37.7,16.9,54,29.7c16.2,12.8,30.2,27.9,41.5,45.2 c7.9,12,14.4,24.8,19.3,38.2C429,269.4,422.5,282.2,414.6,294.2z'/%3e%3ccircle class='st0' cx='256' cy='256' r='96'/%3e%3c/g%3e%3c/svg%3e");
340340
$bg-icon-camera: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3ctitle%3eicon-camera-v2%3c/title%3e%3cpath d='M448,128H384L320,0H192L128,128H64A64.2,64.2,0,0,0,0,192V448a64.2,64.2,0,0,0,64,64H448a64.2,64.2,0,0,0,64-64V192A64.2,64.2,0,0,0,448,128ZM256,432A128,128,0,1,1,384,304,128,128,0,0,1,256,432Z'/%3e%3c/svg%3e");
341-
$bg-icon-derived-telemetry: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M66.1 166c20.2 24.3 35.1 54.6 44 75.7 10 23.6 21.7 44 33.1 57.7 11.1 13.3 18.5 16.3 20.2 16.3s9.1-3 20.2-16.3c11.4-13.7 23.1-34.2 33.1-57.7 8.9-21.1 23.8-51.4 44-75.7 23.3-28.1 48.7-42.3 75.6-42.3s52.2 14.2 75.6 42.3c20.2 24.3 35.1 54.6 44 75.7 10 23.6 21.7 44 33.1 57.7 11.1 13.3 18.5 16.3 20.2 16.3s1.6-.3 3.2-1.1v-58.9c-.2-141.3-114.9-256-256.2-256H.2v124.6c23.3 3 45.4 17 66 41.7Z'/%3e%3cpath d='M509 387.7c-26.8 0-52.2-14.2-75.6-42.3-20.2-24.3-35.1-54.6-44-75.7-10-23.6-21.7-44-33.1-57.7-11.1-13.3-18.5-16.3-20.2-16.3s-9.1 3-20.2 16.3c-11.4 13.7-23.1 34.2-33.1 57.7-8.9 21.1-23.8 51.4-44 75.7-23.3 28.1-48.7 42.3-75.6 42.3s-52.2-14.2-75.6-42.3c-20.2-24.3-35.1-54.6-44-75.7-10-23.6-21.7-44-33.1-57.7-4.1-4.9-7.6-8.4-10.6-10.8v54.5c.3 141.4 114.9 256 256.3 256h256V387.6H509Z'/%3e%3c/svg%3e");
341+
$bg-icon-derived-telemetry: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M66.1 166c20.2 24.3 35.1 54.6 44 75.7 10 23.6 21.7 44 33.1 57.7 11.1 13.3 18.5 16.3 20.2 16.3s9.1-3 20.2-16.3c11.4-13.7 23.1-34.2 33.1-57.7 8.9-21.1 23.8-51.4 44-75.7 23.3-28.1 48.7-42.3 75.6-42.3s52.2 14.2 75.6 42.3c20.2 24.3 35.1 54.6 44 75.7 10 23.6 21.7 44 33.1 57.7 11.1 13.3 18.5 16.3 20.2 16.3s1.6-.3 3.2-1.1v-58.9c-.2-141.3-114.9-256-256.2-256H.2v124.6c23.3 3 45.4 17 66 41.7Z'/%3e%3cpath d='M509 387.7c-26.8 0-52.2-14.2-75.6-42.3-20.2-24.3-35.1-54.6-44-75.7-10-23.6-21.7-44-33.1-57.7-11.1-13.3-18.5-16.3-20.2-16.3s-9.1 3-20.2 16.3c-11.4 13.7-23.1 34.2-33.1 57.7-8.9 21.1-23.8 51.4-44 75.7-23.3 28.1-48.7 42.3-75.6 42.3s-52.2-14.2-75.6-42.3c-20.2-24.3-35.1-54.6-44-75.7-10-23.6-21.7-44-33.1-57.7-4.1-4.9-7.6-8.4-10.6-10.8v54.5c.3 141.4 114.9 256 256.3 256h256V387.6H509Z'/%3e%3c/svg%3e");

src/styles/_glyphs.scss

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -708,13 +708,17 @@
708708
@include glyphBefore($glyph-icon-timelist);
709709
}
710710

711+
.icon-plot-scatter {
712+
@include glyphBefore($glyph-icon-plot-scatter);
713+
}
714+
711715
.icon-notebook-shift-log {
712716
@include glyphBefore($glyph-icon-notebook-shift-log);
713717
}
714718

715719

716720
.icon-derived-telemetry {
717-
@include glyphBefore($glyph-icon-derived-telemetry);
721+
@include glyphBefore($glyph-icon-derived-telemetry);
718722
}
719723

720724
/************************** 12 PX CLASSES */

src/styles/fonts/Open MCT Symbols 16px.json

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"metadata": {
3-
"name": "Open MCT Symbols 16px",
3+
"name": "Open MCT Symbols 16px GOOD Icomoon Derived Telem",
44
"lastOpened": 0,
5-
"created": 1726681576505
5+
"created": 1772065278484
66
},
77
"iconSets": [
88
{
@@ -1350,13 +1350,21 @@
13501350
"prevSize": 16,
13511351
"code": 60209,
13521352
"tempChar": ""
1353+
},
1354+
{
1355+
"order": 220,
1356+
"id": 188,
1357+
"name": "icon-derived-telemetry",
1358+
"prevSize": 16,
1359+
"code": 60210,
1360+
"tempChar": ""
13531361
}
13541362
],
13551363
"id": 0,
13561364
"metadata": {
13571365
"name": "Open MCT Symbols 16px",
13581366
"importSize": {
1359-
"width": 576,
1367+
"width": 512,
13601368
"height": 512
13611369
},
13621370
"designer": "Charles Hacskaylo"
@@ -4369,6 +4377,29 @@
43694377
{}
43704378
]
43714379
}
4380+
},
4381+
{
4382+
"id": 189,
4383+
"paths": [
4384+
"M132.2 332c40.4 48.6 70.2 109.2 88 151.4 20 47.2 43.4 88 66.2 115.4 22.2 26.6 37 32.6 40.4 32.6s18.2-6 40.4-32.6c22.8-27.4 46.2-68.4 66.2-115.4 17.8-42.2 47.6-102.8 88-151.4 46.6-56.2 97.4-84.6 151.2-84.6s104.4 28.4 151.2 84.6c40.4 48.6 70.2 109.2 88 151.4 20 47.2 43.4 88 66.2 115.4 22.2 26.6 37 32.6 40.4 32.6s3.2-0.6 6.4-2.2v-117.8c-0.4-282.6-229.8-512-512.4-512h-512v249.2c46.6 6 90.8 34 132 83.4z",
4385+
"M1018 775.4c-53.6 0-104.4-28.4-151.2-84.6-40.4-48.6-70.2-109.2-88-151.4-20-47.2-43.4-88-66.2-115.4-22.2-26.6-37-32.6-40.4-32.6s-18.2 6-40.4 32.6c-22.8 27.4-46.2 68.4-66.2 115.4-17.8 42.2-47.6 102.8-88 151.4-46.6 56.2-97.4 84.6-151.2 84.6s-104.4-28.4-151.2-84.6c-40.4-48.6-70.2-109.2-88-151.4-20-47.2-43.4-88-66.2-115.4-8.2-9.8-15.2-16.8-21.2-21.6v109c0.6 282.8 229.8 512 512.6 512h512v-248.2c-2.2 0-4.2 0-6.4 0z"
4386+
],
4387+
"attrs": [
4388+
{},
4389+
{}
4390+
],
4391+
"isMulticolor": false,
4392+
"isMulticolor2": false,
4393+
"grid": 16,
4394+
"tags": [
4395+
"icon-derived-telemetry-v3"
4396+
],
4397+
"colorPermutations": {
4398+
"12552552551": [
4399+
{},
4400+
{}
4401+
]
4402+
}
43724403
}
43734404
],
43744405
"invisible": false,

src/styles/fonts/Open-MCT-Symbols-16px.svg

Lines changed: 1 addition & 0 deletions
Loading
304 Bytes
Binary file not shown.
304 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)