Merge branch 'master' into jscs-rebase-142

Conflicts:
	platform/commonUI/edit/test/actions/EditAndComposeActionSpec.js
	platform/representation/src/MCTRepresentation.js
This commit is contained in:
Victor Woeltjen 2016-05-20 13:07:58 -07:00
commit bce5643994
11 changed files with 122 additions and 102 deletions

View File

@ -22,17 +22,19 @@
#* at runtime from the About dialog for additional information. #* at runtime from the About dialog for additional information.
#***************************************************************************** #*****************************************************************************
# Script to build and deploy docs to github pages. # Script to build and deploy docs.
OUTPUT_DIRECTORY="target/docs" OUTPUT_DIRECTORY="target/docs"
REPOSITORY_URL="git@github.com:nasa/openmctweb.git" # Docs, once built, are pushed to the private website repo
REPOSITORY_URL="git@github.com:nasa/openmct-website.git"
WEBSITE_DIRECTORY="website"
BUILD_SHA=`git rev-parse head` BUILD_SHA=`git rev-parse HEAD`
# A remote will be created for the git repository we are pushing to. # A remote will be created for the git repository we are pushing to.
# Don't worry, as this entire directory will get trashed inbetween builds. # Don't worry, as this entire directory will get trashed inbetween builds.
REMOTE_NAME="documentation" REMOTE_NAME="documentation"
WEBSITE_BRANCH="gh-pages" WEBSITE_BRANCH="master"
# Clean output directory, JSDOC will recreate # Clean output directory, JSDOC will recreate
if [ -d $OUTPUT_DIRECTORY ]; then if [ -d $OUTPUT_DIRECTORY ]; then
@ -40,23 +42,21 @@ if [ -d $OUTPUT_DIRECTORY ]; then
fi fi
npm run docs npm run docs
cd $OUTPUT_DIRECTORY || exit 1
echo "git init" echo "git clone $REPOSITORY_URL website"
git init git clone $REPOSITORY_URL website || exit 1
echo "cp -r $OUTPUT_DIRECTORY $WEBSITE_DIRECTORY/docs"
cp -r $OUTPUT_DIRECTORY $WEBSITE_DIRECTORY/docs
echo "cd $WEBSITE_DIRECTORY"
cd $WEBSITE_DIRECTORY || exit 1
# Configure github for CircleCI user. # Configure github for CircleCI user.
git config user.email "buildbot@circleci.com" git config user.email "buildbot@circleci.com"
git config user.name "BuildBot" git config user.name "BuildBot"
echo "git remote add $REMOTE_NAME $REPOSITORY_URL"
git remote add $REMOTE_NAME $REPOSITORY_URL
echo "git add ." echo "git add ."
git add . git add .
echo "git commit -m \"Generate docs from build $BUILD_SHA\"" echo "git commit -m \"Docs updated from build $BUILD_SHA\""
git commit -m "Generate docs from build $BUILD_SHA" git commit -m "Docs updated from build $BUILD_SHA"
# Push to the website repo
echo "git push $REMOTE_NAME HEAD:$WEBSITE_BRANCH -f" git push
git push $REMOTE_NAME HEAD:$WEBSITE_BRANCH -f
echo "Documentation pushed to gh-pages branch."

View File

@ -1,8 +1,11 @@
deployment: deployment:
production: production:
branch: master branch: master
heroku: commands:
appname: openmctweb-demo - npm install canvas nomnoml
- ./build-docs.sh
- git fetch --unshallow
- git push git@heroku.com:openmctweb-demo.git $CIRCLE_SHA1:refs/heads/master
openmct-demo: openmct-demo:
branch: live_demo branch: live_demo
heroku: heroku:
@ -14,3 +17,8 @@ deployment:
test: test:
post: post:
- gulp lint - gulp lint
general:
branches:
ignore:
- gh-pages

View File

@ -26,7 +26,7 @@ define([
"./src/controllers/ElementsController", "./src/controllers/ElementsController",
"./src/controllers/EditObjectController", "./src/controllers/EditObjectController",
"./src/directives/MCTBeforeUnload", "./src/directives/MCTBeforeUnload",
"./src/actions/LinkAction", "./src/actions/EditAndComposeAction",
"./src/actions/EditAction", "./src/actions/EditAction",
"./src/actions/PropertiesAction", "./src/actions/PropertiesAction",
"./src/actions/RemoveAction", "./src/actions/RemoveAction",
@ -55,7 +55,7 @@ define([
ElementsController, ElementsController,
EditObjectController, EditObjectController,
MCTBeforeUnload, MCTBeforeUnload,
LinkAction, EditAndComposeAction,
EditAction, EditAction,
PropertiesAction, PropertiesAction,
RemoveAction, RemoveAction,
@ -126,7 +126,7 @@ define([
"actions": [ "actions": [
{ {
"key": "compose", "key": "compose",
"implementation": LinkAction "implementation": EditAndComposeAction
}, },
{ {
"key": "edit", "key": "edit",

View File

@ -31,13 +31,14 @@ define(
* @memberof platform/commonUI/edit * @memberof platform/commonUI/edit
* @implements {Action} * @implements {Action}
*/ */
function LinkAction(context) { function EditAndComposeAction(context) {
this.domainObject = (context || {}).domainObject; this.domainObject = (context || {}).domainObject;
this.selectedObject = (context || {}).selectedObject; this.selectedObject = (context || {}).selectedObject;
} }
LinkAction.prototype.perform = function () { EditAndComposeAction.prototype.perform = function () {
var self = this; var self = this,
editAction = this.domainObject.getCapability('action').getActions("edit")[0];
// Persist changes to the domain object // Persist changes to the domain object
function doPersist() { function doPersist() {
@ -54,9 +55,13 @@ define(
.then(doPersist); .then(doPersist);
} }
if (editAction) {
editAction.perform();
}
return this.selectedObject && doLink(); return this.selectedObject && doLink();
}; };
return LinkAction; return EditAndComposeAction;
} }
); );

View File

@ -81,17 +81,14 @@ define(
var key = action.getMetadata().key, var key = action.getMetadata().key,
category = (context || {}).category; category = (context || {}).category;
// Only worry about actions in the view-control category // Restrict 'edit' to cases where there are editable
if (category === 'view-control') { // views (similarly, restrict 'properties' to when
// Restrict 'edit' to cases where there are editable // the converse is true), and where the domain object is not
// views (similarly, restrict 'properties' to when // already being edited.
// the converse is true), and where the domain object is not if (key === 'edit') {
// already being edited. return this.countEditableViews(context) > 0 && !isEditing(context);
if (key === 'edit') { } else if (key === 'properties' && category === 'view-control') {
return this.countEditableViews(context) > 0 && !isEditing(context); return this.countEditableViews(context) < 1 && !isEditing(context);
} else if (key === 'properties') {
return this.countEditableViews(context) < 1 && !isEditing(context);
}
} }
// Like all policies, allow by default. // Like all policies, allow by default.

View File

@ -21,8 +21,8 @@
*****************************************************************************/ *****************************************************************************/
define( define(
["../../src/actions/LinkAction"], ["../../src/actions/EditAndComposeAction"],
function (LinkAction) { function (EditAndComposeAction) {
describe("The Link action", function () { describe("The Link action", function () {
var mockQ, var mockQ,
@ -31,6 +31,8 @@ define(
mockContext, mockContext,
mockComposition, mockComposition,
mockPersistence, mockPersistence,
mockActionCapability,
mockEditAction,
mockType, mockType,
actionContext, actionContext,
model, model,
@ -64,21 +66,26 @@ define(
return capabilities[k].invoke(v); return capabilities[k].invoke(v);
} }
}; };
mockContext = jasmine.createSpyObj("context", ["getParent"]); mockContext = jasmine.createSpyObj("context", [ "getParent" ]);
mockComposition = jasmine.createSpyObj("composition", ["invoke", "add"]); mockComposition = jasmine.createSpyObj("composition", [ "invoke", "add" ]);
mockPersistence = jasmine.createSpyObj("persistence", ["persist"]); mockPersistence = jasmine.createSpyObj("persistence", [ "persist" ]);
mockType = jasmine.createSpyObj("type", ["hasFeature"]); mockType = jasmine.createSpyObj("type", [ "hasFeature", "getKey" ]);
mockActionCapability = jasmine.createSpyObj("actionCapability", [ "getActions"]);
mockEditAction = jasmine.createSpyObj("editAction", ["perform"]);
mockDomainObject.getId.andReturn("test"); mockDomainObject.getId.andReturn("test");
mockDomainObject.getCapability.andReturn(mockContext); mockDomainObject.getCapability.andReturn(mockContext);
mockContext.getParent.andReturn(mockParent); mockContext.getParent.andReturn(mockParent);
mockType.hasFeature.andReturn(true); mockType.hasFeature.andReturn(true);
mockType.getKey.andReturn("layout");
mockComposition.invoke.andReturn(mockPromise(true)); mockComposition.invoke.andReturn(mockPromise(true));
mockComposition.add.andReturn(mockPromise(true)); mockComposition.add.andReturn(mockPromise(true));
mockActionCapability.getActions.andReturn([]);
capabilities = { capabilities = {
composition: mockComposition, composition: mockComposition,
persistence: mockPersistence, persistence: mockPersistence,
action: mockActionCapability,
type: mockType type: mockType
}; };
model = { model = {
@ -90,7 +97,7 @@ define(
selectedObject: mockDomainObject selectedObject: mockDomainObject
}; };
action = new LinkAction(actionContext); action = new EditAndComposeAction(actionContext);
}); });
@ -105,6 +112,21 @@ define(
expect(mockPersistence.persist).toHaveBeenCalled(); expect(mockPersistence.persist).toHaveBeenCalled();
}); });
it("enables edit mode for objects that have an edit action", function () {
mockActionCapability.getActions.andReturn([mockEditAction]);
action.perform();
expect(mockEditAction.perform).toHaveBeenCalled();
});
it("Does not enable edit mode for objects that do not have an" +
" edit action", function () {
mockActionCapability.getActions.andReturn([]);
action.perform();
expect(mockEditAction.perform).not.toHaveBeenCalled();
expect(mockComposition.add)
.toHaveBeenCalledWith(mockDomainObject);
});
}); });
} }
); );

View File

@ -32,7 +32,8 @@ define(
*/ */
function InspectorController($scope, policyService) { function InspectorController($scope, policyService) {
var domainObject = $scope.domainObject, var domainObject = $scope.domainObject,
typeCapability = domainObject.getCapability('type'); typeCapability = domainObject.getCapability('type'),
statusListener;
/** /**
* Filters region parts to only those allowed by region policies * Filters region parts to only those allowed by region policies
@ -50,6 +51,11 @@ define(
$scope.regions = filterRegions(typeCapability.getDefinition().inspector || new InspectorRegion()); $scope.regions = filterRegions(typeCapability.getDefinition().inspector || new InspectorRegion());
} }
statusListener = domainObject.getCapability("status").listen(setRegions);
$scope.$on("$destroy", function () {
statusListener();
});
setRegions(); setRegions();
} }

View File

@ -30,6 +30,8 @@ define(
mockTypeCapability, mockTypeCapability,
mockTypeDefinition, mockTypeDefinition,
mockPolicyService, mockPolicyService,
mockStatusCapability,
capabilities = {},
controller; controller;
beforeEach(function () { beforeEach(function () {
@ -47,19 +49,29 @@ define(
'getDefinition' 'getDefinition'
]); ]);
mockTypeCapability.getDefinition.andReturn(mockTypeDefinition); mockTypeCapability.getDefinition.andReturn(mockTypeDefinition);
capabilities.type = mockTypeCapability;
mockStatusCapability = jasmine.createSpyObj('statusCapability', [
'listen'
]);
capabilities.status = mockStatusCapability;
mockDomainObject = jasmine.createSpyObj('domainObject', [ mockDomainObject = jasmine.createSpyObj('domainObject', [
'getCapability' 'getCapability'
]); ]);
mockDomainObject.getCapability.andReturn(mockTypeCapability); mockDomainObject.getCapability.andCallFake(function (name) {
return capabilities[name];
});
mockPolicyService = jasmine.createSpyObj('policyService', [ mockPolicyService = jasmine.createSpyObj('policyService', [
'allow' 'allow'
]); ]);
mockScope = { mockScope = jasmine.createSpyObj('$scope',
domainObject: mockDomainObject ['$on']
}; );
mockScope.domainObject = mockDomainObject;
}); });
it("filters out regions disallowed by region policy", function () { it("filters out regions disallowed by region policy", function () {
@ -73,6 +85,25 @@ define(
controller = new InspectorController(mockScope, mockPolicyService); controller = new InspectorController(mockScope, mockPolicyService);
expect(mockScope.regions.length).toBe(2); expect(mockScope.regions.length).toBe(2);
}); });
it("Responds to status changes", function() {
mockPolicyService.allow.andReturn(true);
controller = new InspectorController(mockScope, mockPolicyService);
expect(mockScope.regions.length).toBe(2);
expect(mockStatusCapability.listen).toHaveBeenCalled();
mockPolicyService.allow.andReturn(false);
mockStatusCapability.listen.mostRecentCall.args[0]();
expect(mockScope.regions.length).toBe(0);
});
it("Unregisters status listener", function() {
var mockListener = jasmine.createSpy('listener');
mockStatusCapability.listen.andReturn(mockListener);
controller = new InspectorController(mockScope, mockPolicyService);
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", jasmine.any(Function));
mockScope.$on.mostRecentCall.args[1]();
expect(mockListener).toHaveBeenCalled();
});
}); });
} }
); );

View File

@ -88,7 +88,6 @@ define(
toClear = [], // Properties to clear out of scope on change toClear = [], // Properties to clear out of scope on change
counter = 0, counter = 0,
couldRepresent = false, couldRepresent = false,
couldEdit = false,
lastIdPath = [], lastIdPath = [],
lastKey, lastKey,
statusListener, statusListener,
@ -137,14 +136,13 @@ define(
}); });
} }
function unchanged(canRepresent, canEdit, idPath, key) { function unchanged(canRepresent, idPath, key) {
return (canRepresent === couldRepresent) && return (canRepresent === couldRepresent) &&
(key === lastKey) && (key === lastKey) &&
(idPath.length === lastIdPath.length) && (idPath.length === lastIdPath.length) &&
idPath.every(function (id, i) { idPath.every(function (id, i) {
return id === lastIdPath[i]; return id === lastIdPath[i];
}) && });
(canEdit === couldEdit);
} }
function getIdPath(domainObject) { function getIdPath(domainObject) {
@ -168,11 +166,10 @@ define(
representation = lookup($scope.key, domainObject), representation = lookup($scope.key, domainObject),
uses = ((representation || {}).uses || []), uses = ((representation || {}).uses || []),
canRepresent = !!(representation && domainObject), canRepresent = !!(representation && domainObject),
canEdit = !!(domainObject && domainObject.hasCapability('editor') && domainObject.getCapability('editor').inEditContext()),
idPath = getIdPath(domainObject), idPath = getIdPath(domainObject),
key = $scope.key; key = $scope.key;
if (unchanged(canRepresent, canEdit, idPath, key)) { if (unchanged(canRepresent, idPath, key)) {
return; return;
} }
@ -201,7 +198,6 @@ define(
// To allow simplified change detection next time around // To allow simplified change detection next time around
couldRepresent = canRepresent; couldRepresent = canRepresent;
lastIdPath = idPath; lastIdPath = idPath;
couldEdit = canEdit;
lastKey = key; lastKey = key;
// Populate scope with fields associated with the current // Populate scope with fields associated with the current
@ -239,26 +235,7 @@ define(
// Also update when the represented domain object changes // Also update when the represented domain object changes
// (to a different object) // (to a different object)
$scope.$watch("domainObject", refresh); $scope.$watch("domainObject", refresh);
function listenForStatusChange(object) {
if (statusListener) {
statusListener();
}
statusListener = object.getCapability("status").listen(refresh);
}
/**
* Add a listener to the object for status changes.
*/
$scope.$watch("domainObject", function (domainObject, oldDomainObject) {
if (domainObject !== oldDomainObject) {
listenForStatusChange(domainObject);
}
});
if ($scope.domainObject) {
listenForStatusChange($scope.domainObject);
}
// Finally, also update when there is a new version of that // Finally, also update when there is a new version of that
// same domain object; these changes should be tracked in the // same domain object; these changes should be tracked in the
// model's "modified" field, by the mutation capability. // model's "modified" field, by the mutation capability.

View File

@ -54,9 +54,6 @@ define(
// ...and broadcast the event. This allows specific // ...and broadcast the event. This allows specific
// views to have post-drop behavior which depends on // views to have post-drop behavior which depends on
// drop position. // drop position.
// Also broadcast the editableDomainObject to
// avoid race condition against non-editable
// version in EditRepresenter
scope.$broadcast( scope.$broadcast(
GestureConstants.MCT_DROP_EVENT, GestureConstants.MCT_DROP_EVENT,
id, id,
@ -93,21 +90,13 @@ define(
function drop(e) { function drop(e) {
var event = (e || {}).originalEvent || e, var event = (e || {}).originalEvent || e,
id = event.dataTransfer.getData(GestureConstants.MCT_DRAG_TYPE), id = event.dataTransfer.getData(GestureConstants.MCT_DRAG_TYPE);
domainObjectType = domainObject.getModel().type;
// Handle the drop; add the dropped identifier to the // Handle the drop; add the dropped identifier to the
// destination domain object's composition, and persist // destination domain object's composition, and persist
// the change. // the change.
if (id) { if (id) {
e.preventDefault(); e.preventDefault();
//Use scope.apply, drop event is outside digest cycle
scope.$apply(function () {
if (domainObjectType !== 'folder') {
domainObject.getCapability('action').perform('edit');
}
});
$q.when(action && action.perform()).then(function () { $q.when(action && action.perform()).then(function () {
broadcastDrop(id, event); broadcastDrop(id, event);
}); });

View File

@ -253,21 +253,6 @@ define(
expect(mockScope.testCapability).toBeUndefined(); expect(mockScope.testCapability).toBeUndefined();
}); });
it("registers a status change listener", function () {
mockScope.$watch.calls[2].args[1](mockDomainObject);
expect(mockStatusCapability.listen).toHaveBeenCalled();
});
it("unlistens for status change on scope destruction", function () {
var mockUnlistener = jasmine.createSpy("unlisten");
mockStatusCapability.listen.andReturn(mockUnlistener);
mockScope.$watch.calls[2].args[1](mockDomainObject);
expect(mockStatusCapability.listen).toHaveBeenCalled();
mockScope.$on.calls[1].args[1]();
expect(mockUnlistener).toHaveBeenCalled();
});
describe("when a domain object has been observed", function () { describe("when a domain object has been observed", function () {
var mockContext, var mockContext,
mockContext2, mockContext2,