Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update moderation API use #336

Merged
merged 7 commits into from
Apr 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/sidebar/annotation-metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,21 @@ function location(annotation) {
return Number.POSITIVE_INFINITY;
}

/**
* Return the number of times the annotation has been flagged
* by other users. If moderation data is unavailable, returns 0.
*/
function flagCount(ann) {
if (!ann.moderation) {
return 0;
}
return ann.moderation.flag_count;
}

module.exports = {
documentMetadata: documentMetadata,
domainAndTitle: domainAndTitle,
flagCount: flagCount,
isAnnotation: isAnnotation,
isNew: isNew,
isOrphan: isOrphan,
Expand Down
5 changes: 0 additions & 5 deletions src/sidebar/annotation-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ var thunk = require('redux-thunk').default;
var reducers = require('./reducers');
var annotationsReducer = require('./reducers/annotations');
var framesReducer = require('./reducers/frames');
var moderationReducer = require('./reducers/moderation');
var selectionReducer = require('./reducers/selection');
var sessionReducer = require('./reducers/session');
var viewerReducer = require('./reducers/viewer');
Expand Down Expand Up @@ -96,7 +95,6 @@ module.exports = function ($rootScope, settings) {
var actionCreators = redux.bindActionCreators(Object.assign({},
annotationsReducer.actions,
framesReducer.actions,
moderationReducer.actions,
selectionReducer.actions,
sessionReducer.actions,
viewerReducer.actions
Expand All @@ -119,9 +117,6 @@ module.exports = function ($rootScope, settings) {

frames: framesReducer.frames,

isHiddenByModerator: moderationReducer.isHiddenByModerator,
flagCount: moderationReducer.flagCount,

isSidebar: viewerReducer.isSidebar,
}, store.getState);

Expand Down
2 changes: 1 addition & 1 deletion src/sidebar/components/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ function AnnotationController(
};

vm.isHiddenByModerator = function () {
return annotationUI.isHiddenByModerator(vm.annotation.id);
return vm.annotation.hidden;
};

vm.isFlagged = function() {
Expand Down
38 changes: 19 additions & 19 deletions src/sidebar/components/moderation-banner.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,42 @@
'use strict';

var annotationMetadata = require('../annotation-metadata');

// @ngInject
function ModerationBannerController(annotationUI, flash, store) {
var self = this;

this.flagCount = function () {
return annotationUI.flagCount(this.annotationId);
return annotationMetadata.flagCount(self.annotation);
};

this.isHidden = function () {
return annotationUI.isHiddenByModerator(this.annotationId);
return self.annotation.hidden;
};

this.isReply = function () {
return annotationMetadata.isReply(self.annotation);
};

/**
* Hide an annotation from non-moderator users.
*/
this.hideAnnotation = function () {
store.annotation.hide({id: this.annotationId}).then(function () {
annotationUI.annotationHiddenChanged(this.annotationId, true);
}).catch(function (err) {
flash.error(err.message);
store.annotation.hide({id: self.annotation.id}).then(function () {
annotationUI.hideAnnotation(self.annotation.id);
}).catch(function () {
flash.error('Failed to hide annotation');
});
};

/**
* Un-hide an annotation from non-moderator users.
*/
this.unhideAnnotation = function () {
store.annotation.unhide({id: this.annotationId}).then(function () {
annotationUI.annotationHiddenChanged(this.annotationId, false);
}).catch(function (err) {
flash.error(err.message);
store.annotation.unhide({id: self.annotation.id}).then(function () {
annotationUI.unhideAnnotation(self.annotation.id);
}).catch(function () {
flash.error('Failed to unhide annotation');
});
};
}
Expand All @@ -42,15 +50,7 @@ module.exports = {
controller: ModerationBannerController,
controllerAs: 'vm',
bindings: {
/**
* The ID of the annotation whose moderation status the banner should
* reflect.
*/
annotationId: '<',
/**
* `true` if this annotation is a reply.
*/
isReply: '<',
annotation: '<',
},
template: require('../templates/moderation_banner.html'),
};
4 changes: 1 addition & 3 deletions src/sidebar/components/test/annotation-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@ describe('annotation', function() {
};

fakeAnnotationUI = {
isHiddenByModerator: sandbox.stub().returns(false),
updateFlagStatus: sandbox.stub().returns(true),
};

Expand Down Expand Up @@ -999,8 +998,7 @@ describe('annotation', function() {
});

it('renders hidden annotations with a custom text class', function () {
var ann = fixtures.defaultAnnotation();
fakeAnnotationUI.isHiddenByModerator.returns(true);
var ann = fixtures.moderatedAnnotation({ hidden: true });
var el = createDirective(ann).element;
assert.deepEqual(el.find('markdown').controller('markdown'), {
customTextClass: {
Expand Down
14 changes: 6 additions & 8 deletions src/sidebar/components/test/annotation-thread-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
var angular = require('angular');

var annotationThread = require('../annotation-thread');
var moderationBanner = require('../moderation-banner');
var fixtures = require('../../test/annotation-fixtures');
var util = require('../../directive/test/util');

function PageObject(element) {
Expand All @@ -27,10 +29,7 @@ describe('annotationThread', function () {
angular.module('app', [])
.component('annotationThread', annotationThread)
.component('moderationBanner', {
bindings: {
annotationId: '<',
isReply: '<',
},
bindings: moderationBanner.bindings,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change ensures that the interface of the <moderation-banner> component (ie. its inputs) assumed by this test matches the actual interface.

});
});

Expand Down Expand Up @@ -184,7 +183,9 @@ describe('annotationThread', function () {
});

it('renders the moderation banner', function () {
var ann = fixtures.moderatedAnnotation({ flagCount: 1 });
var thread = {
annotation: ann,
id: '123',
parent: null,
children: [],
Expand All @@ -195,9 +196,6 @@ describe('annotationThread', function () {
var moderationBanner = element
.find('moderation-banner')
.controller('moderationBanner');
assert.deepEqual(moderationBanner, {
isReply: false,
annotationId: '123',
});
assert.deepEqual(moderationBanner, { annotation: ann });
});
});
54 changes: 28 additions & 26 deletions src/sidebar/components/test/moderation-banner-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
var angular = require('angular');

var util = require('../../directive/test/util');
var fixtures = require('../../test/annotation-fixtures');

var moderatedAnnotation = fixtures.moderatedAnnotation;

describe('moderationBanner', function () {
var bannerEl;
Expand All @@ -17,10 +20,8 @@ describe('moderationBanner', function () {

beforeEach(function () {
fakeAnnotationUI = {
flagCount: sinon.stub().returns(0),
isHiddenByModerator: sinon.stub().returns(false),

annotationHiddenChanged: sinon.stub(),
hideAnnotation: sinon.stub(),
unhideAnnotation: sinon.stub(),
};

fakeFlash = {
Expand All @@ -46,75 +47,76 @@ describe('moderationBanner', function () {
});

function createBanner(inputs) {
inputs.isReply = inputs.isReply || false;
var el = util.createDirective(document, 'moderationBanner', inputs);
bannerEl = el[0];
return bannerEl;
}

it('does not display if annotation is not flagged or hidden', function () {
fakeAnnotationUI.flagCount.returns(0);
fakeAnnotationUI.isHiddenByModerator.returns(false);
var banner = createBanner({ annotationId: 'not-flagged-or-hidden-id' });
var banner = createBanner({ annotation: fixtures.defaultAnnotation() });
assert.equal(banner.textContent.trim(), '');
});

it('displays the number of flags the annotation has received', function () {
fakeAnnotationUI.flagCount.returns(10);
var banner = createBanner({ annotationId: 'flagged-id' });
var ann = fixtures.moderatedAnnotation({ flagCount: 10 });
var banner = createBanner({ annotation: ann });
assert.include(banner.textContent, 'Flagged for review x10');
});

it('displays in a more compact form if the annotation is a reply', function () {
fakeAnnotationUI.flagCount.returns(1);
var banner = createBanner({ annotationId: 'reply-id', isReply: true });
var ann = Object.assign(fixtures.oldReply(), {
moderation: {
flag_count: 10,
},
});
var banner = createBanner({ annotation: ann });
assert.ok(banner.querySelector('.is-reply'));
});

it('reports if the annotation was hidden', function () {
fakeAnnotationUI.isHiddenByModerator.returns(true);
var banner = createBanner({ annotationId: 'hidden-id' });
var ann = moderatedAnnotation({ hidden: true });
var banner = createBanner({ annotation: ann });
assert.include(banner.textContent, 'Hidden from users');
});

it('hides the annotation if "Hide" is clicked', function () {
fakeAnnotationUI.flagCount.returns(10);
var banner = createBanner({ annotationId: 'flagged-id'} );
var ann = moderatedAnnotation({ flagCount: 10 });
var banner = createBanner({ annotation: ann });
banner.querySelector('button').click();
assert.calledWith(fakeStore.annotation.hide, {id: 'flagged-id'});
assert.calledWith(fakeStore.annotation.hide, {id: 'ann-id'});
});

it('reports an error if hiding the annotation fails', function (done) {
fakeAnnotationUI.flagCount.returns(10);
var banner = createBanner({ annotationId: 'flagged-id'} );
var ann = moderatedAnnotation({ flagCount: 10 });
var banner = createBanner({ annotation: ann });
fakeStore.annotation.hide.returns(Promise.reject(new Error('Network Error')));

banner.querySelector('button').click();

setTimeout(function () {
assert.calledWith(fakeFlash.error, 'Network Error');
assert.calledWith(fakeFlash.error, 'Failed to hide annotation');
done();
}, 0);
});

it('unhides the annotation if "Unhide" is clicked', function () {
fakeAnnotationUI.isHiddenByModerator.returns(true);
var banner = createBanner({ annotationId: 'hidden-id'} );
var ann = moderatedAnnotation({ hidden: true });
var banner = createBanner({ annotation: ann });

banner.querySelector('button').click();

assert.calledWith(fakeStore.annotation.unhide, {id: 'hidden-id'});
assert.calledWith(fakeStore.annotation.unhide, {id: 'ann-id'});
});

it('reports an error if unhiding the annotation fails', function (done) {
fakeAnnotationUI.isHiddenByModerator.returns(true);
var banner = createBanner({ annotationId: 'hidden-id'} );
var ann = moderatedAnnotation({ hidden: true });
var banner = createBanner({ annotation: ann });
fakeStore.annotation.unhide.returns(Promise.reject(new Error('Network Error')));

banner.querySelector('button').click();

setTimeout(function () {
assert.calledWith(fakeFlash.error, 'Network Error');
assert.calledWith(fakeFlash.error, 'Failed to unhide annotation');
done();
}, 0);
});
Expand Down
56 changes: 56 additions & 0 deletions src/sidebar/reducers/annotations.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,26 @@ var update = {
});
return {annotations: annotations};
},

HIDE_ANNOTATION: function (state, action) {
var anns = state.annotations.map(function (ann) {
if (ann.id !== action.id) {
return ann;
}
return Object.assign({}, ann, { hidden: true });
});
return {annotations: anns};
},

UNHIDE_ANNOTATION: function (state, action) {
var anns = state.annotations.map(function (ann) {
if (ann.id !== action.id) {
return ann;
}
return Object.assign({}, ann, { hidden: false });
});
return {annotations: anns};
},
};

var actions = util.actionTypes(update);
Expand Down Expand Up @@ -282,6 +302,32 @@ function updateAnchorStatus(id, tag, isOrphan) {
};
}

/**
* Update the local hidden state of an annotation.
*
* This updates an annotation to reflect the fact that it has been hidden from
* non-moderators.
*/
function hideAnnotation(id) {
return {
type: actions.HIDE_ANNOTATION,
id: id,
};
}

/**
* Update the local hidden state of an annotation.
*
* This updates an annotation to reflect the fact that it has been made visible
* to non-moderators.
*/
function unhideAnnotation(id) {
return {
type: actions.UNHIDE_ANNOTATION,
id: id,
};
}

/**
* Return all loaded annotations which have been saved to the server.
*
Expand Down Expand Up @@ -319,6 +365,13 @@ function findIDsForTags(state, tags) {
return ids;
}

/**
* Return the annotation with the given ID.
*/
function findAnnotationByID(state, id) {
return findByID(state.annotations, id);
}

module.exports = {
init: init,
update: update,
Expand All @@ -328,10 +381,13 @@ module.exports = {
removeAnnotations: removeAnnotations,
updateAnchorStatus: updateAnchorStatus,
updateFlagStatus: updateFlagStatus,
hideAnnotation: hideAnnotation,
unhideAnnotation: unhideAnnotation,
},

// Selectors
annotationExists: annotationExists,
findAnnotationByID: findAnnotationByID,
findIDsForTags: findIDsForTags,
savedAnnotations: savedAnnotations,
};
Loading