Compare commits

...

11 Commits

Author SHA1 Message Date
Julie Wang
fc8663c049 Reverted unecessary whitespace change. 2018-08-09 21:50:40 -07:00
Julie Wang
adfe30a891 Added comments to clarify code. 2018-08-09 21:15:54 -07:00
Wang
5218e350c3 Updates tests in locator controller. 2018-08-09 17:32:28 -07:00
Julie Wang
88615e92d2 Added tests for new folder action and locator controller. 2018-08-09 16:40:00 -07:00
Julie Wang
12477a220b testing new implementation. 2018-08-06 22:31:23 -07:00
Deep Tailor
9582fb2b06 Merge pull request #2137 from nasa/prevent-plot-accidental-remove
[Plot] Update formDomainObject on mutate
2018-07-31 12:44:51 -07:00
Pete Richards
3c075b7ff2 [Plot] pass persisted config in constructor
When a plot series is constructed, it checks to see if should set a
default interpolation based on the persisted configuration.  However,
the persisted configuration wasn't available until after construction
which resulted in the default value always being set.

Pass the persisted configuration through the constructor to ensure the
plot series can make the right decision about defaults.

Fixes #2120.
2018-07-31 12:33:10 -07:00
Deep Tailor
081edfbd70 Merge pull request #2127 from nasa/plot-requests-2126
[Plot] Prevent duplicate query on bounds change
2018-07-31 12:32:57 -07:00
Pete Richards
04cc8f7aa2 [Plot] Update formDomainObject on mutate
When a new series is added to a plot, a plot series form controller
is instantiated and passed in a domain object via scope that it will
mutate upon changes.  If a mutation results in a composition add, then
the plot series form controller needs to get a version of the domain
object with the updated composition array, which it was not previously
doing.

This fixes #2120.
2018-07-31 12:03:49 -07:00
tobiasbrown
a1d206bfc3 [Remove] Add confirmation dialog (#1870)
* [Remove] Added confirmation dialog before the remove action is performed

Addresses #563
2018-07-27 13:54:41 -07:00
Pete Richards
ef9c6d5fed [Plot] Prevent duplicate query on bounds change
Bounds change triggers a clearing of plot history, which triggers
a user interaction change, which was triggering a second query.

This change sets a flag to prevent the requery from the user interaction on
bounds change.  This flag could potentially be reused elsewhere, e.g. if we
wanted to prevent requery when not utilizing a minmax data source.

fixes #2126
2018-07-25 11:44:14 -07:00
14 changed files with 821 additions and 236 deletions

View File

@@ -33,6 +33,7 @@ define([
"./src/actions/SaveAndStopEditingAction",
"./src/actions/SaveAsAction",
"./src/actions/CancelAction",
"./src/actions/CreateNewFolderAction",
"./src/policies/EditActionPolicy",
"./src/policies/EditPersistableObjectsPolicy",
"./src/policies/EditableLinkPolicy",
@@ -71,6 +72,7 @@ define([
SaveAndStopEditingAction,
SaveAsAction,
CancelAction,
CreateNewFolderAction,
EditActionPolicy,
EditPersistableObjectsPolicy,
EditableLinkPolicy,
@@ -145,7 +147,10 @@ define([
"depends": [
"$scope",
"$timeout",
"objectService"
"objectService",
"typeService",
"policyService",
"instantiate"
]
}
],
@@ -188,6 +193,7 @@ define([
"name": "Remove",
"description": "Remove this object from its containing object.",
"depends": [
"dialogService",
"navigationService"
]
},
@@ -241,6 +247,14 @@ define([
"cssClass": "icon-x no-label",
"description": "Discard changes made to these objects.",
"depends": []
},
{
"key": "create-new-folder",
"implementation": CreateNewFolderAction,
"description": "Creates a new folder.",
"depends": [
"typeService"
]
}
],
"policies": [

View File

@@ -0,0 +1,33 @@
<!--span
Open MCT, Copyright (c) 2014-2018, 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.
-->
<div name="newFolder" ng-controller="LocatorController">
<div ng-if="!createNewFolder">
<a class="s-button icon-folder-new" ng-click="createNewFolderClickHandler()" >
<span class="title-label">New Folder</span>
</a>
</div>
<div ng-if="createNewFolder">
<span><input type="text" ng-model="newFolderName" name="newFolderName"></span>
<a class="s-button" ng-click="createClickHandler()">Create</a>
<a class="s-button icon-x" ng-click="cancelClickHandler()"></a>
</div>
</div>

View File

@@ -1,19 +1,16 @@
<!--
Open MCT, Copyright (c) 2014-2018, United States Government
Open MCT, Copyright (c) 2014-2017, 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
@@ -26,4 +23,22 @@
ng-model="treeModel">
</mct-representation>
</div>
<!-- Create New Folder Action -->
<div class="newFolderCreation" style="margin-top:10px; position:absolute; left:0px;">
<!-- New folder button, triggers create new folder action. -->
<div ng-show="!newFolderCreationTriggered">
<a class="s-button icon-folder-new" ng-class="{disabled: !validParent()}" ng-click="newFolderButtonClickHandler()">
<span class="text-label">New Folder</span>
</a>
</div>
<!-- Get folder name -->
<div ng-show="newFolderCreationTriggered">
<input type="text" ng-model="newFolderNameInput">
<a class="s-button" ng-class="{ disabled: !validParent() || !validFolderName() }" ng-click="newFolderCreateButtonClickHandler()">Create</a>
<a class="s-button icon-x" ng-click="newFolderCancelButtonClickHandler()"></a>
</div>
</div>
</div>

View File

@@ -0,0 +1,88 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, 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.
*****************************************************************************/
define([
],
function (
) {
/**
* The CreateNewFolderAction; action is triggered by the new folder button in the locator.
*
* @constructor
* @implements {Action}
* @memberof platform/commonUI/edit
*/
function CreateNewFolderAction(
typeService,
context
) {
this.parent = (context || {}).domainObject;
this.typeService = typeService;
this.type = typeService.getType('folder');
}
CreateNewFolderAction.prototype.perform = function (folderName) {
var parent = this.parent,
typeService = this.typeService,
newModel = this.type.getInitialModel(),
folderType = typeService.getType('folder');
newModel.type = folderType.getKey();
newModel.name = folderName;
function instantiateObject(newModel, parent) {
var newObject = parent.useCapability('instantiation', newModel);
newObject.useCapability('mutation', function (newModel) {
newModel.location = parent.getId();
});
return addToParentAndReturn(newObject);
}
function addToParentAndReturn(newObject) {
return parent.getCapability('composition').add(newObject)
.then(function () {
return newObject;
});
}
return instantiateObject(newModel, parent);
}
/**
* Check if this action is applicable in a given context.
* This will ensure that a domain object is present in the context,
* and that this domain object is in Edit mode.
* @returns true if applicable
*/
CreateNewFolderAction.appliesTo = function (context) {
var parent = (context || {}).domainObject;
return parent && parent.hasCapability('editor');
};
return CreateNewFolderAction;
}
);

View File

@@ -23,111 +23,119 @@
/**
* Module defining RemoveAction. Created by vwoeltje on 11/17/14.
*/
define(
[],
function () {
define([
'./RemoveDialog'
], function (
RemoveDialog
) {
/**
* Construct an action which will remove the provided object manifestation.
* The object will be removed from its parent's composition; the parent
* is looked up via the "context" capability (so this will be the
* immediate ancestor by which this specific object was reached.)
*
* @param {DomainObject} object the object to be removed
* @param {ActionContext} context the context in which this action is performed
* @memberof platform/commonUI/edit
* @constructor
* @implements {Action}
/**
* Construct an action which will remove the provided object manifestation.
* The object will be removed from its parent's composition; the parent
* is looked up via the "context" capability (so this will be the
* immediate ancestor by which this specific object was reached.)
*
* @param {DialogService} dialogService a service which will show the dialog
* @param {NavigationService} navigationService a service that maintains the current navigation state
* @param {ActionContext} context the context in which this action is performed
* @memberof platform/commonUI/edit
* @constructor
* @implements {Action}
*/
function RemoveAction(dialogService, navigationService, context) {
this.domainObject = (context || {}).domainObject;
this.dialogService = dialogService;
this.navigationService = navigationService;
}
/**
* Perform this action.
*/
RemoveAction.prototype.perform = function () {
var dialog,
dialogService = this.dialogService,
domainObject = this.domainObject,
navigationService = this.navigationService;
/*
* Check whether an object ID matches the ID of the object being
* removed (used to filter a parent's composition to handle the
* removal.)
*/
function RemoveAction(navigationService, context) {
this.domainObject = (context || {}).domainObject;
this.navigationService = navigationService;
function isNotObject(otherObjectId) {
return otherObjectId !== domainObject.getId();
}
/**
* Perform this action.
* @return {Promise} a promise which will be
* fulfilled when the action has completed.
/*
* Mutate a parent object such that it no longer contains the object
* which is being removed.
*/
RemoveAction.prototype.perform = function () {
var navigationService = this.navigationService,
domainObject = this.domainObject;
/*
* Check whether an object ID matches the ID of the object being
* removed (used to filter a parent's composition to handle the
* removal.)
*/
function isNotObject(otherObjectId) {
return otherObjectId !== domainObject.getId();
}
function doMutate(model) {
model.composition = model.composition.filter(isNotObject);
}
/*
* Mutate a parent object such that it no longer contains the object
* which is being removed.
*/
function doMutate(model) {
model.composition = model.composition.filter(isNotObject);
}
/*
* Checks current object and ascendants of current
* object with object being removed, if the current
* object or any in the current object's path is being removed,
* navigate back to parent of removed object.
*/
function checkObjectNavigation(object, parentObject) {
// Traverse object starts at current location
var traverseObject = (navigationService).getNavigation(),
context;
/*
* Checks current object and ascendants of current
* object with object being removed, if the current
* object or any in the current object's path is being removed,
* navigate back to parent of removed object.
*/
function checkObjectNavigation(object, parentObject) {
// Traverse object starts at current location
var traverseObject = (navigationService).getNavigation(),
context;
// Stop when object is not defined (above ROOT)
while (traverseObject) {
// If object currently traversed to is object being removed
// navigate to parent of current object and then exit loop
if (traverseObject.getId() === object.getId()) {
navigationService.setNavigation(parentObject);
return;
}
// Traverses to parent of current object, moving
// up the ascendant path
context = traverseObject.getCapability('context');
traverseObject = context && context.getParent();
// Stop when object is not defined (above ROOT)
while (traverseObject) {
// If object currently traversed to is object being removed
// navigate to parent of current object and then exit loop
if (traverseObject.getId() === object.getId()) {
navigationService.setNavigation(parentObject);
return;
}
// Traverses to parent of current object, moving
// up the ascendant path
context = traverseObject.getCapability('context');
traverseObject = context && context.getParent();
}
}
/*
* Remove the object from its parent, as identified by its context
* capability. Based on object's location and selected object's location
* user may be navigated to existing parent object
*/
function removeFromContext(object) {
var contextCapability = object.getCapability('context'),
parent = contextCapability.getParent();
/*
* Remove the object from its parent, as identified by its context
* capability. Based on object's location and selected object's location
* user may be navigated to existing parent object
*/
function removeFromContext(object) {
var contextCapability = object.getCapability('context'),
parent = contextCapability.getParent();
// If currently within path of removed object(s),
// navigates to existing object up tree
checkObjectNavigation(object, parent);
// If currently within path of removed object(s),
// navigates to existing object up tree
checkObjectNavigation(object, parent);
return parent.useCapability('mutation', doMutate);
}
return parent.useCapability('mutation', doMutate);
}
return removeFromContext(domainObject);
};
/*
* Pass in the function to remove the domain object so it can be
* associated with an 'OK' button press
*/
dialog = new RemoveDialog(dialogService, domainObject, removeFromContext);
dialog.show();
};
// Object needs to have a parent for Remove to be applicable
RemoveAction.appliesTo = function (context) {
var object = (context || {}).domainObject,
contextCapability = object && object.getCapability("context"),
parent = contextCapability && contextCapability.getParent(),
parentType = parent && parent.getCapability('type'),
parentCreatable = parentType && parentType.hasFeature('creation');
// Object needs to have a parent for Remove to be applicable
RemoveAction.appliesTo = function (context) {
var object = (context || {}).domainObject,
contextCapability = object && object.getCapability("context"),
parent = contextCapability && contextCapability.getParent(),
parentType = parent && parent.getCapability('type'),
parentCreatable = parentType && parentType.hasFeature('creation');
// Only creatable types should be modifiable
return parent !== undefined &&
Array.isArray(parent.getModel().composition) &&
parentCreatable;
};
// Only creatable types should be modifiable
return parent !== undefined &&
Array.isArray(parent.getModel().composition) &&
parentCreatable;
};
return RemoveAction;
}
);
return RemoveAction;
});

View File

@@ -0,0 +1,77 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
define([], function () {
/**
* @callback removeCallback
* @param {DomainObject} domainObject the domain object to be removed
*/
/**
* Construct a new Remove dialog.
*
* @param {DialogService} dialogService the service that shows the dialog
* @param {DomainObject} domainObject the domain object to be removed
* @param {removeCallback} removeCallback callback that handles removal of the domain object
* @memberof platform/commonUI/edit
* @constructor
*/
function RemoveDialog(dialogService, domainObject, removeCallback) {
this.dialogService = dialogService;
this.domainObject = domainObject;
this.removeCallback = removeCallback;
}
/**
* Display a dialog to confirm the removal of a domain object.
*/
RemoveDialog.prototype.show = function () {
var dialog,
domainObject = this.domainObject,
removeCallback = this.removeCallback,
model = {
title: 'Remove ' + domainObject.getModel().name,
actionText: 'Warning! This action will permanently remove this object. Are you sure you want to continue?',
severity: 'alert',
primaryOption: {
label: 'OK',
callback: function () {
removeCallback(domainObject);
dialog.dismiss();
}
},
options: [
{
label: 'Cancel',
callback: function () {
dialog.dismiss();
}
}
]
};
dialog = this.dialogService.showBlockingMessage(model);
};
return RemoveDialog;
});

View File

@@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@@ -19,6 +19,7 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global console*/
define(
[],
@@ -31,7 +32,7 @@ define(
* @memberof platform/commonUI/browse
* @constructor
*/
function LocatorController($scope, $timeout, objectService) {
function LocatorController($scope, $timeout, objectService, typeService, policyService, instantiate) {
// Populate values needed by the locator control. These are:
// * rootObject: The top-level object, since we want to show
// the full tree
@@ -66,8 +67,8 @@ define(
// Restrict which locations can be selected
if (domainObject &&
$scope.structure &&
$scope.structure.validate) {
$scope.structure &&
$scope.structure.validate) {
if (!$scope.structure.validate(domainObject)) {
setLocatingObject(priorObject, undefined);
return;
@@ -81,11 +82,64 @@ define(
!!$scope.treeModel.selectedObject
);
}
// Check if create new folder is a valid action for selected object
$scope.validParent = function () {
if ($scope.treeModel.selectedObject) {
return policyService.allow(
"composition",
$scope.treeModel.selectedObject,
instantiate(typeService.getType('folder').getInitialModel())
);
} else {
return false;
}
}
}
$scope.newFolderButtonClickHandler = function () {
$scope.newFolderCreationTriggered = true;
};
$scope.newFolderCancelButtonClickHandler = function () {
$scope.newFolderCreationTriggered = false;
resetNewFolderNameInput();
};
// Get expected input pattern for folder name
var folderNamePattern = new RegExp(
typeService.getType('folder').getProperties()[0].propertyDefinition.pattern
);
// Validate folder name externally to avoid affecting overall form validation
$scope.validFolderName = function () {
return $scope.newFolderNameInput && folderNamePattern.test($scope.newFolderNameInput);
};
function selectAndScrollToNewFolder(newFolder) {
$scope.treeModel.selectedObject = newFolder;
}
function resetNewFolderNameInput() {
$scope.newFolderNameInput = "Unnamed Folder";
$scope.newFolderCreationTriggered = false;
}
// Create new folder, update selection to new folder and reset new folder button
$scope.newFolderCreateButtonClickHandler = function () {
createNewFolderAction = $scope.treeModel.selectedObject.getCapability('action').getActions('create-new-folder')[0];
createNewFolderAction.perform($scope.newFolderNameInput)
.then(selectAndScrollToNewFolder)
.then(resetNewFolderNameInput);
};
// Initial state for the tree's model
$scope.treeModel =
{ selectedObject: $scope.ngModel[$scope.field] };
$scope.treeModel = { selectedObject: $scope.ngModel[$scope.field] };
//Initial values for new folder action
$scope.newFolderNameInput = "Unnamed Folder";
$scope.newFolderCreationTriggered = false;
// Watch for changes from the tree
$scope.$watch("treeModel.selectedObject", setLocatingObject);
@@ -94,4 +148,3 @@ define(
return LocatorController;
}
);

View File

@@ -0,0 +1,132 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, 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.
*****************************************************************************/
define(
['../../src/actions/CreateNewFolderAction'],
function (CreateNewFolderAction) {
describe("The Create New Folder Action", function () {
var mockDomainObject,
mockNewObject,
mockType,
testModel,
mockFolderName,
mockTypeService,
mockActionContext,
capabilities,
mockCompositionCapability,
action;
function mockPromise(value) {
return (value && value.then) ? value : {
then: function (callback) {
return mockPromise(callback(value));
}
};
}
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj(
"domainObject",
[
"getCapability",
"useCapability",
"hasCapability",
"getId"
]
);
mockNewObject = jasmine.createSpyObj(
"newObject",
[
"getCapability",
"useCapability",
"hasCapability",
"getId"
]
);
mockType = jasmine.createSpyObj(
"type",
[
"getKey",
"getInitialModel"
]
);
testModel = {
type: mockType,
name: "Name",
location: "someLocation"
};
mockFolderName = "Name";
mockTypeService = jasmine.createSpyObj(
"typeService",
["getType"]
);
mockActionContext = { domainObject: mockDomainObject }
mockCompositionCapability = jasmine.createSpyObj(
"composition",
["add"]
);
mockType.getKey.and.returnValue("test");
mockType.getInitialModel.and.returnValue(testModel);
mockDomainObject.getCapability.and.callFake(function (capability) {
return (capability === 'composition') && mockCompositionCapability;
});
mockDomainObject.hasCapability.and.returnValue(true);
mockCompositionCapability.add.and.returnValue(mockPromise(true));
mockDomainObject.useCapability.and.callFake(function (capability) {
return (capability === 'instantiation') && mockNewObject;
});
mockTypeService.getType.and.returnValue(mockType);
mockDomainObject.getId.and.returnValue("id");
action = new CreateNewFolderAction(mockTypeService, mockActionContext);
});
it("uses the instantiation capability when performed", function () {
action.perform(mockFolderName);
expect(mockDomainObject.useCapability)
.toHaveBeenCalledWith("instantiation", jasmine.any(Object));
});
it("adds new objects to the parent's composition", function () {
action.perform(mockFolderName);
expect(mockDomainObject.getCapability).toHaveBeenCalledWith("composition");
expect(mockCompositionCapability.add).toHaveBeenCalled();
});
it("is only applicable when a domain object is in context", function () {
expect(CreateNewFolderAction.appliesTo(mockActionContext)).toBeTruthy();
expect(CreateNewFolderAction.appliesTo({})).toBeFalsy();
expect(mockDomainObject.hasCapability).toHaveBeenCalledWith('editor');
});
});
}
);

View File

@@ -25,50 +25,37 @@ define(
function (RemoveAction) {
describe("The Remove action", function () {
var mockQ,
mockNavigationService,
mockDomainObject,
mockParent,
mockChildObject,
mockGrandchildObject,
mockRootObject,
mockContext,
mockChildContext,
mockGrandchildContext,
mockRootContext,
mockMutation,
mockType,
var action,
actionContext,
model,
capabilities,
action;
function mockPromise(value) {
return {
then: function (callback) {
return mockPromise(callback(value));
}
};
}
mockContext,
mockDialogService,
mockDomainObject,
mockMutation,
mockNavigationService,
mockParent,
mockType,
model;
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj(
"domainObject",
["getId", "getCapability"]
["getId", "getCapability", "getModel"]
);
mockChildObject = jasmine.createSpyObj(
"domainObject",
["getId", "getCapability"]
);
mockGrandchildObject = jasmine.createSpyObj(
"domainObject",
["getId", "getCapability"]
);
mockRootObject = jasmine.createSpyObj(
"domainObject",
["getId", "getCapability"]
);
mockQ = { when: mockPromise };
mockMutation = jasmine.createSpyObj("mutation", ["invoke"]);
mockType = jasmine.createSpyObj("type", ["hasFeature"]);
mockType.hasFeature.and.returnValue(true);
capabilities = {
mutation: mockMutation,
type: mockType
};
model = {
composition: ["a", "test", "b"]
};
mockParent = {
getModel: function () {
return model;
@@ -80,12 +67,12 @@ define(
return capabilities[k].invoke(v);
}
};
mockContext = jasmine.createSpyObj("context", ["getParent"]);
mockChildContext = jasmine.createSpyObj("context", ["getParent"]);
mockGrandchildContext = jasmine.createSpyObj("context", ["getParent"]);
mockRootContext = jasmine.createSpyObj("context", ["getParent"]);
mockMutation = jasmine.createSpyObj("mutation", ["invoke"]);
mockType = jasmine.createSpyObj("type", ["hasFeature"]);
mockDialogService = jasmine.createSpyObj(
"dialogService",
["showBlockingMessage"]
);
mockNavigationService = jasmine.createSpyObj(
"navigationService",
[
@@ -97,23 +84,19 @@ define(
);
mockNavigationService.getNavigation.and.returnValue(mockDomainObject);
mockContext = jasmine.createSpyObj("context", ["getParent"]);
mockContext.getParent.and.returnValue(mockParent);
mockDomainObject.getId.and.returnValue("test");
mockDomainObject.getCapability.and.returnValue(mockContext);
mockDomainObject.getModel.and.returnValue({name: 'test object'});
mockContext.getParent.and.returnValue(mockParent);
mockType.hasFeature.and.returnValue(true);
capabilities = {
mutation: mockMutation,
type: mockType
};
model = {
composition: ["a", "test", "b"]
};
actionContext = { domainObject: mockDomainObject };
action = new RemoveAction(mockNavigationService, actionContext);
action = new RemoveAction(mockDialogService, mockNavigationService, actionContext);
});
it("only applies to objects with parents", function () {
@@ -127,83 +110,146 @@ define(
expect(mockType.hasFeature).toHaveBeenCalledWith('creation');
});
it("mutates the parent when performed", function () {
action.perform();
expect(mockMutation.invoke)
.toHaveBeenCalledWith(jasmine.any(Function));
});
it("changes composition from its mutation function", function () {
var mutator, result;
action.perform();
mutator = mockMutation.invoke.calls.mostRecent().args[0];
result = mutator(model);
// Should not have cancelled the mutation
expect(result).not.toBe(false);
// Simulate mutate's behavior (remove can either return a
// new model or modify this one in-place)
result = result || model;
// Should have removed "test" - that was our
// mock domain object's id.
expect(result.composition).toEqual(["a", "b"]);
});
it("removes parent of object currently navigated to", function () {
// Navigates to child object
mockNavigationService.getNavigation.and.returnValue(mockChildObject);
// Test is id of object being removed
// Child object has different id
mockDomainObject.getId.and.returnValue("test");
mockChildObject.getId.and.returnValue("not test");
// Sets context for the child and domainObject
mockDomainObject.getCapability.and.returnValue(mockContext);
mockChildObject.getCapability.and.returnValue(mockChildContext);
// Parents of child and domainObject are set
mockContext.getParent.and.returnValue(mockParent);
mockChildContext.getParent.and.returnValue(mockDomainObject);
mockType.hasFeature.and.returnValue(true);
it("shows a blocking message dialog", function () {
mockParent = jasmine.createSpyObj(
"parent",
["getModel", "getCapability", "useCapability"]
);
action.perform();
// Expects navigation to parent of domainObject (removed object)
expect(mockNavigationService.setNavigation).toHaveBeenCalledWith(mockParent);
expect(mockDialogService.showBlockingMessage).toHaveBeenCalled();
// Also check that no mutation happens at this point
expect(mockParent.useCapability).not.toHaveBeenCalledWith("mutation", jasmine.any(Function));
});
it("checks if removing object not in ascendent path (reaches ROOT)", function () {
// Navigates to grandchild of ROOT
mockNavigationService.getNavigation.and.returnValue(mockGrandchildObject);
describe("after the remove callback is triggered", function () {
var mockChildContext,
mockChildObject,
mockDialogHandle,
mockGrandchildContext,
mockGrandchildObject,
mockRootContext,
mockRootObject;
// domainObject (grandparent) is set as ROOT, child and grandchild
// are set objects not being removed
mockDomainObject.getId.and.returnValue("test 1");
mockRootObject.getId.and.returnValue("ROOT");
mockChildObject.getId.and.returnValue("not test 2");
mockGrandchildObject.getId.and.returnValue("not test 3");
beforeEach(function () {
mockChildObject = jasmine.createSpyObj(
"domainObject",
["getId", "getCapability"]
);
// Sets context for the grandchild, child, and domainObject
mockRootObject.getCapability.and.returnValue(mockRootContext);
mockChildObject.getCapability.and.returnValue(mockChildContext);
mockGrandchildObject.getCapability.and.returnValue(mockGrandchildContext);
mockDialogHandle = jasmine.createSpyObj(
"dialogHandle",
["dismiss"]
);
// Parents of grandchild and child are set
mockChildContext.getParent.and.returnValue(mockRootObject);
mockGrandchildContext.getParent.and.returnValue(mockChildObject);
mockGrandchildObject = jasmine.createSpyObj(
"domainObject",
["getId", "getCapability"]
);
mockType.hasFeature.and.returnValue(true);
mockRootObject = jasmine.createSpyObj(
"domainObject",
["getId", "getCapability"]
);
action.perform();
mockChildContext = jasmine.createSpyObj("context", ["getParent"]);
mockGrandchildContext = jasmine.createSpyObj("context", ["getParent"]);
mockRootContext = jasmine.createSpyObj("context", ["getParent"]);
mockDialogService.showBlockingMessage.and.returnValue(mockDialogHandle);
});
it("mutates the parent when performed", function () {
action.perform();
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
.primaryOption.callback();
expect(mockMutation.invoke)
.toHaveBeenCalledWith(jasmine.any(Function));
});
it("changes composition from its mutation function", function () {
var mutator, result;
action.perform();
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
.primaryOption.callback();
mutator = mockMutation.invoke.calls.mostRecent().args[0];
result = mutator(model);
// Should not have cancelled the mutation
expect(result).not.toBe(false);
// Simulate mutate's behavior (remove can either return a
// new model or modify this one in-place)
result = result || model;
// Should have removed "test" - that was our
// mock domain object's id.
expect(result.composition).toEqual(["a", "b"]);
});
it("removes parent of object currently navigated to", function () {
// Navigates to child object
mockNavigationService.getNavigation.and.returnValue(mockChildObject);
// Test is id of object being removed
// Child object has different id
mockDomainObject.getId.and.returnValue("test");
mockChildObject.getId.and.returnValue("not test");
// Sets context for the child and domainObject
mockDomainObject.getCapability.and.returnValue(mockContext);
mockChildObject.getCapability.and.returnValue(mockChildContext);
// Parents of child and domainObject are set
mockContext.getParent.and.returnValue(mockParent);
mockChildContext.getParent.and.returnValue(mockDomainObject);
mockType.hasFeature.and.returnValue(true);
action.perform();
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
.primaryOption.callback();
// Expects navigation to parent of domainObject (removed object)
expect(mockNavigationService.setNavigation).toHaveBeenCalledWith(mockParent);
});
it("checks if removing object not in ascendent path (reaches ROOT)", function () {
// Navigates to grandchild of ROOT
mockNavigationService.getNavigation.and.returnValue(mockGrandchildObject);
// domainObject (grandparent) is set as ROOT, child and grandchild
// are set objects not being removed
mockDomainObject.getId.and.returnValue("test 1");
mockRootObject.getId.and.returnValue("ROOT");
mockChildObject.getId.and.returnValue("not test 2");
mockGrandchildObject.getId.and.returnValue("not test 3");
// Sets context for the grandchild, child, and domainObject
mockRootObject.getCapability.and.returnValue(mockRootContext);
mockChildObject.getCapability.and.returnValue(mockChildContext);
mockGrandchildObject.getCapability.and.returnValue(mockGrandchildContext);
// Parents of grandchild and child are set
mockChildContext.getParent.and.returnValue(mockRootObject);
mockGrandchildContext.getParent.and.returnValue(mockChildObject);
mockType.hasFeature.and.returnValue(true);
action.perform();
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
.primaryOption.callback();
// Expects no navigation to occur
expect(mockNavigationService.setNavigation).not.toHaveBeenCalled();
});
// Expects no navigation to occur
expect(mockNavigationService.setNavigation).not.toHaveBeenCalled();
});
});
}
);

View File

@@ -31,26 +31,64 @@ define(
var mockScope,
mockTimeout,
mockDomainObject,
mockFolderObject,
mockRootObject,
mockContext,
mockActions,
mockObjectService,
mockTypeService,
mockType,
mockInstantiate,
mockPolicyService,
getObjectsPromise,
testModel,
capabilities,
mockCreateNewFolderAction,
mockActionCapability,
mockProperties,
controller;
beforeEach(function () {
mockScope = jasmine.createSpyObj(
"$scope",
["$watch"]
["$watch", "validParent"]
);
mockTimeout = jasmine.createSpy("$timeout");
mockInstantiate = jasmine.createSpy("instantiate");
mockDomainObject = jasmine.createSpyObj(
"domainObject",
["getCapability"]
[
"useCapability",
"getModel",
"getCapability"
]
);
mockFolderObject = jasmine.createSpyObj(
"folderObject",
[
"useCapability",
"getModel",
"getCapability"
]
);
mockCreateNewFolderAction = jasmine.createSpyObj(
"createNewFolderAction",
[
"perform"
]
);
mockRootObject = jasmine.createSpyObj(
"rootObject",
["getCapability"]
);
mockActionCapability = jasmine.createSpyObj(
"actionCapability",
[
"getActions",
"perform"
]
);
mockContext = jasmine.createSpyObj(
"context",
["getRoot"]
@@ -59,25 +97,74 @@ define(
"objectService",
["getObjects"]
);
mockTypeService = jasmine.createSpyObj(
"typeService",
["getType"]
);
mockPolicyService = jasmine.createSpyObj(
"policyService",
["allow"]
);
getObjectsPromise = jasmine.createSpyObj(
"promise",
["then"]
);
mockDomainObject.getCapability.and.returnValue(mockContext);
mockType = jasmine.createSpyObj(
"type",
[
"getKey",
"getProperties",
"getInitialModel"
]
);
testModel = { someKey: "some value" };
mockProperties = ['a', 'b', 'c'].map(function (k) {
var mockProperty = jasmine.createSpyObj(
'property-' + k,
['propertyDefinition']
);
mockProperty.propertyDefinition = {
key: "name",
pattern: "test"
}
return mockProperty;
});
capabilities = {
"action" : mockActionCapability,
"context": mockContext
};
mockActions = [mockCreateNewFolderAction];
mockContext.getRoot.and.returnValue(mockRootObject);
mockObjectService.getObjects.and.returnValue(getObjectsPromise);
mockTypeService.getType.and.callFake(function (typename) {
return mockType;
});
mockInstantiate.and.returnValue(mockFolderObject)
mockType.getKey.and.returnValue("test");
mockType.getInitialModel.and.returnValue(testModel);
mockType.getProperties.and.returnValue(mockProperties);
mockDomainObject.getCapability.and.callFake(function (capability) {
return capabilities[capability];
});
mockDomainObject.useCapability.and.returnValue();
mockDomainObject.getModel.and.returnValue(testModel);
mockFolderObject.getCapability.and.returnValue(capabilities);
mockFolderObject.useCapability.and.returnValue();
mockFolderObject.getModel.and.returnValue(testModel);
mockScope.ngModel = {};
mockScope.field = "someField";
controller = new LocatorController(mockScope, mockTimeout, mockObjectService);
controller = new LocatorController(mockScope, mockTimeout, mockObjectService, mockTypeService, mockPolicyService, mockInstantiate);
});
describe("when context is available", function () {
beforeEach(function () {
mockContext.getRoot.and.returnValue(mockRootObject);
controller = new LocatorController(mockScope, mockTimeout, mockObjectService);
controller = new LocatorController(mockScope, mockTimeout, mockObjectService, mockTypeService, mockPolicyService, mockInstantiate);
});
it("adds a treeModel to scope", function () {
@@ -136,6 +223,32 @@ define(
expect(mockScope.ngModelController.$setValidity)
.toHaveBeenCalledWith(jasmine.any(String), false);
});
it("Checks if new folder could be created with policies", function () {
// validParent set to true if policy allows creation of new folders
mockPolicyService.allow.and.returnValue(true);
expect(mockPolicyService.allow).toHaveBeenCalled;
expect(mockScope.validParent).toBeTruthy;
// validParent set to false if policy allows creation of new folders
mockPolicyService.allow.and.returnValue(false);
expect(mockPolicyService.allow).toHaveBeenCalled;
expect(mockScope.validParent).toBeFalsy;
});
it("Validates folder name input with folder properties", function () {
// Get foldername pattern from folder type properties
expect(mockTypeService.getType).toHaveBeenCalledWith('folder');
expect(mockType.getProperties).toHaveBeenCalled();
expect(mockScope.folderNamePattern).toBeDefined;
// Validate folder name input
mockScope.newFolderNameInput = "test";
expect(mockScope.validFolderName).toBeTruthy;
mockScope.newFolderNameInput = " ";
expect(mockScope.validFolderName).toBeFalsy;
});
});
describe("when no context is available", function () {
var defaultRoot = "DEFAULT_ROOT";
@@ -145,7 +258,7 @@ define(
getObjectsPromise.then.and.callFake(function (callback) {
callback({'ROOT': defaultRoot});
});
controller = new LocatorController(mockScope, mockTimeout, mockObjectService);
controller = new LocatorController(mockScope, mockTimeout, mockObjectService, mockTypeService, mockPolicyService, mockInstantiate);
});
it("provides a default context where none is available", function () {

View File

@@ -83,6 +83,7 @@ define([
this.listenTo(this, 'change:xKey', this.onXKeyChange, this);
this.listenTo(this, 'change:yKey', this.onYKeyChange, this);
this.persistedConfig = options.persistedConfig;
Model.apply(this, arguments);
this.onXKeyChange(this.get('xKey'));
@@ -176,8 +177,7 @@ define([
return;
}
var valueMetadata = this.metadata.value(newKey);
var persistedConfig = this.get('persistedConfiguration');
if (!persistedConfig || !persistedConfig.interpolate) {
if (!this.persistedConfig || !this.persistedConfig.interpolate) {
if (valueMetadata.format === 'enum') {
this.set('interpolate', 'stepAfter');
} else {

View File

@@ -54,7 +54,7 @@ define([
domainObject.configuration.series.forEach(function (seriesConfig) {
var series = this.byIdentifier(seriesConfig.identifier);
if (series) {
series.set('persistedConfiguration', seriesConfig);
series.persistedConfig = seriesConfig;
}
}, this);
},
@@ -90,7 +90,9 @@ define([
model: seriesConfig,
domainObject: domainObject,
collection: this,
openmct: this.openmct
openmct: this.openmct,
persistedConfig: this.plot
.getPersistedSeriesConfig(domainObject.identifier)
}));
},
removeTelemetryObject: function (identifier) {

View File

@@ -45,6 +45,7 @@ define([
PlotOptionsController.prototype.updateDomainObject = function (domainObject) {
this.domainObject = domainObject;
this.$scope.formDomainObject = domainObject;
};
PlotOptionsController.prototype.destroy = function () {
@@ -63,8 +64,7 @@ define([
this.config = this.$scope.config = config;
this.$scope.plotSeries = [];
this.domainObject = this.config.get('domainObject');
this.$scope.formDomainObject = this.domainObject;
this.updateDomainObject(this.config.get('domainObject'));
this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject.bind(this));
this.listenTo(this.$scope, '$destroy', this.destroy, this);

View File

@@ -181,7 +181,9 @@ define([
};
this.config.xAxis.set('range', newRange);
if (!isTick) {
this.skipReloadOnInteraction = true;
this.$scope.$broadcast('plot:clearHistory');
this.skipReloadOnInteraction = false;
this.loadMoreData(newRange, true);
} else {
// Drop any data that is more than 1x (max-min) before min.
@@ -234,7 +236,9 @@ define([
var xDisplayRange = this.config.xAxis.get('displayRange');
var xRange = this.config.xAxis.get('range');
this.loadMoreData(xDisplayRange);
if (!this.skipReloadOnInteraction) {
this.loadMoreData(xDisplayRange);
}
this.synchronized(xRange.min === xDisplayRange.min &&
xRange.max === xDisplayRange.max);