Merge remote-tracking branch 'origin/open608' into open-master

This commit is contained in:
bwyu 2014-12-18 13:25:29 -08:00
commit 51376fb38a
10 changed files with 358 additions and 9 deletions

View File

@ -11,6 +11,10 @@
{
"key": "action-button",
"templateUrl": "templates/controls/action-button.html"
},
{
"key": "indicator",
"templateUrl": "templates/indicator.html"
}
],
"controllers": [
@ -42,6 +46,11 @@
"key": "ViewSwitcherController",
"implementation": "ViewSwitcherController.js",
"depends": [ "$scope" ]
},
{
"key": "BottomBarController",
"implementation": "BottomBarController.js",
"depends": [ "indicators[]" ]
}
],
"directives": [

View File

@ -1,13 +1,9 @@
<div class='abs bottom-bar ue-bottom-bar'>
<div class='abs bottom-bar ue-bottom-bar' ng-controller="BottomBarController as bar">
<div id='status' class='status-holder abs'>
<div id='status_data_flow' class='status block data-flow'>
<span class='ui-symbol status-indicator ok'>.</span>
<span class='label'>Connected</span>
</div>
<!--div id='status_data_connection' class='status block data-connection'>
<span class='ui-symbol status-indicator caution'>D</span>
<span class='label'>Connected CPU 0.1% / Mem 1.9%</span>
</div-->
<mct-include ng-repeat="indicator in bar.getIndicators()"
ng-model="indicator.ngModel"
key="indicator.template">
</mct-include>
</div>
<!--mct-include key="'app-logo'"></mct-include-->
</div>

View File

@ -0,0 +1,18 @@
<div class='status block'
title="{{ngModel.getDescription()}}"
ng-click='ngModel.configure()'
ng-class='ngModel.getClass()'>
<span class="ui-symbol status-indicator"
ng-class='ngModel.getGlyphClass()'>
{{ngModel.getGlyph()}}
</span>
<span class="label"
ng-class='ngModel.getTextClass()'>
{{ngModel.getText()}}
</span>
<a href=''
class="ui-symbol"
ng-if="ngModel.configure">
G
</a>
</div>

View File

@ -0,0 +1,40 @@
/*global define*/
define(
[],
function () {
"use strict";
/**
* Controller for the bottombar template. Exposes
* available indicators (of extension category "indicators")
* @constructor
*/
function BottomBarController(indicators) {
// Utility function used to make indicators presentable
// for display.
function present(Indicator) {
return {
template: Indicator.template || "indicator",
ngModel: typeof Indicator === 'function' ?
new Indicator() : Indicator
};
}
indicators = indicators.map(present);
return {
/**
* Get all indicators to display.
* @returns {Indicator[]} all indicators
* to display in the bottom bar.
*/
getIndicators: function () {
return indicators;
}
};
}
return BottomBarController;
}
);

View File

@ -0,0 +1,55 @@
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
define(
["../src/BottomBarController"],
function (BottomBarController) {
"use strict";
describe("The bottom bar controller", function () {
var testIndicators,
testIndicatorA,
testIndicatorB,
testIndicatorC,
mockIndicator,
controller;
beforeEach(function () {
mockIndicator = jasmine.createSpyObj(
"indicator",
[ "getGlyph", "getText" ]
);
testIndicatorA = {};
testIndicatorB = function () { return mockIndicator; };
testIndicatorC = { template: "someTemplate" };
testIndicators = [
testIndicatorA,
testIndicatorB,
testIndicatorC
];
controller = new BottomBarController(testIndicators);
});
it("exposes one indicator description per extension", function () {
expect(controller.getIndicators().length)
.toEqual(testIndicators.length);
});
it("uses template field provided, or its own default", function () {
// "indicator" is the default;
// only testIndicatorC overrides this.
var indicators = controller.getIndicators();
expect(indicators[0].template).toEqual("indicator");
expect(indicators[1].template).toEqual("indicator");
expect(indicators[2].template).toEqual("someTemplate");
});
it("instantiates indicators given as constructors", function () {
// testIndicatorB constructs to mockIndicator
expect(controller.getIndicators()[1].ngModel).toBe(mockIndicator);
});
});
}
);

View File

@ -1,5 +1,6 @@
[
"ActionGroupController",
"BottomBarController",
"ClickAwayController",
"ContextMenuController",
"MCTContainer",

View File

@ -18,6 +18,21 @@
{
"key": "COUCHDB_PATH",
"value": "/couch/openmct"
},
{
"key": "COUCHDB_INDICATOR_INTERVAL",
"value": 15000
}
],
"indicators": [
{
"implementation": "CouchIndicator.js",
"depends": [
"$http",
"$interval",
"COUCHDB_PATH",
"COUCHDB_INDICATOR_INTERVAL"
]
}
]
}

View File

@ -0,0 +1,103 @@
/*global define*/
define(
[],
function () {
"use strict";
// Set of connection states; changing among these states will be
// reflected in the indicator's appearance.
// CONNECTED: Everything nominal, expect to be able to read/write.
// DISCONNECTED: HTTP failed; maybe misconfigured, disconnected.
// SEMICONNECTED: Connected to the database, but it reported an error.
// PENDING: Still trying to connect, and haven't failed yet.
var CONNECTED = {
text: "Connected",
glyphClass: "ok",
description: "Connected to the domain object database."
},
DISCONNECTED = {
text: "Disconnected",
glyphClass: "err",
description: "Unable to connect to the domain object database."
},
SEMICONNECTED = {
text: "Unavailable",
glyphClass: "caution",
description: "Database does not exist or is unavailable."
},
PENDING = {
text: "Checking connection..."
};
/**
* Indicator for the current CouchDB connection. Polls CouchDB
* at a regular interval (defined by bundle constants) to ensure
* that the database is available.
*/
function CouchIndicator($http, $interval, PATH, INTERVAL) {
// Track the current connection state
var state = PENDING;
// Callback if the HTTP request to Couch fails
function handleError(err) {
state = DISCONNECTED;
}
// Callback if the HTTP request succeeds. CouchDB may
// report an error, so check for that.
function handleResponse(response) {
var data = response.data;
state = data.error ? SEMICONNECTED : CONNECTED;
}
// Try to connect to CouchDB, and update the indicator.
function updateIndicator() {
$http.get(PATH).then(handleResponse, handleError);
}
// Update the indicator initially, and start polling.
updateIndicator();
$interval(updateIndicator, INTERVAL);
return {
/**
* Get the glyph (single character used as an icon)
* to display in this indicator. This will return "D",
* which should appear as a database icon.
* @returns {string} the character of the database icon
*/
getGlyph: function () {
return "D";
},
/**
* Get the name of the CSS class to apply to the glyph.
* This is used to color the glyph to match its
* state (one of ok, caution or err)
* @returns {string} the CSS class to apply to this glyph
*/
getGlyphClass: function () {
return state.glyphClass;
},
/**
* Get the text that should appear in the indicator.
* @returns {string} brief summary of connection status
*/
getText: function () {
return state.text;
},
/**
* Get a longer-form description of the current connection
* space, suitable for display in a tooltip
* @returns {string} longer summary of connection status
*/
getDescription: function () {
return state.description;
}
};
}
return CouchIndicator;
}
);

View File

@ -0,0 +1,111 @@
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
define(
["../src/CouchIndicator"],
function (CouchIndicator) {
"use strict";
describe("The CouchDB status indicator", function () {
var mockHttp,
mockInterval,
testPath,
testInterval,
mockPromise,
indicator;
beforeEach(function () {
mockHttp = jasmine.createSpyObj("$http", [ "get" ]);
mockInterval = jasmine.createSpy("$interval");
mockPromise = jasmine.createSpyObj("promise", [ "then" ]);
testPath = "/test/path";
testInterval = 12321; // Some number
mockHttp.get.andReturn(mockPromise);
indicator = new CouchIndicator(
mockHttp,
mockInterval,
testPath,
testInterval
);
});
it("polls for changes", function () {
expect(mockInterval).toHaveBeenCalledWith(
jasmine.any(Function),
testInterval
);
});
it("has a database icon", function () {
expect(indicator.getGlyph()).toEqual("D");
});
it("consults the database at the configured path", function () {
expect(mockHttp.get).toHaveBeenCalledWith(testPath);
});
it("changes when the database connection is nominal", function () {
var initialText = indicator.getText(),
initialDescrption = indicator.getDescription(),
initialGlyphClass = indicator.getGlyphClass();
// Nominal just means getting back an objeect, without
// an error field.
mockPromise.then.mostRecentCall.args[0]({ data: {} });
// Verify that these values changed;
// don't test for specific text.
expect(indicator.getText()).not.toEqual(initialText);
expect(indicator.getGlyphClass()).not.toEqual(initialGlyphClass);
expect(indicator.getDescription()).not.toEqual(initialDescrption);
// Do check for specific class
expect(indicator.getGlyphClass()).toEqual("ok");
});
it("changes when the server reports an error", function () {
var initialText = indicator.getText(),
initialDescrption = indicator.getDescription(),
initialGlyphClass = indicator.getGlyphClass();
// Nominal just means getting back an objeect, with
// an error field.
mockPromise.then.mostRecentCall.args[0](
{ data: { error: "Uh oh." } }
);
// Verify that these values changed;
// don't test for specific text.
expect(indicator.getText()).not.toEqual(initialText);
expect(indicator.getGlyphClass()).not.toEqual(initialGlyphClass);
expect(indicator.getDescription()).not.toEqual(initialDescrption);
// Do check for specific class
expect(indicator.getGlyphClass()).toEqual("caution");
});
it("changes when the server cannot be reached", function () {
var initialText = indicator.getText(),
initialDescrption = indicator.getDescription(),
initialGlyphClass = indicator.getGlyphClass();
// Nominal just means getting back an objeect, without
// an error field.
mockPromise.then.mostRecentCall.args[1]({ data: {} });
// Verify that these values changed;
// don't test for specific text.
expect(indicator.getText()).not.toEqual(initialText);
expect(indicator.getGlyphClass()).not.toEqual(initialGlyphClass);
expect(indicator.getDescription()).not.toEqual(initialDescrption);
// Do check for specific class
expect(indicator.getGlyphClass()).toEqual("err");
});
});
}
);

View File

@ -1,4 +1,5 @@
[
"CouchDocument",
"CouchIndicator",
"CouchPersistenceProvider"
]