mirror of
https://github.com/nasa/openmct.git
synced 2025-06-01 23:20:50 +00:00
Merge release/2.1.2
into master
(#5946)
* Bump version to `2.1.2` * Ensure properties stay in sync and are committed only once (#5717) * Ensure form properties stay in sync * Separate out overlay based forms and custom forms * Use a transaction to save properties * Fix GaugeController to not depend on event emitted from FormsAPI * refactor showForms to call showCustomForm Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Andrew Henry <akhenry@gmail.com> * Fix persistence timestamp (#5916) * Calculate persisted timestamp last * Added regression tests * Correct transaction handling code * Code cleanup * Fix typo for publish (#5936) * Add e2e tests to npm package (#5930) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Shefali Joshi <simplyrender@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Andrew Henry <akhenry@gmail.com> Co-authored-by: John Hill <john.c.hill@nasa.gov>
This commit is contained in:
parent
42a0e503cc
commit
091f6406a8
2
.github/workflows/npm-prerelease.yml
vendored
2
.github/workflows/npm-prerelease.yml
vendored
@ -28,6 +28,6 @@ jobs:
|
|||||||
node-version: 16
|
node-version: 16
|
||||||
registry-url: https://registry.npmjs.org/
|
registry-url: https://registry.npmjs.org/
|
||||||
- run: npm install
|
- run: npm install
|
||||||
- run: npm publish --access public --tag unstable
|
- run: npm publish --access=public --tag unstable
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
@ -21,4 +21,10 @@
|
|||||||
!copyright-notice.html
|
!copyright-notice.html
|
||||||
!index.html
|
!index.html
|
||||||
!openmct.js
|
!openmct.js
|
||||||
!SECURITY.md
|
!SECURITY.md
|
||||||
|
|
||||||
|
# Add e2e tests to npm package
|
||||||
|
!/e2e/**/*
|
||||||
|
|
||||||
|
# ... except our test-data folder files.
|
||||||
|
/e2e/test-data/*.json
|
||||||
|
@ -23,13 +23,11 @@
|
|||||||
import FormController from './FormController';
|
import FormController from './FormController';
|
||||||
import FormProperties from './components/FormProperties.vue';
|
import FormProperties from './components/FormProperties.vue';
|
||||||
|
|
||||||
import EventEmitter from 'EventEmitter';
|
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
export default class FormsAPI extends EventEmitter {
|
export default class FormsAPI {
|
||||||
constructor(openmct) {
|
constructor(openmct) {
|
||||||
super();
|
|
||||||
|
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
this.formController = new FormController(openmct);
|
this.formController = new FormController(openmct);
|
||||||
}
|
}
|
||||||
@ -92,29 +90,75 @@ export default class FormsAPI extends EventEmitter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Show form inside an Overlay dialog with given form structure
|
* Show form inside an Overlay dialog with given form structure
|
||||||
|
* @public
|
||||||
|
* @param {Array<Section>} formStructure a form structure, array of section
|
||||||
|
* @param {Object} options
|
||||||
|
* @property {function} onChange a callback function when any changes detected
|
||||||
|
*/
|
||||||
|
showForm(formStructure, {
|
||||||
|
onChange
|
||||||
|
} = {}) {
|
||||||
|
let overlay;
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
const overlayEl = document.createElement('div');
|
||||||
|
overlayEl.classList.add('u-contents');
|
||||||
|
|
||||||
|
overlay = self.openmct.overlays.overlay({
|
||||||
|
element: overlayEl,
|
||||||
|
size: 'dialog'
|
||||||
|
});
|
||||||
|
|
||||||
|
let formSave;
|
||||||
|
let formCancel;
|
||||||
|
const promise = new Promise((resolve, reject) => {
|
||||||
|
formSave = resolve;
|
||||||
|
formCancel = reject;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.showCustomForm(formStructure, {
|
||||||
|
element: overlayEl,
|
||||||
|
onChange
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
overlay.dismiss();
|
||||||
|
formSave(response);
|
||||||
|
})
|
||||||
|
.catch((response) => {
|
||||||
|
overlay.dismiss();
|
||||||
|
formCancel(response);
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show form as a child of the element provided with given form structure
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
* @param {Array<Section>} formStructure a form structure, array of section
|
* @param {Array<Section>} formStructure a form structure, array of section
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
* @property {HTMLElement} element Parent Element to render a Form
|
* @property {HTMLElement} element Parent Element to render a Form
|
||||||
* @property {function} onChange a callback function when any changes detected
|
* @property {function} onChange a callback function when any changes detected
|
||||||
* @property {function} onSave a callback function when form is submitted
|
|
||||||
* @property {function} onDismiss a callback function when form is dismissed
|
|
||||||
*/
|
*/
|
||||||
showForm(formStructure, {
|
showCustomForm(formStructure, {
|
||||||
element,
|
element,
|
||||||
onChange
|
onChange
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const changes = {};
|
if (element === undefined) {
|
||||||
let overlay;
|
throw Error('Required element parameter not provided');
|
||||||
let onDismiss;
|
}
|
||||||
let onSave;
|
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
|
const changes = {};
|
||||||
|
let formSave;
|
||||||
|
let formCancel;
|
||||||
|
|
||||||
const promise = new Promise((resolve, reject) => {
|
const promise = new Promise((resolve, reject) => {
|
||||||
onSave = onFormAction(resolve);
|
formSave = onFormAction(resolve);
|
||||||
onDismiss = onFormAction(reject);
|
formCancel = onFormAction(reject);
|
||||||
});
|
});
|
||||||
|
|
||||||
const vm = new Vue({
|
const vm = new Vue({
|
||||||
@ -126,26 +170,17 @@ export default class FormsAPI extends EventEmitter {
|
|||||||
return {
|
return {
|
||||||
formStructure,
|
formStructure,
|
||||||
onChange: onFormPropertyChange,
|
onChange: onFormPropertyChange,
|
||||||
onDismiss,
|
onCancel: formCancel,
|
||||||
onSave
|
onSave: formSave
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
template: '<FormProperties :model="formStructure" @onChange="onChange" @onDismiss="onDismiss" @onSave="onSave"></FormProperties>'
|
template: '<FormProperties :model="formStructure" @onChange="onChange" @onCancel="onCancel" @onSave="onSave"></FormProperties>'
|
||||||
}).$mount();
|
}).$mount();
|
||||||
|
|
||||||
const formElement = vm.$el;
|
const formElement = vm.$el;
|
||||||
if (element) {
|
element.append(formElement);
|
||||||
element.append(formElement);
|
|
||||||
} else {
|
|
||||||
overlay = self.openmct.overlays.overlay({
|
|
||||||
element: vm.$el,
|
|
||||||
size: 'dialog',
|
|
||||||
onDestroy: () => vm.$destroy()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onFormPropertyChange(data) {
|
function onFormPropertyChange(data) {
|
||||||
self.emit('onFormPropertyChange', data);
|
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
onChange(data);
|
onChange(data);
|
||||||
}
|
}
|
||||||
@ -158,17 +193,14 @@ export default class FormsAPI extends EventEmitter {
|
|||||||
key = property.join('.');
|
key = property.join('.');
|
||||||
}
|
}
|
||||||
|
|
||||||
changes[key] = data.value;
|
_.set(changes, key, data.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFormAction(callback) {
|
function onFormAction(callback) {
|
||||||
return () => {
|
return () => {
|
||||||
if (element) {
|
formElement.remove();
|
||||||
formElement.remove();
|
vm.$destroy();
|
||||||
} else {
|
|
||||||
overlay.dismiss();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(changes);
|
callback(changes);
|
||||||
|
@ -133,7 +133,7 @@ describe('The Forms API', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('when container element is provided', (done) => {
|
it('when container element is provided', (done) => {
|
||||||
openmct.forms.showForm(formStructure, { element }).catch(() => {
|
openmct.forms.showCustomForm(formStructure, { element }).catch(() => {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
const titleElement = element.querySelector('.c-overlay__dialog-title');
|
const titleElement = element.querySelector('.c-overlay__dialog-title');
|
||||||
|
@ -73,7 +73,7 @@
|
|||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="c-button js-cancel-button"
|
class="c-button js-cancel-button"
|
||||||
aria-label="Cancel"
|
aria-label="Cancel"
|
||||||
@click="onDismiss"
|
@click="onCancel"
|
||||||
>
|
>
|
||||||
{{ cancelLabel }}
|
{{ cancelLabel }}
|
||||||
</button>
|
</button>
|
||||||
@ -164,8 +164,8 @@ export default {
|
|||||||
|
|
||||||
this.$emit('onChange', data);
|
this.$emit('onChange', data);
|
||||||
},
|
},
|
||||||
onDismiss() {
|
onCancel() {
|
||||||
this.$emit('onDismiss');
|
this.$emit('onCancel');
|
||||||
},
|
},
|
||||||
onSave() {
|
onSave() {
|
||||||
this.$emit('onSave');
|
this.$emit('onSave');
|
||||||
|
@ -75,11 +75,7 @@ class MutableDomainObject {
|
|||||||
return eventOff;
|
return eventOff;
|
||||||
}
|
}
|
||||||
$set(path, value) {
|
$set(path, value) {
|
||||||
_.set(this, path, value);
|
MutableDomainObject.mutateObject(this, path, value);
|
||||||
|
|
||||||
if (path !== 'persisted' && path !== 'modified') {
|
|
||||||
_.set(this, 'modified', Date.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
//Emit secret synchronization event first, so that all objects are in sync before subsequent events fired.
|
//Emit secret synchronization event first, so that all objects are in sync before subsequent events fired.
|
||||||
this._globalEventEmitter.emit(qualifiedEventName(this, '$_synchronize_model'), this);
|
this._globalEventEmitter.emit(qualifiedEventName(this, '$_synchronize_model'), this);
|
||||||
@ -136,8 +132,11 @@ class MutableDomainObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static mutateObject(object, path, value) {
|
static mutateObject(object, path, value) {
|
||||||
|
if (path !== 'persisted') {
|
||||||
|
_.set(object, 'modified', Date.now());
|
||||||
|
}
|
||||||
|
|
||||||
_.set(object, path, value);
|
_.set(object, path, value);
|
||||||
_.set(object, 'modified', Date.now());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -363,7 +363,6 @@ export default class ObjectAPI {
|
|||||||
} else if (this.#hasAlreadyBeenPersisted(domainObject)) {
|
} else if (this.#hasAlreadyBeenPersisted(domainObject)) {
|
||||||
result = Promise.resolve(true);
|
result = Promise.resolve(true);
|
||||||
} else {
|
} else {
|
||||||
const persistedTime = Date.now();
|
|
||||||
const username = await this.#getCurrentUsername();
|
const username = await this.#getCurrentUsername();
|
||||||
const isNewObject = domainObject.persisted === undefined;
|
const isNewObject = domainObject.persisted === undefined;
|
||||||
let savedResolve;
|
let savedResolve;
|
||||||
@ -375,15 +374,20 @@ export default class ObjectAPI {
|
|||||||
savedReject = reject;
|
savedReject = reject;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.#mutate(domainObject, 'persisted', persistedTime);
|
|
||||||
this.#mutate(domainObject, 'modifiedBy', username);
|
this.#mutate(domainObject, 'modifiedBy', username);
|
||||||
|
|
||||||
if (isNewObject) {
|
if (isNewObject) {
|
||||||
|
const persistedTime = Date.now();
|
||||||
|
|
||||||
|
this.#mutate(domainObject, 'persisted', persistedTime);
|
||||||
this.#mutate(domainObject, 'created', persistedTime);
|
this.#mutate(domainObject, 'created', persistedTime);
|
||||||
this.#mutate(domainObject, 'createdBy', username);
|
this.#mutate(domainObject, 'createdBy', username);
|
||||||
|
|
||||||
savedObjectPromise = provider.create(domainObject);
|
savedObjectPromise = provider.create(domainObject);
|
||||||
} else {
|
} else {
|
||||||
|
const persistedTime = Date.now();
|
||||||
|
this.#mutate(domainObject, 'persisted', persistedTime);
|
||||||
|
|
||||||
savedObjectPromise = provider.update(domainObject);
|
savedObjectPromise = provider.update(domainObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +94,35 @@ describe("The Object API", () => {
|
|||||||
expect(mockProvider.create).not.toHaveBeenCalled();
|
expect(mockProvider.create).not.toHaveBeenCalled();
|
||||||
expect(mockProvider.update).toHaveBeenCalled();
|
expect(mockProvider.update).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
describe("the persisted timestamp for existing objects", () => {
|
||||||
|
let persistedTimestamp;
|
||||||
|
beforeEach(() => {
|
||||||
|
persistedTimestamp = Date.now() - FIFTEEN_MINUTES;
|
||||||
|
mockDomainObject.persisted = persistedTimestamp;
|
||||||
|
mockDomainObject.modified = Date.now();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is updated", async () => {
|
||||||
|
await objectAPI.save(mockDomainObject);
|
||||||
|
expect(mockDomainObject.persisted).toBeDefined();
|
||||||
|
expect(mockDomainObject.persisted > persistedTimestamp).toBe(true);
|
||||||
|
});
|
||||||
|
it("is >= modified timestamp", async () => {
|
||||||
|
await objectAPI.save(mockDomainObject);
|
||||||
|
expect(mockDomainObject.persisted >= mockDomainObject.modified).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("the persisted timestamp for new objects", () => {
|
||||||
|
it("is updated", async () => {
|
||||||
|
await objectAPI.save(mockDomainObject);
|
||||||
|
expect(mockDomainObject.persisted).toBeDefined();
|
||||||
|
});
|
||||||
|
it("is >= modified timestamp", async () => {
|
||||||
|
await objectAPI.save(mockDomainObject);
|
||||||
|
expect(mockDomainObject.persisted >= mockDomainObject.modified).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("Sets the current user for 'createdBy' on new objects", async () => {
|
it("Sets the current user for 'createdBy' on new objects", async () => {
|
||||||
await objectAPI.save(mockDomainObject);
|
await objectAPI.save(mockDomainObject);
|
||||||
expect(mockDomainObject.createdBy).toBe(USERNAME);
|
expect(mockDomainObject.createdBy).toBe(USERNAME);
|
||||||
|
@ -17,6 +17,7 @@ class Overlay extends EventEmitter {
|
|||||||
dismissable = true,
|
dismissable = true,
|
||||||
element,
|
element,
|
||||||
onDestroy,
|
onDestroy,
|
||||||
|
onDismiss,
|
||||||
size
|
size
|
||||||
} = {}) {
|
} = {}) {
|
||||||
super();
|
super();
|
||||||
@ -32,7 +33,7 @@ class Overlay extends EventEmitter {
|
|||||||
OverlayComponent: OverlayComponent
|
OverlayComponent: OverlayComponent
|
||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
dismiss: this.dismiss.bind(this),
|
dismiss: this.notifyAndDismiss.bind(this),
|
||||||
element,
|
element,
|
||||||
buttons,
|
buttons,
|
||||||
dismissable: this.dismissable
|
dismissable: this.dismissable
|
||||||
@ -43,6 +44,10 @@ class Overlay extends EventEmitter {
|
|||||||
if (onDestroy) {
|
if (onDestroy) {
|
||||||
this.once('destroy', onDestroy);
|
this.once('destroy', onDestroy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (onDismiss) {
|
||||||
|
this.once('dismiss', onDismiss);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dismiss() {
|
dismiss() {
|
||||||
@ -51,6 +56,12 @@ class Overlay extends EventEmitter {
|
|||||||
this.component.$destroy();
|
this.component.$destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Ensures that any callers are notified that the overlay is dismissed
|
||||||
|
notifyAndDismiss() {
|
||||||
|
this.emit('dismiss');
|
||||||
|
this.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
**/
|
**/
|
||||||
|
@ -55,7 +55,7 @@ class OverlayAPI {
|
|||||||
dismissLastOverlay() {
|
dismissLastOverlay() {
|
||||||
let lastOverlay = this.activeOverlays[this.activeOverlays.length - 1];
|
let lastOverlay = this.activeOverlays[this.activeOverlays.length - 1];
|
||||||
if (lastOverlay && lastOverlay.dismissable) {
|
if (lastOverlay && lastOverlay.dismissable) {
|
||||||
lastOverlay.dismiss();
|
lastOverlay.notifyAndDismiss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ import PropertiesAction from './PropertiesAction';
|
|||||||
import CreateWizard from './CreateWizard';
|
import CreateWizard from './CreateWizard';
|
||||||
|
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
export default class CreateAction extends PropertiesAction {
|
export default class CreateAction extends PropertiesAction {
|
||||||
constructor(openmct, type, parentDomainObject) {
|
constructor(openmct, type, parentDomainObject) {
|
||||||
@ -50,19 +51,15 @@ export default class CreateAction extends PropertiesAction {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const properties = key.split('.');
|
const existingValue = this.domainObject[key];
|
||||||
let object = this.domainObject;
|
if (!(existingValue instanceof Array) && (typeof existingValue === 'object')) {
|
||||||
const propertiesLength = properties.length;
|
value = {
|
||||||
properties.forEach((property, index) => {
|
...existingValue,
|
||||||
const isComplexProperty = propertiesLength > 1 && index !== propertiesLength - 1;
|
...value
|
||||||
if (isComplexProperty && object[property] !== null) {
|
};
|
||||||
object = object[property];
|
}
|
||||||
} else {
|
|
||||||
object[property] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
object = value;
|
_.set(this.domainObject, key, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
const parentDomainObject = parentDomainObjectPath[0];
|
const parentDomainObject = parentDomainObjectPath[0];
|
||||||
@ -94,6 +91,12 @@ export default class CreateAction extends PropertiesAction {
|
|||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onCancel() {
|
||||||
|
//do Nothing
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
@ -151,6 +154,7 @@ export default class CreateAction extends PropertiesAction {
|
|||||||
formStructure.title = 'Create a New ' + definition.name;
|
formStructure.title = 'Create a New ' + definition.name;
|
||||||
|
|
||||||
this.openmct.forms.showForm(formStructure)
|
this.openmct.forms.showForm(formStructure)
|
||||||
.then(this._onSave.bind(this));
|
.then(this._onSave.bind(this))
|
||||||
|
.catch(this._onCancel.bind(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
import PropertiesAction from './PropertiesAction';
|
import PropertiesAction from './PropertiesAction';
|
||||||
import CreateWizard from './CreateWizard';
|
import CreateWizard from './CreateWizard';
|
||||||
|
|
||||||
export default class EditPropertiesAction extends PropertiesAction {
|
export default class EditPropertiesAction extends PropertiesAction {
|
||||||
constructor(openmct) {
|
constructor(openmct) {
|
||||||
super(openmct);
|
super(openmct);
|
||||||
@ -52,24 +53,31 @@ export default class EditPropertiesAction extends PropertiesAction {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_onSave(changes) {
|
_onSave(changes) {
|
||||||
|
if (!this.openmct.objects.isTransactionActive()) {
|
||||||
|
this.openmct.objects.startTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Object.entries(changes).forEach(([key, value]) => {
|
Object.entries(changes).forEach(([key, value]) => {
|
||||||
const properties = key.split('.');
|
const existingValue = this.domainObject[key];
|
||||||
let object = this.domainObject;
|
if (!(Array.isArray(existingValue)) && (typeof existingValue === 'object')) {
|
||||||
const propertiesLength = properties.length;
|
value = {
|
||||||
properties.forEach((property, index) => {
|
...existingValue,
|
||||||
const isComplexProperty = propertiesLength > 1 && index !== propertiesLength - 1;
|
...value
|
||||||
if (isComplexProperty && object[property] !== null) {
|
};
|
||||||
object = object[property];
|
}
|
||||||
} else {
|
|
||||||
object[property] = value;
|
this.openmct.objects.mutate(this.domainObject, key, value);
|
||||||
}
|
});
|
||||||
|
const transaction = this.openmct.objects.getActiveTransaction();
|
||||||
|
|
||||||
|
return transaction.commit()
|
||||||
|
.catch(error => {
|
||||||
|
throw error;
|
||||||
|
}).finally(() => {
|
||||||
|
this.openmct.objects.endTransaction();
|
||||||
});
|
});
|
||||||
|
|
||||||
object = value;
|
|
||||||
this.openmct.objects.mutate(this.domainObject, key, value);
|
|
||||||
this.openmct.notifications.info('Save successful');
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.openmct.notifications.error('Error saving objects');
|
this.openmct.notifications.error('Error saving objects');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -24,6 +24,7 @@ import {
|
|||||||
createOpenMct,
|
createOpenMct,
|
||||||
resetApplicationState
|
resetApplicationState
|
||||||
} from 'utils/testing';
|
} from 'utils/testing';
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
|
|
||||||
@ -101,10 +102,15 @@ describe('EditPropertiesAction plugin', () => {
|
|||||||
composition: []
|
composition: []
|
||||||
};
|
};
|
||||||
|
|
||||||
const deBouncedFormChange = debounce(handleFormPropertyChange, 500);
|
editPropertiesAction.invoke([domainObject])
|
||||||
openmct.forms.on('onFormPropertyChange', deBouncedFormChange);
|
.then(() => {
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
function handleFormPropertyChange(data) {
|
Vue.nextTick(() => {
|
||||||
const form = document.querySelector('.js-form');
|
const form = document.querySelector('.js-form');
|
||||||
const title = form.querySelector('input');
|
const title = form.querySelector('input');
|
||||||
expect(title.value).toEqual(domainObject.name);
|
expect(title.value).toEqual(domainObject.name);
|
||||||
@ -118,17 +124,7 @@ describe('EditPropertiesAction plugin', () => {
|
|||||||
|
|
||||||
const clickEvent = createMouseEvent('click');
|
const clickEvent = createMouseEvent('click');
|
||||||
buttons[1].dispatchEvent(clickEvent);
|
buttons[1].dispatchEvent(clickEvent);
|
||||||
|
});
|
||||||
openmct.forms.off('onFormPropertyChange', deBouncedFormChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
editPropertiesAction.invoke([domainObject])
|
|
||||||
.then(() => {
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('edit properties action saves changes', (done) => {
|
it('edit properties action saves changes', (done) => {
|
||||||
@ -159,11 +155,9 @@ describe('EditPropertiesAction plugin', () => {
|
|||||||
const deBouncedCallback = debounce(callback, 300);
|
const deBouncedCallback = debounce(callback, 300);
|
||||||
unObserve = openmct.objects.observe(domainObject, '*', deBouncedCallback);
|
unObserve = openmct.objects.observe(domainObject, '*', deBouncedCallback);
|
||||||
|
|
||||||
let changed = false;
|
editPropertiesAction.invoke([domainObject]);
|
||||||
const deBouncedFormChange = debounce(handleFormPropertyChange, 500);
|
|
||||||
openmct.forms.on('onFormPropertyChange', deBouncedFormChange);
|
|
||||||
|
|
||||||
function handleFormPropertyChange(data) {
|
Vue.nextTick(() => {
|
||||||
const form = document.querySelector('.js-form');
|
const form = document.querySelector('.js-form');
|
||||||
const title = form.querySelector('input');
|
const title = form.querySelector('input');
|
||||||
const notes = form.querySelector('textArea');
|
const notes = form.querySelector('textArea');
|
||||||
@ -172,27 +166,18 @@ describe('EditPropertiesAction plugin', () => {
|
|||||||
expect(buttons[0].textContent.trim()).toEqual('OK');
|
expect(buttons[0].textContent.trim()).toEqual('OK');
|
||||||
expect(buttons[1].textContent.trim()).toEqual('Cancel');
|
expect(buttons[1].textContent.trim()).toEqual('Cancel');
|
||||||
|
|
||||||
if (!changed) {
|
expect(title.value).toEqual(domainObject.name);
|
||||||
expect(title.value).toEqual(domainObject.name);
|
expect(notes.value).toEqual(domainObject.notes);
|
||||||
expect(notes.value).toEqual(domainObject.notes);
|
|
||||||
|
|
||||||
// change input field value and dispatch event for it
|
// change input field value and dispatch event for it
|
||||||
title.focus();
|
title.focus();
|
||||||
title.value = newName;
|
title.value = newName;
|
||||||
title.dispatchEvent(new Event('input'));
|
title.dispatchEvent(new Event('input'));
|
||||||
title.blur();
|
title.blur();
|
||||||
|
|
||||||
changed = true;
|
const clickEvent = createMouseEvent('click');
|
||||||
} else {
|
buttons[0].dispatchEvent(clickEvent);
|
||||||
// click ok to save form changes
|
});
|
||||||
const clickEvent = createMouseEvent('click');
|
|
||||||
buttons[0].dispatchEvent(clickEvent);
|
|
||||||
|
|
||||||
openmct.forms.off('onFormPropertyChange', deBouncedFormChange);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
editPropertiesAction.invoke([domainObject]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('edit properties action discards changes', (done) => {
|
it('edit properties action discards changes', (done) => {
|
||||||
@ -217,7 +202,6 @@ describe('EditPropertiesAction plugin', () => {
|
|||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
expect(domainObject.name).toEqual(name);
|
expect(domainObject.name).toEqual(name);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -100,6 +100,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
ToggleSwitch
|
ToggleSwitch
|
||||||
},
|
},
|
||||||
|
inject: ["openmct"],
|
||||||
props: {
|
props: {
|
||||||
model: {
|
model: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -107,11 +108,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
this.changes = {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isUseTelemetryLimits: this.model.value.isUseTelemetryLimits,
|
isUseTelemetryLimits: this.model.value.isUseTelemetryLimits,
|
||||||
isDisplayMinMax: this.model.value.isDisplayMinMax,
|
|
||||||
isDisplayCurVal: this.model.value.isDisplayCurVal,
|
|
||||||
isDisplayUnits: this.model.value.isDisplayUnits,
|
|
||||||
limitHigh: this.model.value.limitHigh,
|
limitHigh: this.model.value.limitHigh,
|
||||||
limitLow: this.model.value.limitLow,
|
limitLow: this.model.value.limitLow,
|
||||||
max: this.model.value.max,
|
max: this.model.value.max,
|
||||||
@ -120,24 +120,15 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onChange(event) {
|
onChange(event) {
|
||||||
const data = {
|
let data = {
|
||||||
model: this.model,
|
model: {}
|
||||||
value: {
|
|
||||||
gaugeType: this.model.value.gaugeType,
|
|
||||||
isDisplayMinMax: this.isDisplayMinMax,
|
|
||||||
isDisplayCurVal: this.isDisplayCurVal,
|
|
||||||
isDisplayUnits: this.isDisplayUnits,
|
|
||||||
isUseTelemetryLimits: this.isUseTelemetryLimits,
|
|
||||||
limitLow: this.limitLow,
|
|
||||||
limitHigh: this.limitHigh,
|
|
||||||
max: this.max,
|
|
||||||
min: this.min,
|
|
||||||
precision: this.model.value.precision
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
|
const property = target.dataset.fieldName;
|
||||||
|
data.model.property = Array.from(this.model.property).concat([property]);
|
||||||
|
data.value = this[property];
|
||||||
const targetIndicator = target.parentElement.querySelector('.req-indicator');
|
const targetIndicator = target.parentElement.querySelector('.req-indicator');
|
||||||
if (targetIndicator.classList.contains('req')) {
|
if (targetIndicator.classList.contains('req')) {
|
||||||
targetIndicator.classList.add('visited');
|
targetIndicator.classList.add('visited');
|
||||||
@ -160,13 +151,13 @@ export default {
|
|||||||
},
|
},
|
||||||
toggleUseTelemetryLimits() {
|
toggleUseTelemetryLimits() {
|
||||||
this.isUseTelemetryLimits = !this.isUseTelemetryLimits;
|
this.isUseTelemetryLimits = !this.isUseTelemetryLimits;
|
||||||
|
const data = {
|
||||||
this.onChange();
|
model: {
|
||||||
},
|
property: Array.from(this.model.property).concat(['isUseTelemetryLimits'])
|
||||||
toggleMinMax() {
|
},
|
||||||
this.isDisplayMinMax = !this.isDisplayMinMax;
|
value: this.isUseTelemetryLimits
|
||||||
|
};
|
||||||
this.onChange();
|
this.$emit('onChange', data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -889,32 +889,24 @@ export default {
|
|||||||
this.syncUrlWithPageAndSection();
|
this.syncUrlWithPageAndSection();
|
||||||
this.filterAndSortEntries();
|
this.filterAndSortEntries();
|
||||||
},
|
},
|
||||||
activeTransaction() {
|
|
||||||
return this.openmct.objects.getActiveTransaction();
|
|
||||||
},
|
|
||||||
startTransaction() {
|
startTransaction() {
|
||||||
if (!this.openmct.editor.isEditing()) {
|
if (!this.openmct.objects.isTransactionActive()) {
|
||||||
this.openmct.objects.startTransaction();
|
this.transaction = this.openmct.objects.startTransaction();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
saveTransaction() {
|
saveTransaction() {
|
||||||
const transaction = this.activeTransaction();
|
if (this.transaction !== undefined) {
|
||||||
|
this.transaction.commit()
|
||||||
if (!transaction || this.openmct.editor.isEditing()) {
|
.catch(error => {
|
||||||
return;
|
throw error;
|
||||||
|
}).finally(() => {
|
||||||
|
this.openmct.objects.endTransaction();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return transaction.commit()
|
|
||||||
.catch(error => {
|
|
||||||
throw error;
|
|
||||||
}).finally(() => {
|
|
||||||
this.openmct.objects.endTransaction();
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
cancelTransaction() {
|
cancelTransaction() {
|
||||||
if (!this.openmct.editor.isEditing()) {
|
if (this.transaction !== undefined) {
|
||||||
const transaction = this.activeTransaction();
|
this.transaction.cancel()
|
||||||
transaction.cancel()
|
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
throw error;
|
throw error;
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user