Merge branch 'open-master' into open1241
Merge latest from master branch into topic branch for WTD-1241.
This commit is contained in:
@@ -176,6 +176,10 @@
|
||||
"implementation": "capabilities/PersistenceCapability.js",
|
||||
"depends": [ "persistenceService", "PERSISTENCE_SPACE" ]
|
||||
},
|
||||
{
|
||||
"key": "metadata",
|
||||
"implementation": "capabilities/MetadataCapability.js"
|
||||
},
|
||||
{
|
||||
"key": "mutation",
|
||||
"implementation": "capabilities/MutationCapability.js",
|
||||
@@ -191,6 +195,11 @@
|
||||
{
|
||||
"key": "now",
|
||||
"implementation": "services/Now.js"
|
||||
},
|
||||
{
|
||||
"key": "throttle",
|
||||
"implementation": "services/Throttle.js",
|
||||
"depends": [ "$timeout" ]
|
||||
}
|
||||
],
|
||||
"roots": [
|
||||
|
||||
92
platform/core/src/capabilities/MetadataCapability.js
Normal file
92
platform/core/src/capabilities/MetadataCapability.js
Normal file
@@ -0,0 +1,92 @@
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
['moment'],
|
||||
function (moment) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* A piece of information about a domain object.
|
||||
* @typedef {Object} MetadataProperty
|
||||
* @property {string} name the human-readable name of this property
|
||||
* @property {string} value the human-readable value of this property,
|
||||
* for this specific domain object
|
||||
*/
|
||||
|
||||
var TIME_FORMAT = "YYYY-MM-DD HH:mm:ss";
|
||||
|
||||
/**
|
||||
* Implements the `metadata` capability of a domain object, providing
|
||||
* properties of that object for display.
|
||||
*
|
||||
* Usage: `domainObject.useCapability("metadata")`
|
||||
*
|
||||
* ...which will return an array of objects containing `name` and
|
||||
* `value` properties describing that domain object (suitable for
|
||||
* display.)
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function MetadataCapability(domainObject) {
|
||||
var model = domainObject.getModel();
|
||||
|
||||
function hasDisplayableValue(metadataProperty) {
|
||||
var t = typeof metadataProperty.value;
|
||||
return (t === 'string' || t === 'number');
|
||||
}
|
||||
|
||||
function formatTimestamp(timestamp) {
|
||||
return typeof timestamp === 'number' ?
|
||||
(moment.utc(timestamp).format(TIME_FORMAT) + " UTC") :
|
||||
undefined;
|
||||
}
|
||||
|
||||
function getProperties() {
|
||||
var type = domainObject.getCapability('type');
|
||||
|
||||
function lookupProperty(typeProperty) {
|
||||
return {
|
||||
name: typeProperty.getDefinition().name,
|
||||
value: typeProperty.getValue(model)
|
||||
};
|
||||
}
|
||||
|
||||
return (type ? type.getProperties() : []).map(lookupProperty);
|
||||
}
|
||||
|
||||
function getCommonMetadata() {
|
||||
var type = domainObject.getCapability('type');
|
||||
// Note that invalid values will be filtered out later
|
||||
return [
|
||||
{
|
||||
name: "Updated",
|
||||
value: formatTimestamp(model.modified)
|
||||
},
|
||||
{
|
||||
name: "Type",
|
||||
value: type && type.getName()
|
||||
},
|
||||
{
|
||||
name: "ID",
|
||||
value: domainObject.getId()
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function getMetadata() {
|
||||
return getProperties().concat(getCommonMetadata())
|
||||
.filter(hasDisplayableValue);
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get metadata about this object.
|
||||
* @returns {MetadataProperty[]} metadata about this object
|
||||
*/
|
||||
invoke: getMetadata
|
||||
};
|
||||
}
|
||||
|
||||
return MetadataCapability;
|
||||
}
|
||||
);
|
||||
@@ -77,7 +77,8 @@ define(
|
||||
// Get the object's model and clone it, so the
|
||||
// mutator function has a temporary copy to work with.
|
||||
var model = domainObject.getModel(),
|
||||
clone = JSON.parse(JSON.stringify(model));
|
||||
clone = JSON.parse(JSON.stringify(model)),
|
||||
useTimestamp = arguments.length > 1;
|
||||
|
||||
// Function to handle copying values to the actual
|
||||
function handleMutation(mutationResult) {
|
||||
@@ -94,8 +95,7 @@ define(
|
||||
if (model !== result) {
|
||||
copyValues(model, result);
|
||||
}
|
||||
model.modified = (typeof timestamp === 'number') ?
|
||||
timestamp : now();
|
||||
model.modified = useTimestamp ? timestamp : now();
|
||||
}
|
||||
|
||||
// Report the result of the mutation
|
||||
|
||||
63
platform/core/src/services/Throttle.js
Normal file
63
platform/core/src/services/Throttle.js
Normal file
@@ -0,0 +1,63 @@
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Throttler for function executions, registered as the `throttle`
|
||||
* service.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* throttle(fn, delay, [apply])
|
||||
*
|
||||
* Returns a function that, when invoked, will invoke `fn` after
|
||||
* `delay` milliseconds, only if no other invocations are pending.
|
||||
* The optional argument `apply` determines whether.
|
||||
*
|
||||
* The returned function will itself return a `Promise` which will
|
||||
* resolve to the returned value of `fn` whenever that is invoked.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
function Throttle($timeout) {
|
||||
/**
|
||||
* Throttle this function.
|
||||
* @param {Function} fn the function to throttle
|
||||
* @param {number} [delay] the delay, in milliseconds, before
|
||||
* executing this function; defaults to 0.
|
||||
* @param {boolean} apply true if a `$apply` call should be
|
||||
* invoked after this function executes; defaults to
|
||||
* `false`.
|
||||
*/
|
||||
return function (fn, delay, apply) {
|
||||
var activeTimeout;
|
||||
|
||||
// Clear active timeout, so that next invocation starts
|
||||
// a new one.
|
||||
function clearActiveTimeout() {
|
||||
activeTimeout = undefined;
|
||||
}
|
||||
|
||||
// Defaults
|
||||
delay = delay || 0;
|
||||
apply = apply || false;
|
||||
|
||||
return function () {
|
||||
// Start a timeout if needed
|
||||
if (!activeTimeout) {
|
||||
activeTimeout = $timeout(fn, delay, apply);
|
||||
activeTimeout.then(clearActiveTimeout);
|
||||
}
|
||||
// Return whichever timeout is active (to get
|
||||
// a promise for the results of fn)
|
||||
return activeTimeout;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
return Throttle;
|
||||
}
|
||||
);
|
||||
101
platform/core/test/capabilities/MetadataCapabilitySpec.js
Normal file
101
platform/core/test/capabilities/MetadataCapabilitySpec.js
Normal file
@@ -0,0 +1,101 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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.
|
||||
*****************************************************************************/
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
['../../src/capabilities/MetadataCapability'],
|
||||
function (MetadataCapability) {
|
||||
"use strict";
|
||||
|
||||
describe("The metadata capability", function () {
|
||||
var mockDomainObject,
|
||||
mockType,
|
||||
mockProperties,
|
||||
testModel,
|
||||
metadata;
|
||||
|
||||
function getCapability(key) {
|
||||
return key === 'type' ? mockType : undefined;
|
||||
}
|
||||
|
||||
function findValue(properties, name) {
|
||||
var i;
|
||||
for (i = 0; i < properties.length; i += 1) {
|
||||
if (properties[i].name === name) {
|
||||
return properties[i].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
'domainObject',
|
||||
['getId', 'getCapability', 'useCapability', 'getModel']
|
||||
);
|
||||
mockType = jasmine.createSpyObj(
|
||||
'type',
|
||||
['getProperties', 'getName']
|
||||
);
|
||||
mockProperties = ['a', 'b', 'c'].map(function (k) {
|
||||
var mockProperty = jasmine.createSpyObj(
|
||||
'property-' + k,
|
||||
['getValue', 'getDefinition']
|
||||
);
|
||||
mockProperty.getValue.andReturn("Value " + k);
|
||||
mockProperty.getDefinition.andReturn({ name: "Property " + k});
|
||||
return mockProperty;
|
||||
});
|
||||
testModel = { name: "" };
|
||||
|
||||
mockDomainObject.getId.andReturn("Test id");
|
||||
mockDomainObject.getModel.andReturn(testModel);
|
||||
mockDomainObject.getCapability.andCallFake(getCapability);
|
||||
mockDomainObject.useCapability.andCallFake(getCapability);
|
||||
mockType.getProperties.andReturn(mockProperties);
|
||||
mockType.getName.andReturn("Test type");
|
||||
|
||||
metadata = new MetadataCapability(mockDomainObject);
|
||||
});
|
||||
|
||||
it("reads properties from the domain object model", function () {
|
||||
metadata.invoke();
|
||||
mockProperties.forEach(function (mockProperty) {
|
||||
expect(mockProperty.getValue).toHaveBeenCalledWith(testModel);
|
||||
});
|
||||
});
|
||||
|
||||
it("reports type-specific properties", function () {
|
||||
var properties = metadata.invoke();
|
||||
expect(findValue(properties, 'Property a')).toEqual("Value a");
|
||||
expect(findValue(properties, 'Property b')).toEqual("Value b");
|
||||
expect(findValue(properties, 'Property c')).toEqual("Value c");
|
||||
});
|
||||
|
||||
it("reports generic properties", function () {
|
||||
var properties = metadata.invoke();
|
||||
expect(findValue(properties, 'ID')).toEqual("Test id");
|
||||
expect(findValue(properties, 'Type')).toEqual("Test type");
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
49
platform/core/test/services/ThrottleSpec.js
Normal file
49
platform/core/test/services/ThrottleSpec.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../../src/services/Throttle"],
|
||||
function (Throttle) {
|
||||
"use strict";
|
||||
|
||||
describe("The 'throttle' service", function () {
|
||||
var throttle,
|
||||
mockTimeout,
|
||||
mockFn,
|
||||
mockPromise;
|
||||
|
||||
beforeEach(function () {
|
||||
mockTimeout = jasmine.createSpy("$timeout");
|
||||
mockPromise = jasmine.createSpyObj("promise", ["then"]);
|
||||
mockFn = jasmine.createSpy("fn");
|
||||
mockTimeout.andReturn(mockPromise);
|
||||
throttle = new Throttle(mockTimeout);
|
||||
});
|
||||
|
||||
it("provides functions which run on a timeout", function () {
|
||||
var throttled = throttle(mockFn);
|
||||
// Verify precondition: Not called at throttle-time
|
||||
expect(mockTimeout).not.toHaveBeenCalled();
|
||||
expect(throttled()).toEqual(mockPromise);
|
||||
expect(mockTimeout).toHaveBeenCalledWith(mockFn, 0, false);
|
||||
});
|
||||
|
||||
it("schedules only one timeout at a time", function () {
|
||||
var throttled = throttle(mockFn);
|
||||
throttled();
|
||||
throttled();
|
||||
throttled();
|
||||
expect(mockTimeout.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("schedules additional invocations after resolution", function () {
|
||||
var throttled = throttle(mockFn);
|
||||
throttled();
|
||||
mockPromise.then.mostRecentCall.args[0](); // Resolve timeout
|
||||
throttled();
|
||||
mockPromise.then.mostRecentCall.args[0]();
|
||||
throttled();
|
||||
expect(mockTimeout.calls.length).toEqual(3);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -9,6 +9,7 @@
|
||||
"capabilities/ContextualDomainObject",
|
||||
"capabilities/CoreCapabilityProvider",
|
||||
"capabilities/DelegationCapability",
|
||||
"capabilities/MetadataCapability",
|
||||
"capabilities/MutationCapability",
|
||||
"capabilities/PersistenceCapability",
|
||||
"capabilities/RelationshipCapability",
|
||||
@@ -23,6 +24,7 @@
|
||||
"objects/DomainObjectProvider",
|
||||
|
||||
"services/Now",
|
||||
"services/Throttle",
|
||||
|
||||
"types/MergeModels",
|
||||
"types/TypeCapability",
|
||||
|
||||
Reference in New Issue
Block a user