Commit a762a135 authored by Sheetal Umesh Kumar's avatar Sheetal Umesh Kumar Committed by GitHub

Implement moderation UI for group reader for flagging an annotation. (#296)

https://github.com/hypothesis/product-backlog/issues/181
parent 3030bf40
......@@ -60,6 +60,7 @@ function analytics($analytics, $window, settings) {
events: {
ANNOTATION_CREATED: 'annotationCreated',
ANNOTATION_DELETED: 'annotationDeleted',
ANNOTATION_FLAGGED: 'annotationFlagged',
ANNOTATION_UPDATED: 'annotationUpdated',
HIGHLIGHT_CREATED: 'highlightCreated',
HIGHLIGHT_UPDATED: 'highlightUpdated',
......
......@@ -54,11 +54,21 @@ function annotationMapper($rootScope, annotationUI, store) {
});
}
function flagAnnotation(annot) {
return store.flag.create(null,{
annotation: annot.id,
}).then(function () {
$rootScope.$broadcast(events.ANNOTATION_FLAGGED, annot);
return annot;
});
}
return {
loadAnnotations: loadAnnotations,
unloadAnnotations: unloadAnnotations,
createAnnotation: createAnnotation,
deleteAnnotation: deleteAnnotation,
flagAnnotation: flagAnnotation,
};
}
......
......@@ -220,6 +220,22 @@ function AnnotationController(
return permissions.permits(action, vm.annotation, session.state.userid);
};
/**
* @ngdoc method
* @name annotation.AnnotationController#flag
* @description Flag the annotation.
*/
vm.flag = function() {
var onRejected = function(reason) {
flash.error(
errorMessage(reason), 'Flagging annotation failed');
};
annotationMapper.flagAnnotation(vm.annotation).then(function(){
analytics.track(analytics.events.ANNOTATION_FLAGGED);
vm.isFlagged = true;
}, onRejected);
};
/**
* @ngdoc method
* @name annotation.AnnotationController#delete
......
......@@ -138,6 +138,7 @@ describe('annotation', function() {
},
}),
deleteAnnotation: sandbox.stub(),
flagAnnotation: sandbox.stub(),
};
var fakeAnnotationUI = {};
......@@ -689,6 +690,47 @@ describe('annotation', function() {
});
});
describe('#flag()', function() {
beforeEach(function() {
fakeAnnotationMapper.flagAnnotation = sandbox.stub();
});
it(
'calls annotationMapper.flag() when an annotation is flagged',
function(done) {
var parts = createDirective();
fakeAnnotationMapper.flagAnnotation.returns($q.resolve());
parts.controller.flag();
assert.calledWith(fakeAnnotationMapper.flagAnnotation,
parts.annotation);
done();
}
);
it('flashes an error if the flag fails', function(done) {
var controller = createDirective().controller;
fakeAnnotationMapper.flagAnnotation.returns(Promise.reject({
status: 500,
statusText: 'Server error',
}));
controller.flag();
setTimeout(function () {
assert.calledWith(fakeFlash.error, '500 Server error', 'Flagging annotation failed');
done();
}, 0);
});
it('doesn\'t flash an error if the flag succeeds', function(done) {
var controller = createDirective().controller;
fakeAnnotationMapper.flagAnnotation.returns($q.resolve());
controller.flag();
setTimeout(function () {
assert.notCalled(fakeFlash.error);
done();
}, 0);
});
});
describe('#documentMeta()', function () {
it('returns the domain, title link and text for the annotation', function () {
var annot = fixtures.defaultAnnotation();
......
......@@ -31,6 +31,9 @@ module.exports = {
/** An annotation was either deleted or unloaded. */
ANNOTATION_DELETED: 'annotationDeleted',
/** An annotation was flagged. */
ANNOTATION_FLAGGED: 'annotationFlagged',
/** An annotation has been updated. */
ANNOTATION_UPDATED: 'annotationUpdated',
......
......@@ -143,6 +143,9 @@ function store($http, $q, auth, settings) {
get: apiCall('annotation.read'),
update: apiCall('annotation.update'),
},
flag: {
create: apiCall('flag.create'),
},
profile: {
read: apiCall('profile.read'),
update: apiCall('profile.update'),
......
......@@ -185,6 +185,23 @@
on-close="vm.showShareDialog = false">
</annotation-share-dialog>
</span>
<span ng-if="vm.isThirdPartyUser()">
<button class="btn btn-clean annotation-action-btn"
ng-if="!vm.isFlagged"
ng-click="vm.flag()"
ng-disabled="vm.isDeleted()"
aria-label="Flag"
h-tooltip>
<i class="h-icon-annotation-flag btn-icon"></i>
</button>
<button class="btn btn-clean annotation-action-btn"
ng-if="vm.isFlagged"
ng-disabled="vm.isDeleted()"
aria-label="Annotation has been flagged"
h-tooltip>
<i class="h-icon-annotation-flag annotation--flagged btn-icon"></i>
</button>
</span>
</div>
</footer>
</div>
......@@ -17,6 +17,9 @@ describe('annotationMapper', function() {
annotation: {
delete: sinon.stub().returns(Promise.resolve({})),
},
flag: {
create: sinon.stub().returns(Promise.resolve({})),
},
};
angular.module('app', [])
.service('annotationMapper', require('../annotation-mapper'))
......@@ -124,6 +127,24 @@ describe('annotationMapper', function() {
});
});
describe('#flagAnnotation()', function () {
it('flags an annotation', function () {
var ann = {id: 'test-id'};
annotationMapper.flagAnnotation(ann);
assert.calledOnce(fakeStore.flag.create);
assert.calledWith(fakeStore.flag.create, null, {annotation: ann.id});
});
it('emits the "annotationFlagged" event', function (done) {
sandbox.stub($rootScope, '$broadcast');
var ann = {id: 'test-id'};
annotationMapper.flagAnnotation(ann).then(function () {
assert.calledWith($rootScope.$broadcast,
events.ANNOTATION_FLAGGED, ann);
}).then(done, done);
});
});
describe('#createAnnotation()', function () {
it('creates a new annotation resource', function () {
var ann = {};
......
......@@ -67,6 +67,12 @@ describe('store', function () {
url: 'http://example.com/api/annotations/:id',
},
},
flag: {
create: {
method: 'POST',
url: 'http://example.com/api/flags',
},
},
search: {
method: 'GET',
url: 'http://example.com/api/search',
......@@ -123,6 +129,18 @@ describe('store', function () {
$httpBackend.flush();
});
it('flags an annotation', function (done) {
store.flag.create(null, {annotation: 'an-id'}).then(function () {
done();
});
$httpBackend.expectPOST('http://example.com/api/flags')
.respond(function () {
return [204, {}, {}];
});
$httpBackend.flush();
});
it('removes internal properties before sending data to the server', function (done) {
var annotation = {
$highlight: true,
......
......@@ -261,3 +261,8 @@
margin-bottom: $layout-h-margin - 3px;
}
}
.annotation--flagged {
color: $brand-color;
cursor: default;
}
This diff is collapsed.
......@@ -20,6 +20,9 @@
-moz-osx-font-smoothing: grayscale;
}
.h-icon-annotation-flag:before {
content: "\e90d";
}
.h-icon-clipboard:before {
content: "\e90c";
}
......
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