Plan state (#4310)

* Add plan state indicators
* Changes to simplify timeline view
* Styling for draft plans
* Adds status to Plan.vue
* Adds tests
* Mods for #4309
- New font and icomoon JSON file - when merging, please override with this version if any conflicts!
- New glyph and bg-icon svg style for plan;
- Updated glyph and bg-icon svg style for timestrip;
- Modified visual approach, glyph, color for `is-status--draft`;
- Updated icon usage for Plan views;
- Updated description for Plan and Timestrip views;

Co-authored-by: Andrew Henry <akhenry@gmail.com>
Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
Co-authored-by: Khalid Adil <khalidadil29@gmail.com>
Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
Co-authored-by: Scott Bell <scott@traclabs.com>
Co-authored-by: Michael Rogers <michael@mhrogers.com>
This commit is contained in:
Shefali Joshi
2021-11-29 15:24:11 -08:00
committed by GitHub
parent 25b3431131
commit 0b02b083c3
22 changed files with 381 additions and 188 deletions

View File

@ -65,6 +65,30 @@
}
}
&.is-status--current {
.is-status__indicator {
display: block;
&:before {
color: $colorFilter;
content: $glyph-icon-asterisk;
font-family: symbolsfont;
}
}
}
&.is-status--draft {
.is-status__indicator {
display: block;
&:before {
color: $colorStatusAlert;
content: $glyph-icon-draft;
font-family: symbolsfont;
}
}
}
&[class*='is-status--missing'],
&[class*='is-status--suspect']{
[class*='__type-icon'],

View File

@ -71,7 +71,9 @@ describe("the plugin", () => {
beforeEach(async () => {
mockMissingProvider = {
get: () => Promise.resolve(missingObj)
get: () => Promise.resolve(missingObj),
create: () => Promise.resolve(missingObj),
update: () => Promise.resolve(missingObj)
};
activeProvider = mockMissingProvider;

View File

@ -102,21 +102,26 @@ describe('the plugin', () => {
expect(result.identifier.key).toEqual(mockDomainObject.identifier.key);
});
it('creates an object', async () => {
const result = await openmct.objects.save(mockDomainObject);
expect(provider.create).toHaveBeenCalled();
expect(result).toBeTrue();
it('creates an object', (done) => {
openmct.objects.save(mockDomainObject).then((result) => {
expect(provider.create).toHaveBeenCalled();
expect(result).toBeTrue();
done();
});
});
xit('updates an object', async () => {
const result = await openmct.objects.save(mockDomainObject);
expect(result).toBeTrue();
expect(provider.create).toHaveBeenCalled();
//Set modified timestamp it detects a change and persists the updated model.
mockDomainObject.modified = Date.now();
const updatedResult = await openmct.objects.save(mockDomainObject);
expect(updatedResult).toBeTrue();
expect(provider.update).toHaveBeenCalled();
it('updates an object', (done) => {
openmct.objects.save(mockDomainObject).then((result) => {
expect(result).toBeTrue();
expect(provider.create).toHaveBeenCalled();
//Set modified timestamp it detects a change and persists the updated model.
mockDomainObject.modified = mockDomainObject.persisted + 1;
openmct.objects.save(mockDomainObject).then((updatedResult) => {
expect(updatedResult).toBeTrue();
expect(provider.update).toHaveBeenCalled();
done();
});
});
});
});
describe('batches requests', () => {

View File

@ -102,6 +102,8 @@ export default {
this.setTimeContext();
this.resizeTimer = setInterval(this.resize, RESIZE_POLL_INTERVAL);
this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.observeForChanges);
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.setStatus);
this.status = this.openmct.status.get(this.domainObject.identifier);
},
beforeDestroy() {
clearInterval(this.resizeTimer);
@ -109,6 +111,10 @@ export default {
if (this.unlisten) {
this.unlisten();
}
if (this.removeStatusListener) {
this.removeStatusListener();
}
},
methods: {
setTimeContext() {
@ -365,6 +371,7 @@ export default {
const rows = Object.keys(activityRows);
const isNested = this.options.isChildObject;
const status = isNested ? '' : this.status;
if (rows.length) {
const lastActivityRow = rows[rows.length - 1];
@ -383,11 +390,12 @@ export default {
return {
heading,
isNested,
status,
height: svgHeight,
width: svgWidth
};
},
template: `<swim-lane :is-nested="isNested"><template slot="label">{{heading}}</template><template slot="object"><svg :height="height" :width="width"></svg></template></swim-lane>`
template: `<swim-lane :is-nested="isNested" :status="status"><template slot="label">{{heading}}</template><template slot="object"><svg :height="height" :width="width"></svg></template></swim-lane>`
});
this.$refs.planHolder.appendChild(component.$mount().$el);
@ -547,6 +555,13 @@ export default {
}
}], multiSelect);
event.stopPropagation();
},
setStatus(status) {
this.status = status;
if (this.xScale) {
this.drawPlan();
}
}
}
};

View File

@ -33,7 +33,7 @@ export default function PlanViewProvider(openmct) {
return {
key: 'plan.view',
name: 'Plan',
cssClass: 'icon-calendar',
cssClass: 'icon-plan',
canView(domainObject) {
return domainObject.type === 'plan';
},

View File

@ -23,14 +23,14 @@
import PlanViewProvider from './PlanViewProvider';
import PlanInspectorViewProvider from "./inspector/PlanInspectorViewProvider";
export default function () {
export default function (configuration) {
return function install(openmct) {
openmct.types.addType('plan', {
name: 'Plan',
key: 'plan',
description: 'A plan',
description: 'A configurable timeline-like view for a compatible mission plan file.',
creatable: true,
cssClass: 'icon-calendar',
cssClass: 'icon-plan',
form: [
{
name: 'Upload Plan (JSON File)',

View File

@ -186,5 +186,18 @@ describe('the plugin', function () {
done();
});
});
it ('shows the status indicator when available', (done) => {
openmct.status.set({
key: "test-object",
namespace: ''
}, 'draft');
Vue.nextTick(() => {
const statusEl = element.querySelector('.c-plan__contents .is-status--draft');
expect(statusEl).toBeDefined();
done();
});
});
});
});

View File

@ -21,6 +21,7 @@
*****************************************************************************/
<template>
<swim-lane :icon-class="item.type.definition.cssClass"
:status="status"
:min-height="item.height"
:show-ucontents="item.domainObject.type === 'plan'"
:span-rows-count="item.rowCount"
@ -58,7 +59,8 @@ export default {
data() {
return {
domainObject: undefined,
mutablePromise: undefined
mutablePromise: undefined,
status: ''
};
},
watch: {
@ -103,13 +105,27 @@ export default {
let childContext = this.$refs.objectView.getSelectionContext();
childContext.item = domainObject;
this.context = childContext;
if (this.removeSelectable) {
this.removeSelectable();
}
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context);
}
if (this.removeStatusListener) {
this.removeStatusListener();
}
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.setStatus);
this.status = this.openmct.status.get(this.domainObject.identifier);
});
},
setActionCollection(actionCollection) {
this.openmct.menus.actionsToMenuItems(actionCollection.getVisibleActions(), actionCollection.objectPath, actionCollection.view);
},
setStatus(status) {
this.status = status;
}
}
};

View File

@ -24,40 +24,31 @@
<div ref="timelineHolder"
class="c-timeline-holder"
>
<div class="c-timeline">
<div v-for="timeSystemItem in timeSystems"
:key="timeSystemItem.timeSystem.key"
class="u-contents"
>
<swim-lane>
<template slot="label">
{{ timeSystemItem.timeSystem.name }}
</template>
<template slot="object">
<timeline-axis :bounds="timeSystemItem.bounds"
:time-system="timeSystemItem.timeSystem"
:content-height="height"
:rendering-engine="'svg'"
/>
</template>
<swim-lane v-for="timeSystemItem in timeSystems"
:key="timeSystemItem.timeSystem.key"
>
<template slot="label">
{{ timeSystemItem.timeSystem.name }}
</template>
<template slot="object">
<timeline-axis :bounds="timeSystemItem.bounds"
:time-system="timeSystemItem.timeSystem"
:content-height="height"
:rendering-engine="'svg'"
/>
</template>
</swim-lane>
</div>
</swim-lane>
<div ref="contentHolder"
class="u-contents c-timeline__objects c-timeline__content-holder"
>
<div
v-for="item in items"
:key="item.keyString"
class="u-contents c-timeline__content"
>
<timeline-object-view
class="u-contents"
:item="item"
/>
</div>
</div>
<div ref="contentHolder"
class="c-timeline__objects"
>
<timeline-object-view
v-for="item in items"
:key="item.keyString"
class="c-timeline__content"
:item="item"
/>
</div>
</div>
</template>

View File

@ -28,7 +28,7 @@ export default function () {
openmct.types.addType('time-strip', {
name: 'Time Strip',
key: 'time-strip',
description: 'An activity timeline',
description: 'Compose and display time-based telemetry and other object types in a timeline-like view.',
creatable: true,
cssClass: 'icon-timeline',
initialize: function (domainObject) {

View File

@ -1,10 +1,12 @@
.c-timeline-holder {
@include abs();
display: flex;
flex-direction: column;
overflow-x: hidden;
> * + * {
margin-top: $interiorMargin;
}
overflow: hidden;
}
.c-plan.c-timeline-holder {
overflow-x: hidden;
overflow-y: auto;
}
.c-timeline__objects {
display: contents;
}