Merge branch 'activity-state-display' of https://github.com/nasa/openmct into activity-states-and-compact-view

This commit is contained in:
Shefali 2024-01-18 09:23:38 -08:00
commit 9e880a5020
15 changed files with 550 additions and 49 deletions

View File

@ -108,6 +108,7 @@
openmct.install(openmct.plugins.Espresso());
openmct.install(openmct.plugins.MyItems());
openmct.install(openmct.plugins.ActivityStates());
openmct.install(
openmct.plugins.PlanLayout({
creatable: true

View File

@ -99,7 +99,13 @@ export default class ObjectAPI {
this.cache = {};
this.interceptorRegistry = new InterceptorRegistry();
this.SYNCHRONIZED_OBJECT_TYPES = ['notebook', 'restricted-notebook', 'plan', 'annotation'];
this.SYNCHRONIZED_OBJECT_TYPES = [
'notebook',
'restricted-notebook',
'plan',
'annotation',
'activity-states'
];
this.errors = {
Conflict: ConflictError

View File

@ -0,0 +1,51 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, 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 { ACTIVITYSTATES_KEY, ACTIVITYSTATES_TYPE } from './createActivityStatesIdentifier.js';
function activityStatesInterceptor(openmct, identifierObject, name) {
const activityStatesModel = {
identifier: identifierObject,
name,
type: ACTIVITYSTATES_TYPE,
activities: {},
location: null
};
return {
appliesTo: (identifier) => {
return identifier.key === ACTIVITYSTATES_KEY;
},
invoke: (identifier, object) => {
if (!object || openmct.objects.isMissing(object)) {
openmct.objects.save(activityStatesModel);
return activityStatesModel;
}
return object;
},
priority: openmct.priority.HIGH
};
}
export default activityStatesInterceptor;

View File

@ -0,0 +1,9 @@
export const ACTIVITYSTATES_KEY = 'activity-states';
export const ACTIVITYSTATES_TYPE = 'activity-states';
export function createActivityStatesIdentifier(namespace = '') {
return {
key: ACTIVITYSTATES_KEY,
namespace
};
}

View File

@ -0,0 +1,42 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, 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 activityStatesInterceptor from './activityStatesInterceptor.js';
import { createActivityStatesIdentifier } from './createActivityStatesIdentifier.js';
const ACTIVITYSTATES_DEFAULT_NAME = 'Activity States';
export default function ActivityStatesPlugin(
name = ACTIVITYSTATES_DEFAULT_NAME,
namespace = '',
priority = undefined
) {
return function install(openmct) {
const identifier = createActivityStatesIdentifier(namespace);
if (priority === undefined) {
priority = openmct.priority.LOW;
}
openmct.objects.addGetInterceptor(activityStatesInterceptor(openmct, identifier, name));
};
}

View File

@ -0,0 +1,89 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, 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 { createOpenMct, resetApplicationState } from 'utils/testing';
import {
ACTIVITYSTATES_KEY,
createActivityStatesIdentifier
} from './createActivityStatesIdentifier.js';
const MISSING_NAME = `Missing: ${ACTIVITYSTATES_KEY}`;
const DEFAULT_NAME = 'Activity States';
const activityStatesIdentifier = createActivityStatesIdentifier();
describe('the plugin', () => {
let openmct;
let missingObj = {
identifier: activityStatesIdentifier,
type: 'unknown',
name: MISSING_NAME
};
describe('with no arguments passed in', () => {
beforeEach((done) => {
openmct = createOpenMct();
openmct.install(openmct.plugins.ActivityStates());
openmct.on('start', done);
openmct.startHeadless();
});
afterEach(() => {
return resetApplicationState(openmct);
});
it('when installed, adds "Activity States"', async () => {
const activityStatesObject = await openmct.objects.get(activityStatesIdentifier);
expect(activityStatesObject.name).toBe(DEFAULT_NAME);
expect(activityStatesObject).toBeDefined();
});
describe('adds an interceptor that returns a "Activity States" model for', () => {
let activityStatesObject;
let mockNotFoundProvider;
let activeProvider;
beforeEach(async () => {
mockNotFoundProvider = {
get: () => Promise.reject(new Error('Not found')),
create: () => Promise.resolve(missingObj),
update: () => Promise.resolve(missingObj)
};
activeProvider = mockNotFoundProvider;
spyOn(openmct.objects, 'getProvider').and.returnValue(activeProvider);
activityStatesObject = await openmct.objects.get(activityStatesIdentifier);
});
it('missing objects', () => {
let idsMatch = openmct.objects.areIdsEqual(
activityStatesObject.identifier,
activityStatesIdentifier
);
expect(activityStatesObject).toBeDefined();
expect(idsMatch).toBeTrue();
});
});
});
});

View File

@ -488,6 +488,7 @@ export default {
},
start: rawActivity.start,
end: rawActivity.end,
description: rawActivity.description,
row: currentRow,
textLines: textLines,
textStart: textStart,
@ -496,7 +497,8 @@ export default {
rectStart: rectX1,
rectEnd: showTextInsideRect ? rectX2 : textStart + textWidth,
rectWidth: rectWidth,
clipPathId: this.getClipPathId(groupName, rawActivity, currentRow)
clipPathId: this.getClipPathId(groupName, rawActivity, currentRow),
id: rawActivity.id
};
activitiesByRow[currentRow].push(activity);
});

View File

@ -20,21 +20,39 @@
at runtime from the About dialog for additional information.
-->
<template>
<div class="c-inspector__properties c-inspect-properties">
<plan-activity-view
<div>
<plan-activity-time-view
v-for="activity in activities"
:key="activity.id"
:key="activity.key"
class="c-inspector__properties c-inspect-properties"
:activity="activity"
:heading="heading"
/>
<plan-activity-properties-view
v-for="activity in activities"
:key="activity.key"
:heading="'Properties'"
class="c-inspector__properties c-inspect-properties"
:activity="activity"
></plan-activity-properties-view>
<plan-activity-status-view
v-if="canPersistState"
:key="activities[0].key"
class="c-inspector__properties c-inspect-properties"
:activity="activities[0]"
:execution-state="persistedActivityStates[activities[0].id]"
:heading="'Activity Status'"
@update-activity-state="persistedActivityState"
/>
</div>
</template>
<script>
import { getPreciseDuration } from 'utils/duration';
import { v4 as uuid } from 'uuid';
import PlanActivityView from './PlanActivityView.vue';
import PlanActivityPropertiesView from './PlanActivityPropertiesView.vue';
import PlanActivityStatusView from './PlanActivityStatusView.vue';
import PlanActivityTimeView from './PlanActivityTimeView.vue';
const propertyLabels = {
start: 'Start DateTime',
@ -44,23 +62,34 @@ const propertyLabels = {
latestEnd: 'Latest End',
gap: 'Gap',
overlap: 'Overlap',
totalTime: 'Total Time'
totalTime: 'Total Time',
description: 'Description'
};
export default {
components: {
PlanActivityView
PlanActivityTimeView,
PlanActivityPropertiesView,
PlanActivityStatusView
},
inject: ['openmct', 'selection'],
data() {
return {
name: '',
activities: [],
selectedActivities: [],
persistedActivityStates: {},
heading: ''
};
},
computed: {
canPersistState() {
return this.selectedActivities.length === 1 && this.activities[0].id;
}
},
mounted() {
this.setFormatters();
this.getPlanData(this.selection);
this.getActivityStates();
this.getActivities();
this.openmct.selection.on('change', this.updateSelection);
this.openmct.time.on('timeSystem', this.setFormatters);
@ -68,8 +97,23 @@ export default {
beforeUnmount() {
this.openmct.selection.off('change', this.updateSelection);
this.openmct.time.off('timeSystem', this.setFormatters);
if (this.stopObservingActivityStatesObject) {
this.stopObservingActivityStatesObject();
}
},
methods: {
async getActivityStates() {
this.activityStatesObject = await this.openmct.objects.get('activity-states');
this.setActivityStates();
this.stopObservingActivityStatesObject = this.openmct.objects.observe(
this.activityStatesObject,
'*',
this.setActivityStates
);
},
setActivityStates() {
this.persistedActivityStates = { ...this.activityStatesObject.activities };
},
setFormatters() {
let timeSystem = this.openmct.time.timeSystem();
this.timeFormatter = this.openmct.telemetry.getValueFormatter({
@ -86,6 +130,7 @@ export default {
if (selectionItem[0].context.type === 'activity') {
const activity = selectionItem[0].context.activity;
if (activity) {
activity.key = activity.id ?? activity.name;
this.selectedActivities.push(activity);
}
}
@ -104,7 +149,9 @@ export default {
this.activities.splice(0);
this.selectedActivities.forEach((selectedActivity, index) => {
const activity = {
id: uuid(),
id: selectedActivity.id,
key: selectedActivity.key,
timeProperties: {
start: {
label: propertyLabels.start,
value: this.formatTime(selectedActivity.start)
@ -117,7 +164,16 @@ export default {
label: propertyLabels.duration,
value: this.formatDuration(selectedActivity.end - selectedActivity.start)
}
}
};
if (selectedActivity.description) {
activity.metadata = {
description: {
label: propertyLabels.description,
value: selectedActivity.description
}
};
}
this.activities[index] = activity;
});
},
@ -141,6 +197,8 @@ export default {
let latestEnd;
let gap;
let overlap;
let id;
let key;
//Sort by start time
let selectedActivities = this.selectedActivities.sort(this.sortFn);
@ -159,6 +217,8 @@ export default {
earliestStart = Math.min(earliestStart, selectedActivity.start);
latestEnd = Math.max(latestEnd, selectedActivity.end);
} else {
id = selectedActivity.id;
key = selectedActivity.id ?? selectedActivity.name;
earliestStart = selectedActivity.start;
latestEnd = selectedActivity.end;
}
@ -166,7 +226,9 @@ export default {
let totalTime = latestEnd - earliestStart;
const activity = {
id: uuid(),
id,
key,
timeProperties: {
earliestStart: {
label: propertyLabels.earliestStart,
value: this.formatTime(earliestStart)
@ -175,21 +237,22 @@ export default {
label: propertyLabels.latestEnd,
value: this.formatTime(latestEnd)
}
}
};
if (gap) {
activity.gap = {
activity.timeProperties.gap = {
label: propertyLabels.gap,
value: this.formatDuration(gap)
};
} else if (overlap) {
activity.overlap = {
activity.timeProperties.overlap = {
label: propertyLabels.overlap,
value: this.formatDuration(overlap)
};
}
activity.totalTime = {
activity.timeProperties.totalTime = {
label: propertyLabels.totalTime,
value: this.formatDuration(totalTime)
};
@ -201,6 +264,11 @@ export default {
},
formatTime(time) {
return this.timeFormatter.format(time);
},
persistedActivityState(data) {
const { key, executionState } = data;
const activitiesPath = `activities.${key}`;
this.openmct.objects.mutate(this.activityStatesObject, activitiesPath, executionState);
}
}
};

View File

@ -0,0 +1,82 @@
<!--
Open MCT, Copyright (c) 2014-2023, 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.
-->
<template>
<div>
<div v-if="properties.length" class="u-contents">
<div class="c-inspect-properties__header">{{ heading }}</div>
<ul v-for="property in properties" :key="property.id" class="c-inspect-properties__section">
<activity-property :label="property.label" :value="property.value" />
</ul>
</div>
</div>
</template>
<script>
import ActivityProperty from './ActivityProperty.vue';
export default {
components: {
ActivityProperty
},
props: {
activity: {
type: Object,
required: true
},
heading: {
type: String,
required: true
}
},
data() {
return {
properties: []
};
},
mounted() {
this.setProperties();
},
methods: {
setProperties() {
if (!this.activity.metadata) {
return;
}
Object.keys(this.activity.metadata).forEach((key) => {
if (this.activity.metadata[key].label) {
const label = this.activity.metadata[key].label;
const value = String(this.activity.metadata[key].value);
const id = this.activity.id;
this.properties[this.properties.length] = {
id,
label,
value
};
}
});
console.log(this.activity.metadata, this.properties);
}
}
};
</script>

View File

@ -0,0 +1,114 @@
<!--
Open MCT, Copyright (c) 2014-2023, 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.
-->
<template>
<div>
<div class="u-contents">
<div class="c-inspect-properties__header">{{ heading }}</div>
<form name="activityStatus">
<select v-model="currentStatusKey" name="setActivityStatus" @change="changeActivityStatus">
<option v-for="status in activityStates" :key="status.key" :value="status.label">
{{ status.label }}
</option>
</select>
</form>
</div>
</div>
</template>
<script>
const activityStates = [
{
key: '',
label: '- Select Activity Status -'
},
{
key: 'active',
label: 'In progress'
},
{
key: 'completed',
label: 'Completed'
},
{
key: 'aborted',
label: 'Aborted'
},
{
key: 'cancelled',
label: 'Cancelled'
},
{
key: 'notStarted',
label: 'Not started'
}
];
export default {
props: {
activity: {
type: Object,
required: true
},
executionState: {
type: String,
default() {
return '';
}
},
heading: {
type: String,
required: true
}
},
emits: ['updateActivityState'],
data() {
return {
activityStates: activityStates,
currentStatusKey: activityStates[0].key
};
},
watch: {
executionState() {
this.setActivityStatus();
}
},
mounted() {
this.setActivityStatus();
},
methods: {
setActivityStatus() {
this.currentStatusKey = this.executionState;
},
changeActivityStatus() {
if (this.currentStatusKey === '') {
return;
}
this.activity.executionState = this.currentStatusKey;
this.$emit('updateActivityState', {
key: this.activity.id,
executionState: this.currentStatusKey
});
}
}
};
</script>

View File

@ -21,6 +21,7 @@
-->
<template>
<div>
<div v-if="timeProperties.length" class="u-contents">
<div class="c-inspect-properties__header">
{{ heading }}
@ -33,11 +34,10 @@
<activity-property :label="timeProperty.label" :value="timeProperty.value" />
</ul>
</div>
</div>
</template>
<script>
import { v4 as uuid } from 'uuid';
import ActivityProperty from './ActivityProperty.vue';
export default {
@ -64,13 +64,14 @@ export default {
},
methods: {
setProperties() {
Object.keys(this.activity).forEach((key) => {
if (this.activity[key].label) {
const label = this.activity[key].label;
const value = String(this.activity[key].value);
Object.keys(this.activity.timeProperties).forEach((key) => {
if (this.activity.timeProperties[key].label) {
const label = this.activity.timeProperties[key].label;
const value = String(this.activity.timeProperties[key].value);
const id = this.activity.id;
this.timeProperties[this.timeProperties.length] = {
id: uuid(),
id,
label,
value
};

View File

@ -45,6 +45,10 @@ export function getValidatedData(domainObject) {
groupActivity.end = activity[sourceMap.end];
}
if (sourceMap.id) {
groupActivity.id = activity[sourceMap.id];
}
if (!mappedJson[groupIdKey]) {
mappedJson[groupIdKey] = [];
}

View File

@ -27,6 +27,7 @@ import ExampleUser from '../../example/exampleUser/plugin.js';
import ExampleFaultSource from '../../example/faultManagement/exampleFaultSource.js';
import GeneratorPlugin from '../../example/generator/plugin.js';
import ExampleImagery from '../../example/imagery/plugin.js';
import ActivityStatesPlugin from './activityStates/plugin.js';
import AutoflowPlugin from './autoflow/AutoflowTabularPlugin.js';
import BarChartPlugin from './charts/bar/plugin.js';
import ScatterPlotPlugin from './charts/scatter/plugin.js';
@ -101,6 +102,7 @@ plugins.LocalTimeSystem = LocalTimeSystem;
plugins.RemoteClock = RemoteClock;
plugins.MyItems = MyItems;
plugins.ActivityStates = ActivityStatesPlugin;
plugins.StaticRootPlugin = StaticRootPlugin;

View File

@ -34,6 +34,7 @@
:header-items="headerItems"
:default-sort="defaultSort"
class="sticky"
@item-selection-changed="setSelectionForActivity"
/>
</div>
</template>
@ -548,6 +549,29 @@ export default {
setEditState(isEditing) {
this.isEditing = isEditing;
this.setViewFromConfig(this.domainObject.configuration);
},
setSelectionForActivity(activity, element) {
const multiSelect = false;
this.openmct.selection.select(
[
{
element: element,
context: {
type: 'activity',
activity: activity
}
},
{
element: this.openmct.layout.$refs.browseObject.$el,
context: {
item: this.domainObject,
supportsMultiSelect: false
}
}
],
multiSelect
);
}
}
};

View File

@ -43,6 +43,7 @@
:key="item.key"
:item="item"
:item-properties="itemProperties"
@click="itemSelected(item, $event)"
/>
</tbody>
</table>
@ -86,6 +87,7 @@ export default {
}
}
},
emits: ['itemSelectionChanged'],
data() {
let sortBy = this.defaultSort.property;
let ascending = this.defaultSort.defaultDirection;
@ -156,6 +158,10 @@ export default {
})
);
}
},
itemSelected(item, event) {
event.stopPropagation();
this.$emit('itemSelectionChanged', item, event.currentTarget);
}
}
};