From 894da25461c1dc30800a9bc7aa8f4fbc102dc260 Mon Sep 17 00:00:00 2001 From: charlesh88 Date: Tue, 14 Jul 2020 18:42:52 -0700 Subject: [PATCH 1/9] Fix Safari display issues #3192 - Fix Inspector `__content` to properly use flex column layout; - Change `u-angular-object-view-wrapper` to `display: contents`; - Fix `gl-plot` to properly use `flex: 1 1 auto` instead of width and height; --- src/styles/_legacy-plots.scss | 4 +--- src/ui/components/object-frame.scss | 13 ++++--------- src/ui/inspector/inspector.scss | 4 +++- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/styles/_legacy-plots.scss b/src/styles/_legacy-plots.scss index e02e449204..bd8c12710d 100644 --- a/src/styles/_legacy-plots.scss +++ b/src/styles/_legacy-plots.scss @@ -118,9 +118,7 @@ mct-plot { .gl-plot { display: flex; - position: relative; - width: 100%; - height: 100%; + flex: 1 1 auto; /*********************** AXIS AND DISPLAY AREA */ .plot-wrapper-axis-and-display-area { diff --git a/src/ui/components/object-frame.scss b/src/ui/components/object-frame.scss index dd1d17f654..f36eb6e678 100644 --- a/src/ui/components/object-frame.scss +++ b/src/ui/components/object-frame.scss @@ -74,11 +74,9 @@ height: 0; // Chrome 73 overflow bug fix overflow: auto; - .u-angular-object-view-wrapper { - .u-fills-container { - // Expand component types that fill a container - @include abs(); - } + .u-fills-container { + // Expand component types that fill a container + @include abs(); } } @@ -91,8 +89,5 @@ } .u-angular-object-view-wrapper { - flex: 1 1 auto; - height: 100%; - width: 100%; - overflow: hidden; + display: contents; } diff --git a/src/ui/inspector/inspector.scss b/src/ui/inspector/inspector.scss index cd5b56107a..528e6ead24 100644 --- a/src/ui/inspector/inspector.scss +++ b/src/ui/inspector/inspector.scss @@ -4,7 +4,7 @@ flex-direction: column; > * { - // Thi is on purpose: want extra margin on top object-name element + // This is on purpose: want extra margin on top object-name element margin-top: $interiorMargin; } @@ -41,6 +41,8 @@ &__content { flex: 1 1 auto; + display: flex; + flex-direction: column; } &__elements { From 8b088b7a2c1df097eb4c47506c69df662b9c1b9b Mon Sep 17 00:00:00 2001 From: charlesh88 Date: Tue, 14 Jul 2020 18:53:30 -0700 Subject: [PATCH 2/9] Fix Safari display issues #3192 - Fix Status area indicators width problem; - Also fixes collapsing-status-area-indicator-bubbles transition problem as well!; --- src/ui/layout/status-bar/indicators.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ui/layout/status-bar/indicators.scss b/src/ui/layout/status-bar/indicators.scss index 67001aeb73..10955bc94d 100644 --- a/src/ui/layout/status-bar/indicators.scss +++ b/src/ui/layout/status-bar/indicators.scss @@ -76,8 +76,12 @@ [class*='minify-indicators'] { // All styles for minified Indicators should go in here .c-indicator:not(.no-minify) { + overflow: hidden; // Solves width problem in Safari as well as collapsing bubbles problem + @include hover() { background: $colorIndicatorBgHov; + overflow: visible; + .c-indicator__label { box-shadow: $colorIndicatorMenuBgShdw; transform: scale(1.0); From 73b81e38e736ec7fc435958e8abf92f7f9e4bd0c Mon Sep 17 00:00:00 2001 From: charlesh88 Date: Tue, 14 Jul 2020 19:22:18 -0700 Subject: [PATCH 3/9] Fix Safari display issues #3192 - Fix collapsed Status area indicators width problem; --- src/ui/layout/status-bar/indicators.scss | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/ui/layout/status-bar/indicators.scss b/src/ui/layout/status-bar/indicators.scss index 10955bc94d..1d33692662 100644 --- a/src/ui/layout/status-bar/indicators.scss +++ b/src/ui/layout/status-bar/indicators.scss @@ -76,16 +76,19 @@ [class*='minify-indicators'] { // All styles for minified Indicators should go in here .c-indicator:not(.no-minify) { - overflow: hidden; // Solves width problem in Safari as well as collapsing bubbles problem + border: 1px solid transparent; // Hack to make minified sizing work in Safari. Have no idea why this works. + overflow: visible; + transition: all; @include hover() { background: $colorIndicatorBgHov; - overflow: visible; + transition: all 250ms ease-in 200ms; .c-indicator__label { box-shadow: $colorIndicatorMenuBgShdw; transform: scale(1.0); - transition: all 100ms ease-out 100ms; + overflow: visible; + //transition: all 100ms ease-out 100ms; } } .c-indicator__label { @@ -99,7 +102,7 @@ position: absolute; transform-origin: 90% 0; transform: scale(0.0); - overflow: visible; + overflow: hidden; z-index: 50; &:before { From baa7c0bc584a22162e6c2d972346670bb8851a1d Mon Sep 17 00:00:00 2001 From: charlesh88 Date: Tue, 14 Jul 2020 21:51:22 -0700 Subject: [PATCH 4/9] Fix Safari display issues #3192 - Tweak to Status area indicator hover bubbles; --- src/ui/layout/status-bar/indicators.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ui/layout/status-bar/indicators.scss b/src/ui/layout/status-bar/indicators.scss index 1d33692662..ce334d3c24 100644 --- a/src/ui/layout/status-bar/indicators.scss +++ b/src/ui/layout/status-bar/indicators.scss @@ -78,21 +78,21 @@ .c-indicator:not(.no-minify) { border: 1px solid transparent; // Hack to make minified sizing work in Safari. Have no idea why this works. overflow: visible; - transition: all; + transition: transform; @include hover() { background: $colorIndicatorBgHov; - transition: all 250ms ease-in 200ms; + transition: transform 250ms ease-in 200ms; // Go-away transition .c-indicator__label { box-shadow: $colorIndicatorMenuBgShdw; transform: scale(1.0); overflow: visible; - //transition: all 100ms ease-out 100ms; + transition: transform 100ms ease-out 100ms; // Appear transition } } .c-indicator__label { - transition: all 250ms ease-in 200ms; + transition: transform 250ms ease-in 200ms; // Go-away transition background: $colorIndicatorMenuBg; color: $colorIndicatorMenuFg; border-radius: $controlCr; From b1467548da489c9a8176b47f70fd2adc1cf71179 Mon Sep 17 00:00:00 2001 From: charlesh88 Date: Tue, 14 Jul 2020 23:40:42 -0700 Subject: [PATCH 5/9] Fix Safari display issues #3192 - Tweaks to fix `c-tab` elements, fix clip-path for webkit; - Fix Notebook Snapshots header; --- .../notebook-snapshot-container.vue | 17 +++++++------ src/styles/_controls.scss | 24 ++++++++++--------- src/ui/components/object-label.scss | 3 +-- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/plugins/notebook/components/notebook-snapshot-container.vue b/src/plugins/notebook/components/notebook-snapshot-container.vue index 950f655ba3..8dcbba0f64 100644 --- a/src/plugins/notebook/components/notebook-snapshot-container.vue +++ b/src/plugins/notebook/components/notebook-snapshot-container.vue @@ -2,13 +2,16 @@
-
-
- Notebook Snapshots -  {{ snapshots.length }} of {{ getNotebookSnapshotMaxCount() }} - +
+
+
+
+ Notebook Snapshots +  {{ snapshots.length }} of {{ getNotebookSnapshotMaxCount() }} + +
* + * { margin-left: $interiorMargin; diff --git a/src/ui/components/object-label.scss b/src/ui/components/object-label.scss index 3181f23a31..395c223086 100644 --- a/src/ui/components/object-label.scss +++ b/src/ui/components/object-label.scss @@ -3,7 +3,7 @@ // Used mostly in trees and lists display: flex; align-items: center; - flex: 1 1 auto; + flex: 0 1 auto; overflow: hidden; white-space: nowrap; @@ -19,7 +19,6 @@ display: block; flex: 0 0 auto; font-size: 1.1em; - //margin-right: $interiorMargin; } &.is-missing { From cb63f4eca1b092bead15332650f00cc2a313d576 Mon Sep 17 00:00:00 2001 From: Charles Hacskaylo Date: Thu, 16 Jul 2020 12:43:37 -0700 Subject: [PATCH 6/9] Fix `is-missing` layout problem #3194 (#3195) - Fixes related to `is-missing` including fixes for Display Layout alphanumeric views and Tabs view tabs; --- .../components/telemetry-view.scss | 15 ++++++------- src/plugins/tabs/components/tabs.vue | 21 +++++++++++-------- src/styles/_mixins.scss | 5 ++++- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/plugins/displayLayout/components/telemetry-view.scss b/src/plugins/displayLayout/components/telemetry-view.scss index 075ded5273..22ea9a90b4 100644 --- a/src/plugins/displayLayout/components/telemetry-view.scss +++ b/src/plugins/displayLayout/components/telemetry-view.scss @@ -27,13 +27,14 @@ border: 1px solid transparent; } - &.is-missing { - @include isMissing($absPos: true); - border: $borderMissing; + @include isMissing($absPos: true); - .is-missing__indicator { - top: 0; - left: 0; - } + .is-missing__indicator { + top: 0; + left: 0; + } + + &.is-missing { + border: $borderMissing; } } diff --git a/src/plugins/tabs/components/tabs.vue b/src/plugins/tabs/components/tabs.vue index 0d32867131..294ba41470 100644 --- a/src/plugins/tabs/components/tabs.vue +++ b/src/plugins/tabs/components/tabs.vue @@ -22,21 +22,24 @@
Date: Fri, 17 Jul 2020 09:23:51 -0700 Subject: [PATCH 7/9] Disallow editor Edit mode when object is locked (#3208) * Don't allow editor edit if object is locked * Adds log statement * Use capture phase for onDragOver Co-authored-by: Deep Tailor --- src/ui/components/ObjectView.vue | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ui/components/ObjectView.vue b/src/ui/components/ObjectView.vue index 870cc34d4d..1685a8fdd3 100644 --- a/src/ui/components/ObjectView.vue +++ b/src/ui/components/ObjectView.vue @@ -53,7 +53,9 @@ export default { mounted() { this.currentObject = this.object; this.updateView(); - this.$el.addEventListener('dragover', this.onDragOver); + this.$el.addEventListener('dragover', this.onDragOver, { + capture: true + }); this.$el.addEventListener('drop', this.editIfEditable, { capture: true }); @@ -269,6 +271,7 @@ export default { if (provider && provider.canEdit && provider.canEdit(this.currentObject) && + this.isEditingAllowed() && !this.openmct.editor.isEditing()) { this.openmct.editor.edit(); } From 63bf856d89ce8bf02e90e3b6b97b941aa6859645 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Fri, 17 Jul 2020 09:58:03 -0700 Subject: [PATCH 8/9] Enable persistence operations from Object Providers (#3200) * Implement 'save' method in Object API * Refactor legacy persistence code to work with new save object API * Added 'isPersistable' check to object API * Fixed incompatibility between object API changes and composition policies * Make save method private Co-authored-by: Deep Tailor --- .../policies/EditPersistableObjectsPolicy.js | 7 +- .../policies/EditPersistableObjectsSpec.js | 23 +++--- .../src/PersistableCompositionPolicy.js | 5 +- .../test/PersistableCompositionPolicySpec.js | 15 ++-- platform/core/bundle.js | 3 +- .../src/capabilities/PersistenceCapability.js | 46 ++++------- .../capabilities/PersistenceCapabilitySpec.js | 82 +++++++++---------- .../services/LegacyObjectAPIInterceptor.js | 57 +++++++++++-- src/api/objects/ObjectAPI.js | 60 ++++++++++++-- src/api/objects/ObjectAPISpec.js | 60 ++++++++++++++ 10 files changed, 238 insertions(+), 120 deletions(-) create mode 100644 src/api/objects/ObjectAPISpec.js diff --git a/platform/commonUI/edit/src/policies/EditPersistableObjectsPolicy.js b/platform/commonUI/edit/src/policies/EditPersistableObjectsPolicy.js index e384e4970a..50a0dc7a9b 100644 --- a/platform/commonUI/edit/src/policies/EditPersistableObjectsPolicy.js +++ b/platform/commonUI/edit/src/policies/EditPersistableObjectsPolicy.js @@ -36,8 +36,6 @@ define( } EditPersistableObjectsPolicy.prototype.allow = function (action, context) { - var identifier; - var provider; var domainObject = context.domainObject; var key = action.getMetadata().key; var category = (context || {}).category; @@ -46,9 +44,8 @@ define( // is also invoked during the create process which should be allowed, // because it may be saved elsewhere if ((key === 'edit' && category === 'view-control') || key === 'properties') { - identifier = objectUtils.parseKeyString(domainObject.getId()); - provider = this.openmct.objects.getProvider(identifier); - return provider.save !== undefined; + let newStyleObject = objectUtils.toNewFormat(domainObject, domainObject.getId()); + return this.openmct.objects.isPersistable(newStyleObject); } return true; diff --git a/platform/commonUI/edit/test/policies/EditPersistableObjectsSpec.js b/platform/commonUI/edit/test/policies/EditPersistableObjectsSpec.js index ed9c61bf1d..92b0da2d36 100644 --- a/platform/commonUI/edit/test/policies/EditPersistableObjectsSpec.js +++ b/platform/commonUI/edit/test/policies/EditPersistableObjectsSpec.js @@ -43,7 +43,7 @@ define( ); mockObjectAPI = jasmine.createSpyObj('objectAPI', [ - 'getProvider' + 'isPersistable' ]); mockAPI = { @@ -69,34 +69,31 @@ define( }); it("Applies to edit action", function () { - mockObjectAPI.getProvider.and.returnValue({}); - expect(mockObjectAPI.getProvider).not.toHaveBeenCalled(); + expect(mockObjectAPI.isPersistable).not.toHaveBeenCalled(); policy.allow(mockEditAction, testContext); - expect(mockObjectAPI.getProvider).toHaveBeenCalled(); + expect(mockObjectAPI.isPersistable).toHaveBeenCalled(); }); it("Applies to properties action", function () { - mockObjectAPI.getProvider.and.returnValue({}); - expect(mockObjectAPI.getProvider).not.toHaveBeenCalled(); + expect(mockObjectAPI.isPersistable).not.toHaveBeenCalled(); policy.allow(mockPropertiesAction, testContext); - expect(mockObjectAPI.getProvider).toHaveBeenCalled(); + expect(mockObjectAPI.isPersistable).toHaveBeenCalled(); }); it("does not apply to other actions", function () { - mockObjectAPI.getProvider.and.returnValue({}); - expect(mockObjectAPI.getProvider).not.toHaveBeenCalled(); + expect(mockObjectAPI.isPersistable).not.toHaveBeenCalled(); policy.allow(mockOtherAction, testContext); - expect(mockObjectAPI.getProvider).not.toHaveBeenCalled(); + expect(mockObjectAPI.isPersistable).not.toHaveBeenCalled(); }); it("Tests object provider for editability", function () { - mockObjectAPI.getProvider.and.returnValue({}); + mockObjectAPI.isPersistable.and.returnValue(false); expect(policy.allow(mockEditAction, testContext)).toBe(false); - expect(mockObjectAPI.getProvider).toHaveBeenCalled(); - mockObjectAPI.getProvider.and.returnValue({save: function () {}}); + expect(mockObjectAPI.isPersistable).toHaveBeenCalled(); + mockObjectAPI.isPersistable.and.returnValue(true); expect(policy.allow(mockEditAction, testContext)).toBe(true); }); }); diff --git a/platform/containment/src/PersistableCompositionPolicy.js b/platform/containment/src/PersistableCompositionPolicy.js index df9d228222..bb8c4078ac 100644 --- a/platform/containment/src/PersistableCompositionPolicy.js +++ b/platform/containment/src/PersistableCompositionPolicy.js @@ -48,9 +48,8 @@ define( // prevents editing of objects that cannot be persisted, so we can assume that this // is a new object. if (!(parent.hasCapability('editor') && parent.getCapability('editor').isEditContextRoot())) { - var identifier = objectUtils.parseKeyString(parent.getId()); - var provider = this.openmct.objects.getProvider(identifier); - return provider.save !== undefined; + let newStyleObject = objectUtils.toNewFormat(parent, parent.getId()); + return this.openmct.objects.isPersistable(newStyleObject); } return true; }; diff --git a/platform/containment/test/PersistableCompositionPolicySpec.js b/platform/containment/test/PersistableCompositionPolicySpec.js index f06ef7d16b..dcc2a928a5 100644 --- a/platform/containment/test/PersistableCompositionPolicySpec.js +++ b/platform/containment/test/PersistableCompositionPolicySpec.js @@ -33,7 +33,7 @@ define( beforeEach(function () { objectAPI = jasmine.createSpyObj('objectsAPI', [ - 'getProvider' + 'isPersistable' ]); mockOpenMCT = { @@ -51,10 +51,6 @@ define( 'isEditContextRoot' ]); mockParent.getCapability.and.returnValue(mockEditorCapability); - - objectAPI.getProvider.and.returnValue({ - save: function () {} - }); persistableCompositionPolicy = new PersistableCompositionPolicy(mockOpenMCT); }); @@ -65,19 +61,22 @@ define( it("Does not allow composition for objects that are not persistable", function () { mockEditorCapability.isEditContextRoot.and.returnValue(false); + objectAPI.isPersistable.and.returnValue(true); expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(true); - objectAPI.getProvider.and.returnValue({}); + objectAPI.isPersistable.and.returnValue(false); expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(false); }); it("Always allows composition of objects in edit mode to support object creation", function () { mockEditorCapability.isEditContextRoot.and.returnValue(true); + objectAPI.isPersistable.and.returnValue(true); expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(true); - expect(objectAPI.getProvider).not.toHaveBeenCalled(); + expect(objectAPI.isPersistable).not.toHaveBeenCalled(); mockEditorCapability.isEditContextRoot.and.returnValue(false); + objectAPI.isPersistable.and.returnValue(true); expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(true); - expect(objectAPI.getProvider).toHaveBeenCalled(); + expect(objectAPI.isPersistable).toHaveBeenCalled(); }); }); diff --git a/platform/core/bundle.js b/platform/core/bundle.js index 487a009581..30f8ee9303 100644 --- a/platform/core/bundle.js +++ b/platform/core/bundle.js @@ -297,7 +297,8 @@ define([ "persistenceService", "identifierService", "notificationService", - "$q" + "$q", + "openmct" ] }, { diff --git a/platform/core/src/capabilities/PersistenceCapability.js b/platform/core/src/capabilities/PersistenceCapability.js index 7b1658e66e..12ec5e275c 100644 --- a/platform/core/src/capabilities/PersistenceCapability.js +++ b/platform/core/src/capabilities/PersistenceCapability.js @@ -20,8 +20,8 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ -define( - function () { +define(["objectUtils"], + function (objectUtils) { /** * Defines the `persistence` capability, used to trigger the @@ -47,6 +47,7 @@ define( identifierService, notificationService, $q, + openmct, domainObject ) { // Cache modified timestamp @@ -58,6 +59,7 @@ define( this.persistenceService = persistenceService; this.notificationService = notificationService; this.$q = $q; + this.openmct = openmct; } /** @@ -66,7 +68,7 @@ define( */ function rejectIfFalsey(value, $q) { if (!value) { - return $q.reject("Error persisting object"); + return Promise.reject("Error persisting object"); } else { return value; } @@ -98,7 +100,7 @@ define( dismissable: true }); - return $q.reject(error); + return Promise.reject(error); } /** @@ -110,34 +112,16 @@ define( */ PersistenceCapability.prototype.persist = function () { var self = this, - domainObject = this.domainObject, - model = domainObject.getModel(), - modified = model.modified, - persisted = model.persisted, - persistenceService = this.persistenceService, - persistenceFn = persisted !== undefined ? - this.persistenceService.updateObject : - this.persistenceService.createObject; + domainObject = this.domainObject; - if (persisted !== undefined && persisted === modified) { - return this.$q.when(true); - } - - // Update persistence timestamp... - domainObject.useCapability("mutation", function (m) { - m.persisted = modified; - }, modified); - - // ...and persist - return persistenceFn.apply(persistenceService, [ - this.getSpace(), - this.getKey(), - domainObject.getModel() - ]).then(function (result) { - return rejectIfFalsey(result, self.$q); - }).catch(function (error) { - return notifyOnError(error, domainObject, self.notificationService, self.$q); - }); + let newStyleObject = objectUtils.toNewFormat(domainObject.getModel(), domainObject.getId()); + return this.openmct.objects + .save(newStyleObject) + .then(function (result) { + return rejectIfFalsey(result, self.$q); + }).catch(function (error) { + return notifyOnError(error, domainObject, self.notificationService, self.$q); + }); }; /** diff --git a/platform/core/test/capabilities/PersistenceCapabilitySpec.js b/platform/core/test/capabilities/PersistenceCapabilitySpec.js index aa112fb8cf..68fd84092d 100644 --- a/platform/core/test/capabilities/PersistenceCapabilitySpec.js +++ b/platform/core/test/capabilities/PersistenceCapabilitySpec.js @@ -19,7 +19,6 @@ * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ - /** * PersistenceCapabilitySpec. Created by vwoeltje on 11/6/14. */ @@ -40,7 +39,8 @@ define( model, SPACE = "some space", persistence, - happyPromise; + mockOpenMCT, + mockNewStyleDomainObject; function asPromise(value, doCatch) { return (value || {}).then ? value : { @@ -56,7 +56,6 @@ define( } beforeEach(function () { - happyPromise = asPromise(true); model = { someKey: "some value", name: "domain object"}; mockPersistenceService = jasmine.createSpyObj( @@ -94,12 +93,23 @@ define( }, useCapability: jasmine.createSpy() }; + + mockNewStyleDomainObject = Object.assign({}, model); + mockNewStyleDomainObject.identifier = { + namespace: "", + key: id + } + // Simulate mutation capability mockDomainObject.useCapability.and.callFake(function (capability, mutator) { if (capability === 'mutation') { model = mutator(model) || model; } }); + + mockOpenMCT = {}; + mockOpenMCT.objects = jasmine.createSpyObj('Object API', ['save']); + mockIdentifierService.parse.and.returnValue(mockIdentifier); mockIdentifier.getSpace.and.returnValue(SPACE); mockIdentifier.getKey.and.returnValue(key); @@ -110,51 +120,28 @@ define( mockIdentifierService, mockNofificationService, mockQ, + mockOpenMCT, mockDomainObject ); }); describe("successful persistence", function () { beforeEach(function () { - mockPersistenceService.updateObject.and.returnValue(happyPromise); - mockPersistenceService.createObject.and.returnValue(happyPromise); + mockOpenMCT.objects.save.and.returnValue(Promise.resolve(true)); }); it("creates unpersisted objects with the persistence service", function () { // Verify precondition; no call made during constructor - expect(mockPersistenceService.createObject).not.toHaveBeenCalled(); + expect(mockOpenMCT.objects.save).not.toHaveBeenCalled(); persistence.persist(); - expect(mockPersistenceService.createObject).toHaveBeenCalledWith( - SPACE, - key, - model - ); - }); - - it("updates previously persisted objects with the persistence service", function () { - // Verify precondition; no call made during constructor - expect(mockPersistenceService.updateObject).not.toHaveBeenCalled(); - - model.persisted = 12321; - persistence.persist(); - - expect(mockPersistenceService.updateObject).toHaveBeenCalledWith( - SPACE, - key, - model - ); + expect(mockOpenMCT.objects.save).toHaveBeenCalledWith(mockNewStyleDomainObject); }); it("reports which persistence space an object belongs to", function () { expect(persistence.getSpace()).toEqual(SPACE); }); - it("updates persisted timestamp on persistence", function () { - model.modified = 12321; - persistence.persist(); - expect(model.persisted).toEqual(12321); - }); it("refreshes the domain object model from persistence", function () { var refreshModel = {someOtherKey: "some other value"}; model.persisted = 1; @@ -165,30 +152,37 @@ define( it("does not trigger error notification on successful" + " persistence", function () { - persistence.persist(); - expect(mockQ.reject).not.toHaveBeenCalled(); - expect(mockNofificationService.error).not.toHaveBeenCalled(); + let rejected = false; + return persistence.persist() + .catch(() => rejected = true) + .then(() => { + expect(rejected).toBe(false); + expect(mockNofificationService.error).not.toHaveBeenCalled(); + }); }); }); describe("unsuccessful persistence", function () { - var sadPromise = { - then: function (callback) { - return asPromise(callback(0), true); - } - }; beforeEach(function () { - mockPersistenceService.createObject.and.returnValue(sadPromise); + mockOpenMCT.objects.save.and.returnValue(Promise.resolve(false)); }); it("rejects on falsey persistence result", function () { - persistence.persist(); - expect(mockQ.reject).toHaveBeenCalled(); + let rejected = false; + return persistence.persist() + .catch(() => rejected = true) + .then(() => { + expect(rejected).toBe(true); + }); }); it("notifies user on persistence failure", function () { - persistence.persist(); - expect(mockQ.reject).toHaveBeenCalled(); - expect(mockNofificationService.error).toHaveBeenCalled(); + let rejected = false; + return persistence.persist() + .catch(() => rejected = true) + .then(() => { + expect(rejected).toBe(true); + expect(mockNofificationService.error).toHaveBeenCalled(); + }); }); }); }); diff --git a/src/adapter/services/LegacyObjectAPIInterceptor.js b/src/adapter/services/LegacyObjectAPIInterceptor.js index d71dcd1837..5448f50c82 100644 --- a/src/adapter/services/LegacyObjectAPIInterceptor.js +++ b/src/adapter/services/LegacyObjectAPIInterceptor.js @@ -25,10 +25,11 @@ define([ ], function ( utils ) { - function ObjectServiceProvider(eventEmitter, objectService, instantiate, topic) { + function ObjectServiceProvider(eventEmitter, objectService, instantiate, topic, $injector) { this.eventEmitter = eventEmitter; this.objectService = objectService; this.instantiate = instantiate; + this.$injector = $injector; this.generalTopic = topic('mutation'); this.bridgeEventBuses(); @@ -68,16 +69,53 @@ define([ removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation); }; - ObjectServiceProvider.prototype.save = function (object) { - var key = object.key; + ObjectServiceProvider.prototype.create = async function (object) { + let model = utils.toOldFormat(object); - return object.getCapability('persistence') - .persist() - .then(function () { - return utils.toNewFormat(object, key); - }); + return this.getPersistenceService().createObject( + this.getSpace(utils.makeKeyString(object.identifier)), + object.identifier.key, + model + ); + } + ObjectServiceProvider.prototype.update = async function (object) { + let model = utils.toOldFormat(object); + + return this.getPersistenceService().updateObject( + this.getSpace(utils.makeKeyString(object.identifier)), + object.identifier.key, + model + ); + } + + /** + * Get the space in which this domain object is persisted; + * this is useful when, for example, decided which space a + * newly-created domain object should be persisted to (by + * default, this should be the space of its containing + * object.) + * + * @returns {string} the name of the space which should + * be used to persist this object + */ + ObjectServiceProvider.prototype.getSpace = function (keystring) { + return this.getIdentifierService().parse(keystring).getSpace(); }; + ObjectServiceProvider.prototype.getIdentifierService = function () { + if (this.identifierService === undefined) { + this.identifierService = this.$injector.get('identifierService'); + } + return this.identifierService; + }; + + ObjectServiceProvider.prototype.getPersistenceService = function () { + if (this.persistenceService === undefined) { + this.persistenceService = this.$injector.get('persistenceService'); + } + return this.persistenceService; + } + ObjectServiceProvider.prototype.delete = function (object) { // TODO! }; @@ -118,7 +156,8 @@ define([ eventEmitter, objectService, instantiate, - topic + topic, + openmct.$injector ) ); diff --git a/src/api/objects/ObjectAPI.js b/src/api/objects/ObjectAPI.js index 3d874eb295..914620607b 100644 --- a/src/api/objects/ObjectAPI.js +++ b/src/api/objects/ObjectAPI.js @@ -101,14 +101,25 @@ define([ */ /** - * Save this domain object in its current state. + * Create the given domain object in the corresponding persistence store * - * @method save + * @method create * @memberof module:openmct.ObjectProvider# * @param {module:openmct.DomainObject} domainObject the domain object to - * save + * create * @returns {Promise} a promise which will resolve when the domain object - * has been saved, or be rejected if it cannot be saved + * has been created, or be rejected if it cannot be saved + */ + + /** + * Update this domain object in its persistence store + * + * @method update + * @memberof module:openmct.ObjectProvider# + * @param {module:openmct.DomainObject} domainObject the domain object to + * update + * @returns {Promise} a promise which will resolve when the domain object + * has been updated, or be rejected if it cannot be saved */ /** @@ -161,8 +172,41 @@ define([ throw new Error('Delete not implemented'); }; - ObjectAPI.prototype.save = function () { - throw new Error('Save not implemented'); + ObjectAPI.prototype.isPersistable = function (domainObject) { + let provider = this.getProvider(domainObject.identifier); + return provider !== undefined && + provider.create !== undefined && + provider.update !== undefined; + } + + /** + * Save this domain object in its current state. EXPERIMENTAL + * + * @private + * @memberof module:openmct.ObjectAPI# + * @param {module:openmct.DomainObject} domainObject the domain object to + * save + * @returns {Promise} a promise which will resolve when the domain object + * has been saved, or be rejected if it cannot be saved + */ + ObjectAPI.prototype.save = function (domainObject) { + let provider = this.getProvider(domainObject.identifier); + let result; + + if (!this.isPersistable(domainObject)) { + result = Promise.reject('Object provider does not support saving'); + } else if (hasAlreadyBeenPersisted(domainObject)) { + result = Promise.resolve(true); + } else { + if (domainObject.persisted === undefined) { + this.mutate(domainObject, 'persisted', domainObject.modified); + result = provider.create(domainObject); + } else { + this.mutate(domainObject, 'persisted', domainObject.modified); + result = provider.update(domainObject); + } + } + return result; }; /** @@ -276,5 +320,9 @@ define([ * @memberof module:openmct */ + function hasAlreadyBeenPersisted(domainObject) { + return domainObject.persisted !== undefined && + domainObject.persisted === domainObject.modified; + } return ObjectAPI; }); diff --git a/src/api/objects/ObjectAPISpec.js b/src/api/objects/ObjectAPISpec.js new file mode 100644 index 0000000000..38d648e100 --- /dev/null +++ b/src/api/objects/ObjectAPISpec.js @@ -0,0 +1,60 @@ +import ObjectAPI from './ObjectAPI.js'; + +describe("The Object API", () => { + let objectAPI; + let mockDomainObject; + const TEST_NAMESPACE = "test-namespace"; + const FIFTEEN_MINUTES = 15 * 60 * 1000; + + beforeEach(() => { + objectAPI = new ObjectAPI(); + mockDomainObject = { + identifier: { + namespace: TEST_NAMESPACE, + key: "test-key" + }, + name: "test object", + type: "test-type" + }; + }) + describe("The save function", () => { + it("Rejects if no provider available", () => { + let rejected = false; + return objectAPI.save(mockDomainObject) + .catch(() => rejected = true) + .then(() => expect(rejected).toBe(true)); + }); + describe("when a provider is available", () => { + let mockProvider; + beforeEach(() => { + mockProvider = jasmine.createSpyObj("mock provider", [ + "create", + "update" + ]); + objectAPI.addProvider(TEST_NAMESPACE, mockProvider); + }) + it("Calls 'create' on provider if object is new", () => { + objectAPI.save(mockDomainObject); + expect(mockProvider.create).toHaveBeenCalled(); + expect(mockProvider.update).not.toHaveBeenCalled(); + }); + it("Calls 'update' on provider if object is not new", () => { + mockDomainObject.persisted = Date.now() - FIFTEEN_MINUTES; + mockDomainObject.modified = Date.now(); + + objectAPI.save(mockDomainObject); + expect(mockProvider.create).not.toHaveBeenCalled(); + expect(mockProvider.update).toHaveBeenCalled(); + }); + + it("Does not persist if the object is unchanged", () => { + mockDomainObject.persisted = + mockDomainObject.modified = Date.now(); + + objectAPI.save(mockDomainObject); + expect(mockProvider.create).not.toHaveBeenCalled(); + expect(mockProvider.update).not.toHaveBeenCalled(); + }); + }); + }) +}); From 7221dc1ac6c4fdb77cd55a4245103956a90bf5cc Mon Sep 17 00:00:00 2001 From: Deep Tailor Date: Fri, 17 Jul 2020 16:48:14 -0700 Subject: [PATCH 9/9] make clear data indicator a configurable option (#3206) --- index.html | 5 ++++- src/plugins/clearData/plugin.js | 32 ++++++++++++++++++-------------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/index.html b/index.html index 1034cd506e..3911020002 100644 --- a/index.html +++ b/index.html @@ -113,7 +113,10 @@ openmct.install(openmct.plugins.LADTable()); openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay'])); openmct.install(openmct.plugins.ObjectMigration()); - openmct.install(openmct.plugins.ClearData(['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'])); + openmct.install(openmct.plugins.ClearData( + ['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'], + {indicator: true} + )); openmct.start(); diff --git a/src/plugins/clearData/plugin.js b/src/plugins/clearData/plugin.js index d4a6689cdc..ecc4d2ef8a 100644 --- a/src/plugins/clearData/plugin.js +++ b/src/plugins/clearData/plugin.js @@ -29,24 +29,28 @@ define([ ClearDataAction, Vue ) { - return function plugin(appliesToObjects) { + return function plugin(appliesToObjects, options = {indicator: true}) { + let installIndicator = options.indicator; + appliesToObjects = appliesToObjects || []; return function install(openmct) { - let component = new Vue ({ - provide: { - openmct - }, - components: { - GlobalClearIndicator: GlobaClearIndicator.default - }, - template: '' - }), - indicator = { - element: component.$mount().$el - }; + if (installIndicator) { + let component = new Vue ({ + provide: { + openmct + }, + components: { + GlobalClearIndicator: GlobaClearIndicator.default + }, + template: '' + }), + indicator = { + element: component.$mount().$el + }; - openmct.indicators.add(indicator); + openmct.indicators.add(indicator); + } openmct.contextMenu.registerAction(new ClearDataAction.default(openmct, appliesToObjects)); };