Commit 2bf9e9c3 authored by Sean Hammond's avatar Sean Hammond Committed by GitHub

Merge pull request #440 from hypothesis/annotation-layer-ff

Enable feature flagging in the annotation layer
parents 134aec53 6984f332
'use strict';
const events = require('../shared/bridge-events');
let _features = {};
const _set = (features) => {
_features = features || {};
};
module.exports = {
init: function(crossframe) {
crossframe.on(events.FEATURE_FLAGS_UPDATED, _set);
},
reset: function() {
_set({});
},
flagEnabled: function(flag) {
if (!(flag in _features)) {
console.warn('looked up unknown feature', flag);
return false;
}
return _features[flag];
},
};
......@@ -6,6 +6,7 @@ Host = require('./host')
annotationCounts = require('./annotation-counts')
sidebarTrigger = require('./sidebar-trigger')
events = require('../shared/bridge-events');
features = require('./features');
# Minimum width to which the frame can be resized.
MIN_RESIZE = 280
......@@ -89,7 +90,6 @@ module.exports = class Sidebar extends Host
if @onHelpRequest
@onHelpRequest()
);
# Return this for chaining
this
......
'use strict';
var events = require('../../shared/bridge-events');
var features = require('../features');
describe('features - annotation layer', function () {
var featureFlagsUpdateHandler;
var initialFeatures = {
feature_on: true,
feature_off: false,
};
var setFeatures = function(features){
featureFlagsUpdateHandler(features || initialFeatures);
};
beforeEach(function () {
sinon.stub(console, 'warn');
features.init({
on: function(topic, handler){
if(topic === events.FEATURE_FLAGS_UPDATED){
featureFlagsUpdateHandler = handler;
}
},
});
// set default features
setFeatures();
});
afterEach(function () {
console.warn.restore();
features.reset();
});
describe('flagEnabled', function () {
it('should retrieve features data', function () {
assert.equal(features.flagEnabled('feature_on'), true);
assert.equal(features.flagEnabled('feature_off'), false);
});
it('should return false if features have not been loaded', function () {
// simulate feature data not having been loaded yet
features.reset();
assert.equal(features.flagEnabled('feature_on'), false);
});
it('should return false for unknown flags', function () {
assert.isFalse(features.flagEnabled('unknown_feature'));
});
it('should warn when accessing unknown flags', function () {
assert.notCalled(console.warn);
assert.isFalse(features.flagEnabled('unknown_feature'));
assert.calledOnce(console.warn);
assert.calledWith(console.warn, 'looked up unknown feature');
});
});
});
......@@ -8,8 +8,15 @@ module.exports = {
// Events that the sidebar sends to the annotator
// ----------------------------------------------
/** The set of annotations was updated. */
PUBLIC_ANNOTATION_COUNT_CHANGED: 'publicAnnotationCountChanged',
/**
* The updated feature flags for the user
*/
FEATURE_FLAGS_UPDATED: 'featureFlagsUpdated',
/**
* The sidebar is asking the annotator to open the partner site help page.
*/
HELP_REQUESTED: 'helpRequested',
/** The sidebar is asking the annotator to do a partner site log in
* (for example, pop up a log in window). This is used when the client is
......@@ -24,19 +31,20 @@ module.exports = {
LOGOUT_REQUESTED: 'logoutRequested',
/**
* The sidebar is asking the annotator to do a partner site sign-up.
* The sidebar is asking the annotator to open the partner site profile page.
*/
SIGNUP_REQUESTED: 'signupRequested',
PROFILE_REQUESTED: 'profileRequested',
/**
* The sidebar is asking the annotator to open the partner site profile page.
* The set of annotations was updated.
*/
PROFILE_REQUESTED: 'profileRequested',
PUBLIC_ANNOTATION_COUNT_CHANGED: 'publicAnnotationCountChanged',
/**
* The sidebar is asking the annotator to open the partner site help page.
* The sidebar is asking the annotator to do a partner site sign-up.
*/
HELP_REQUESTED: 'helpRequested',
SIGNUP_REQUESTED: 'signupRequested',
// Events that the annotator sends to the sidebar
// ----------------------------------------------
......
......@@ -5,6 +5,10 @@
* on $rootScope
*/
module.exports = {
// Internal state changes
FRAME_CONNECTED: 'frameConnected',
// Session state changes
/** The list of groups changed */
......
......@@ -11,8 +11,27 @@
*/
'use strict';
var events = require('./events');
var bridgeEvents = require('../shared/bridge-events');
// @ngInject
function features($log, session) {
function features($log, $rootScope, bridge, session) {
var _sendFeatureFlags = function(){
var userFeatures = session.state.features;
bridge.call(bridgeEvents.FEATURE_FLAGS_UPDATED, userFeatures || {});
};
// user changed is currently called when we initially load
// the sidebar and when the user actually logs out/in.
$rootScope.$on(events.USER_CHANGED, _sendFeatureFlags);
// send on frame connected as well because the user_changed event
// alone might run before the frames ever connected. This will
// provide us the follow up to make sure that the frames get the flags
$rootScope.$on(events.FRAME_CONNECTED, _sendFeatureFlags);
/**
* Returns true if the flag with the given name is enabled for the current
* user.
......
......@@ -163,6 +163,8 @@ function FrameSync($rootScope, $window, Discovery, annotationUI, bridge) {
return;
}
$rootScope.$broadcast(events.FRAME_CONNECTED);
annotationUI.connectFrame({
metadata: info.metadata,
uri: info.uri,
......
'use strict';
var features = require('../features');
var events = require('../events');
var bridgeEvents = require('../../shared/bridge-events');
describe('h:features', function () {
describe('h:features - sidebar layer', function () {
var fakeBridge;
var fakeLog;
var fakeRootScope;
var fakeSession;
var sandbox;
beforeEach(function () {
sandbox = sinon.sandbox.create();
fakeBridge = {
call: sinon.stub(),
};
fakeLog = {
warn: sinon.stub(),
};
fakeRootScope = {
eventCallbacks: {},
$broadcast: sandbox.stub(),
$on: function(event, callback) {
this.eventCallbacks[event] = callback;
},
};
fakeSession = {
load: sinon.stub(),
state: {
......@@ -22,29 +44,58 @@ describe('h:features', function () {
};
});
afterEach(function(){
sandbox.restore();
});
describe('flagEnabled', function () {
it('should retrieve features data', function () {
var features_ = features(fakeLog, fakeSession);
var features_ = features(fakeLog, fakeRootScope, fakeBridge, fakeSession);
assert.equal(features_.flagEnabled('feature_on'), true);
assert.equal(features_.flagEnabled('feature_off'), false);
});
it('should return false if features have not been loaded', function () {
var features_ = features(fakeLog, fakeSession);
var features_ = features(fakeLog, fakeRootScope, fakeBridge, fakeSession);
// simulate feature data not having been loaded yet
fakeSession.state = {};
assert.equal(features_.flagEnabled('feature_on'), false);
});
it('should trigger a refresh of session data', function () {
var features_ = features(fakeLog, fakeSession);
var features_ = features(fakeLog, fakeRootScope, fakeBridge, fakeSession);
features_.flagEnabled('feature_on');
assert.calledOnce(fakeSession.load);
});
it('should return false for unknown flags', function () {
var features_ = features(fakeLog, fakeSession);
var features_ = features(fakeLog, fakeRootScope, fakeBridge, fakeSession);
assert.isFalse(features_.flagEnabled('unknown_feature'));
});
});
it('should broadcast feature flags to annotation layer based on load/user changes', function(){
assert.notProperty(fakeRootScope.eventCallbacks, events.USER_CHANGED);
assert.notProperty(fakeRootScope.eventCallbacks, events.FRAME_CONNECTED);
features(fakeLog, fakeRootScope, fakeBridge, fakeSession);
assert.property(fakeRootScope.eventCallbacks, events.USER_CHANGED);
assert.property(fakeRootScope.eventCallbacks, events.FRAME_CONNECTED);
// respond to user changing by broadcasting the feature flags
assert.notCalled(fakeBridge.call);
fakeRootScope.eventCallbacks[events.USER_CHANGED]();
assert.calledOnce(fakeBridge.call);
assert.calledWith(fakeBridge.call, bridgeEvents.FEATURE_FLAGS_UPDATED, fakeSession.state.features);
// respond to frame connections by broadcasting the feature flags
fakeRootScope.eventCallbacks[events.FRAME_CONNECTED]();
assert.calledTwice(fakeBridge.call);
assert.calledWith(fakeBridge.call, bridgeEvents.FEATURE_FLAGS_UPDATED, fakeSession.state.features);
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment