mirror of
https://github.com/nasa/openmct.git
synced 2024-12-23 23:12:23 +00:00
Merge branch 'open1062' into open-master
Merge changes for WTD-1062
This commit is contained in:
commit
79f6e8c082
@ -108,7 +108,8 @@
|
|||||||
"templateUrl": "templates/items/items.html",
|
"templateUrl": "templates/items/items.html",
|
||||||
"uses": [ "composition" ],
|
"uses": [ "composition" ],
|
||||||
"gestures": [ "drop" ],
|
"gestures": [ "drop" ],
|
||||||
"type": "folder"
|
"type": "folder",
|
||||||
|
"editable": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"components": [
|
"components": [
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "properties",
|
"key": "properties",
|
||||||
"category": "contextual",
|
"category": ["contextual", "view-control"],
|
||||||
"implementation": "actions/PropertiesAction.js",
|
"implementation": "actions/PropertiesAction.js",
|
||||||
"glyph": "p",
|
"glyph": "p",
|
||||||
"name": "Edit Properties...",
|
"name": "Edit Properties...",
|
||||||
@ -75,6 +75,16 @@
|
|||||||
"depends": [ "$location" ]
|
"depends": [ "$location" ]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"policies": [
|
||||||
|
{
|
||||||
|
"category": "action",
|
||||||
|
"implementation": "policies/EditActionPolicy.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "view",
|
||||||
|
"implementation": "policies/EditableViewPolicy.js"
|
||||||
|
}
|
||||||
|
],
|
||||||
"templates": [
|
"templates": [
|
||||||
{
|
{
|
||||||
"key": "edit-library",
|
"key": "edit-library",
|
||||||
|
61
platform/commonUI/edit/src/policies/EditActionPolicy.js
Normal file
61
platform/commonUI/edit/src/policies/EditActionPolicy.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Policy controlling when the `edit` and/or `properties` actions
|
||||||
|
* can appear as applicable actions of the `view-control` category
|
||||||
|
* (shown as buttons in the top-right of browse mode.)
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function EditActionPolicy() {
|
||||||
|
// Get a count of views which are not flagged as non-editable.
|
||||||
|
function countEditableViews(context) {
|
||||||
|
var domainObject = (context || {}).domainObject,
|
||||||
|
views = domainObject && domainObject.useCapability('view'),
|
||||||
|
count = 0;
|
||||||
|
|
||||||
|
// A view is editable unless explicitly flagged as not
|
||||||
|
(views || []).forEach(function (view) {
|
||||||
|
count += (view.editable !== false) ? 1 : 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Check whether or not a given action is allowed by this
|
||||||
|
* policy.
|
||||||
|
* @param {Action} action the action
|
||||||
|
* @param context the context
|
||||||
|
* @returns {boolean} true if not disallowed
|
||||||
|
*/
|
||||||
|
allow: function (action, context) {
|
||||||
|
var key = action.getMetadata().key,
|
||||||
|
category = (context || {}).category;
|
||||||
|
|
||||||
|
// Only worry about actions in the view-control category
|
||||||
|
if (category === 'view-control') {
|
||||||
|
// Restrict 'edit' to cases where there are editable
|
||||||
|
// views (similarly, restrict 'properties' to when
|
||||||
|
// the converse is true)
|
||||||
|
if (key === 'edit') {
|
||||||
|
return countEditableViews(context) > 0;
|
||||||
|
} else if (key === 'properties') {
|
||||||
|
return countEditableViews(context) < 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like all policies, allow by default.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return EditActionPolicy;
|
||||||
|
}
|
||||||
|
);
|
36
platform/commonUI/edit/src/policies/EditableViewPolicy.js
Normal file
36
platform/commonUI/edit/src/policies/EditableViewPolicy.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Policy controlling which views should be visible in Edit mode.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function EditableViewPolicy() {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Check whether or not a given action is allowed by this
|
||||||
|
* policy.
|
||||||
|
* @param {Action} action the action
|
||||||
|
* @param domainObject the domain object which will be viewed
|
||||||
|
* @returns {boolean} true if not disallowed
|
||||||
|
*/
|
||||||
|
allow: function (view, domainObject) {
|
||||||
|
// If a view is flagged as non-editable, only allow it
|
||||||
|
// while we're not in Edit mode.
|
||||||
|
if ((view || {}).editable === false) {
|
||||||
|
return !domainObject.hasCapability('editor');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like all policies, allow by default.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return EditableViewPolicy;
|
||||||
|
}
|
||||||
|
);
|
78
platform/commonUI/edit/test/policies/EditActionPolicySpec.js
Normal file
78
platform/commonUI/edit/test/policies/EditActionPolicySpec.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/*global define,describe,it,expect,beforeEach,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../../src/policies/EditActionPolicy"],
|
||||||
|
function (EditActionPolicy) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("The Edit action policy", function () {
|
||||||
|
var editableView,
|
||||||
|
nonEditableView,
|
||||||
|
undefinedView,
|
||||||
|
testViews,
|
||||||
|
testContext,
|
||||||
|
mockDomainObject,
|
||||||
|
mockEditAction,
|
||||||
|
mockPropertiesAction,
|
||||||
|
policy;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockDomainObject = jasmine.createSpyObj(
|
||||||
|
'domainObject',
|
||||||
|
[ 'useCapability' ]
|
||||||
|
);
|
||||||
|
mockEditAction = jasmine.createSpyObj('edit', ['getMetadata']);
|
||||||
|
mockPropertiesAction = jasmine.createSpyObj('edit', ['getMetadata']);
|
||||||
|
|
||||||
|
editableView = { editable: true };
|
||||||
|
nonEditableView = { editable: false };
|
||||||
|
undefinedView = { someKey: "some value" };
|
||||||
|
testViews = [];
|
||||||
|
|
||||||
|
mockDomainObject.useCapability.andCallFake(function (c) {
|
||||||
|
// Provide test views, only for the view capability
|
||||||
|
return c === 'view' && testViews;
|
||||||
|
});
|
||||||
|
|
||||||
|
mockEditAction.getMetadata.andReturn({ key: 'edit' });
|
||||||
|
mockPropertiesAction.getMetadata.andReturn({ key: 'properties' });
|
||||||
|
|
||||||
|
testContext = {
|
||||||
|
domainObject: mockDomainObject,
|
||||||
|
category: 'view-control'
|
||||||
|
};
|
||||||
|
|
||||||
|
policy = new EditActionPolicy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows the edit action when there are editable views", function () {
|
||||||
|
testViews = [ editableView ];
|
||||||
|
expect(policy.allow(mockEditAction, testContext)).toBeTruthy();
|
||||||
|
// No edit flag defined; should be treated as editable
|
||||||
|
testViews = [ undefinedView, undefinedView ];
|
||||||
|
expect(policy.allow(mockEditAction, testContext)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows the edit properties action when there are no editable views", function () {
|
||||||
|
testViews = [ nonEditableView, nonEditableView ];
|
||||||
|
expect(policy.allow(mockPropertiesAction, testContext)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disallows the edit action when there are no editable views", function () {
|
||||||
|
testViews = [ nonEditableView, nonEditableView ];
|
||||||
|
expect(policy.allow(mockEditAction, testContext)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disallows the edit properties action when there are editable views", function () {
|
||||||
|
testViews = [ editableView ];
|
||||||
|
expect(policy.allow(mockPropertiesAction, testContext)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows the edit properties outside of the 'view-control' category", function () {
|
||||||
|
testViews = [ nonEditableView ];
|
||||||
|
testContext.category = "something-else";
|
||||||
|
expect(policy.allow(mockPropertiesAction, testContext)).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,56 @@
|
|||||||
|
/*global define,describe,it,expect,beforeEach,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../../src/policies/EditableViewPolicy"],
|
||||||
|
function (EditableViewPolicy) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("The editable view policy", function () {
|
||||||
|
var testView,
|
||||||
|
mockDomainObject,
|
||||||
|
testMode,
|
||||||
|
policy;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
testMode = true; // Act as if we're in Edit mode by default
|
||||||
|
mockDomainObject = jasmine.createSpyObj(
|
||||||
|
'domainObject',
|
||||||
|
['hasCapability']
|
||||||
|
);
|
||||||
|
mockDomainObject.hasCapability.andCallFake(function (c) {
|
||||||
|
return (c === 'editor') && testMode;
|
||||||
|
});
|
||||||
|
|
||||||
|
policy = new EditableViewPolicy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disallows views in edit mode that are flagged as non-editable", function () {
|
||||||
|
expect(policy.allow({ editable: false }, mockDomainObject))
|
||||||
|
.toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows views in edit mode that are flagged as editable", function () {
|
||||||
|
expect(policy.allow({ editable: true }, mockDomainObject))
|
||||||
|
.toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows any view outside of edit mode", function () {
|
||||||
|
var testViews = [
|
||||||
|
{ editable: false },
|
||||||
|
{ editable: true },
|
||||||
|
{ someKey: "some value" }
|
||||||
|
];
|
||||||
|
testMode = false; // Act as if we're not in Edit mode
|
||||||
|
|
||||||
|
testViews.forEach(function (testView) {
|
||||||
|
expect(policy.allow(testView, mockDomainObject)).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("treats views with no defined 'editable' property as editable", function () {
|
||||||
|
expect(policy.allow({ someKey: "some value" }, mockDomainObject))
|
||||||
|
.toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -18,6 +18,8 @@
|
|||||||
"objects/EditableDomainObject",
|
"objects/EditableDomainObject",
|
||||||
"objects/EditableDomainObjectCache",
|
"objects/EditableDomainObjectCache",
|
||||||
"objects/EditableModelCache",
|
"objects/EditableModelCache",
|
||||||
|
"policies/EditableViewPolicy",
|
||||||
|
"policies/EditActionPolicy",
|
||||||
"representers/EditRepresenter",
|
"representers/EditRepresenter",
|
||||||
"representers/EditToolbar",
|
"representers/EditToolbar",
|
||||||
"representers/EditToolbarRepresenter",
|
"representers/EditToolbarRepresenter",
|
||||||
|
@ -84,11 +84,21 @@ define(
|
|||||||
|
|
||||||
// Build up look-up tables
|
// Build up look-up tables
|
||||||
actions.forEach(function (Action) {
|
actions.forEach(function (Action) {
|
||||||
if (Action.category) {
|
// Get an action's category or categories
|
||||||
actionsByCategory[Action.category] =
|
var categories = Action.category || [];
|
||||||
actionsByCategory[Action.category] || [];
|
|
||||||
actionsByCategory[Action.category].push(Action);
|
// Convert to an array if necessary
|
||||||
}
|
categories = Array.isArray(categories) ?
|
||||||
|
categories : [categories];
|
||||||
|
|
||||||
|
// Store action under all relevant categories
|
||||||
|
categories.forEach(function (category) {
|
||||||
|
actionsByCategory[category] =
|
||||||
|
actionsByCategory[category] || [];
|
||||||
|
actionsByCategory[category].push(Action);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store action by ekey as well
|
||||||
if (Action.key) {
|
if (Action.key) {
|
||||||
actionsByKey[Action.key] =
|
actionsByKey[Action.key] =
|
||||||
actionsByKey[Action.key] || [];
|
actionsByKey[Action.key] || [];
|
||||||
|
@ -10,6 +10,12 @@
|
|||||||
"implementation": "PolicyActionDecorator.js",
|
"implementation": "PolicyActionDecorator.js",
|
||||||
"depends": [ "policyService" ]
|
"depends": [ "policyService" ]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "decorator",
|
||||||
|
"provides": "viewService",
|
||||||
|
"implementation": "PolicyViewDecorator.js",
|
||||||
|
"depends": [ "policyService" ]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "provider",
|
"type": "provider",
|
||||||
"provides": "policyService",
|
"provides": "policyService",
|
||||||
|
@ -15,7 +15,7 @@ define(
|
|||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
* Get actions which are applicable in this context.
|
* Get actions which are applicable in this context.
|
||||||
* These will be filters to remove any actions which
|
* These will be filtered to remove any actions which
|
||||||
* are deemed inapplicable by policy.
|
* are deemed inapplicable by policy.
|
||||||
* @param context the context in which the action will occur
|
* @param context the context in which the action will occur
|
||||||
* @returns {Action[]} applicable actions
|
* @returns {Action[]} applicable actions
|
||||||
|
37
platform/policy/src/PolicyViewDecorator.js
Normal file
37
platform/policy/src/PolicyViewDecorator.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters out views based on policy.
|
||||||
|
* @param {PolicyService} policyService the service which provides
|
||||||
|
* policy decisions
|
||||||
|
* @param {ViewService} viewService the service to decorate
|
||||||
|
*/
|
||||||
|
function PolicyActionDecorator(policyService, viewService) {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Get views which are applicable to this domain object.
|
||||||
|
* These will be filtered to remove any views which
|
||||||
|
* are deemed inapplicable by policy.
|
||||||
|
* @param {DomainObject} the domain object to view
|
||||||
|
* @returns {View[]} applicable views
|
||||||
|
*/
|
||||||
|
getViews: function (domainObject) {
|
||||||
|
// Check if an action is allowed by policy.
|
||||||
|
function allow(view) {
|
||||||
|
return policyService.allow('view', view, domainObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up actions, filter out the disallowed ones.
|
||||||
|
return viewService.getViews(domainObject).filter(allow);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return PolicyActionDecorator;
|
||||||
|
}
|
||||||
|
);
|
83
platform/policy/test/PolicyViewDecoratorSpec.js
Normal file
83
platform/policy/test/PolicyViewDecoratorSpec.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../src/PolicyViewDecorator"],
|
||||||
|
function (PolicyViewDecorator) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("The policy view decorator", function () {
|
||||||
|
var mockPolicyService,
|
||||||
|
mockViewService,
|
||||||
|
mockDomainObject,
|
||||||
|
testViews,
|
||||||
|
decorator;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockPolicyService = jasmine.createSpyObj(
|
||||||
|
'policyService',
|
||||||
|
['allow']
|
||||||
|
);
|
||||||
|
mockViewService = jasmine.createSpyObj(
|
||||||
|
'viewService',
|
||||||
|
['getViews']
|
||||||
|
);
|
||||||
|
mockDomainObject = jasmine.createSpyObj(
|
||||||
|
'domainObject',
|
||||||
|
['getId']
|
||||||
|
);
|
||||||
|
|
||||||
|
// Content of actions should be irrelevant to this
|
||||||
|
// decorator, so just give it some objects to pass
|
||||||
|
// around.
|
||||||
|
testViews = [
|
||||||
|
{ someKey: "a" },
|
||||||
|
{ someKey: "b" },
|
||||||
|
{ someKey: "c" }
|
||||||
|
];
|
||||||
|
|
||||||
|
mockDomainObject.getId.andReturn('xyz');
|
||||||
|
mockViewService.getViews.andReturn(testViews);
|
||||||
|
mockPolicyService.allow.andReturn(true);
|
||||||
|
|
||||||
|
decorator = new PolicyViewDecorator(
|
||||||
|
mockPolicyService,
|
||||||
|
mockViewService
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("delegates to its decorated view service", function () {
|
||||||
|
decorator.getViews(mockDomainObject);
|
||||||
|
expect(mockViewService.getViews)
|
||||||
|
.toHaveBeenCalledWith(mockDomainObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides views from its decorated view service", function () {
|
||||||
|
// Mock policy service allows everything by default,
|
||||||
|
// so everything should be returned
|
||||||
|
expect(decorator.getViews(mockDomainObject))
|
||||||
|
.toEqual(testViews);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("consults the policy service for each candidate view", function () {
|
||||||
|
decorator.getViews(mockDomainObject);
|
||||||
|
testViews.forEach(function (testView) {
|
||||||
|
expect(mockPolicyService.allow).toHaveBeenCalledWith(
|
||||||
|
'view',
|
||||||
|
testView,
|
||||||
|
mockDomainObject
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("filters out policy-disallowed views", function () {
|
||||||
|
// Disallow the second action
|
||||||
|
mockPolicyService.allow.andCallFake(function (cat, candidate, ctxt) {
|
||||||
|
return candidate.someKey !== 'b';
|
||||||
|
});
|
||||||
|
expect(decorator.getViews(mockDomainObject))
|
||||||
|
.toEqual([ testViews[0], testViews[2] ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -1,4 +1,5 @@
|
|||||||
[
|
[
|
||||||
"PolicyActionDecorator",
|
"PolicyActionDecorator",
|
||||||
|
"PolicyViewDecorator",
|
||||||
"PolicyProvider"
|
"PolicyProvider"
|
||||||
]
|
]
|
Loading…
Reference in New Issue
Block a user