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

Update moderation API use #336

merged 7 commits into from
Apr 6, 2017

Conversation

robertknight
Copy link
Member

@robertknight robertknight commented Apr 4, 2017

Update logic for reading and updating moderation state to reflect new APIs

Moderation state was originally going to be retrieved via a separate API
call from /api/search but is now returned as a moderation field in the
annotation response.

This PR simplifies and updates the client to reflect this.

  • Remove the moderation state (in src/sidebar/reducers/moderation) from the Redux store since this state is now part of the annotation itself.
  • Update moderation banner to read moderation state from the annotation's moderation property (see src/sidebar/components/moderation-banner)
  • Add annotation actions (in src/sidebar/reducers/annotations) to update the hidden state of an annotation after a call to hide or unhide the annotation succeeds. This is needed because the hide/unhide API calls do not return the updated annotation content.
  • Add API routes to client for hiding and unhiding an annotation (in src/sidebar/store)
  • Emit hardcoded errors when hiding and unhiding the annotation fails. I will fix the client to use the error message from the API in future but this needs some refactoring of the API client to make the type of error thrown when a response fails reliably Error-like.

@codecov-io
Copy link

codecov-io commented Apr 4, 2017

Codecov Report

Merging #336 into master will decrease coverage by 0.03%.
The diff coverage is 94.59%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #336      +/-   ##
==========================================
- Coverage   76.56%   76.52%   -0.04%     
==========================================
  Files         120      119       -1     
  Lines        5953     5948       -5     
  Branches      966      967       +1     
==========================================
- Hits         4558     4552       -6     
- Misses       1395     1396       +1
Impacted Files Coverage Δ
src/sidebar/store.js 92.64% <ø> (ø) ⬆️
src/sidebar/annotation-ui.js 100% <ø> (ø) ⬆️
src/sidebar/reducers/index.js 100% <ø> (ø) ⬆️
src/sidebar/components/annotation.js 88.34% <100%> (ø) ⬆️
src/sidebar/annotation-metadata.js 100% <100%> (ø) ⬆️
src/sidebar/components/moderation-banner.js 100% <100%> (ø) ⬆️
src/sidebar/reducers/annotations.js 97.91% <88.88%> (-1.29%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8de91da...b71a6ff. Read the comment docs.

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.

@robertknight robertknight requested a review from sheetaluk April 4, 2017 11:48
@robertknight robertknight force-pushed the update-moderation-api-use branch from ab01aee to e053185 Compare April 4, 2017 16:47
Copy link
Contributor

@seanh seanh left a comment

Choose a reason for hiding this comment

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

This all looks pretty solid. I'll do some manual testing to make sure it works but want to see what you think of some requested changes first (and then if the changes are made, I'll test the final version):

My only suggestion is to maybe move a handful of methods (isHiddenByModerator(), flagCount(), isHidden()) into annotation-metadata and add unit tests to them there, both to avoid duplication and to have things more unit tested. Let me know what you think of this:

It seems like annotation-metadata is the place that we have to put functions that compute values based on an annotation's data, for example isReply(), isNew(), isPublic() are already in there, and isHiddenByModerator() etc seem to fit with that.

But also it seems like at least some components, for example moderation-banner, are tested using what I'd call an integration testing style where the tests work by looking for things in the rendered HTML and asserting against that.

For example the moderation-banner tests are things like 'displays the number of flags the annotation has received' that tests that the text content of an HTML element is 'Flagged for review x10'. If I break the moderation banner component's flagCount() method to always return 0 then four tests fail, most of these test failures don't seem relevant to the bug and none of them point me very accurately towards the line of code that's wrong, at least 3/4 of these test failures are quite misleading about what the bug is:

  • displays the number of flags the annotation has received
  • displays in a more compact form if the annotation is a reply
  • hides the annotation if "Hide" is clicked
  • reports an error if hiding the annotation fails

Worse, if I break flagCount() to throw an exception instead then (after I fix an afterEach() that crashes and prevents most of the tests from running) 8 different tests fail due to a one-line bug.

I see this in the annotation component tests too - there's no direct unit tests for isHiddenByModerator(), if I break it to always return false then the test that fails is 'renders hidden annotations with a custom text class' which is a test that works by looking for a CSS class in the element's rendered HTML.

Again if isHiddenByModerator() is broken the test failure that I get isn't particularly "close" to isHiddenByModerator(), I don't think the test failure zeros in on the particular line of code that has the bug like direct unit tests of isHiddenByModerator() would.

And again if I break isHiddenByModerator() to do something completely different, for example throw an exception, then 75 tests fail (and none of the test failures points very accurately towards the line of code that's broken)

I think tests that work by looking at the HTML probably do make sense as a way of testing components, since quite a lot of what makes up the implementation of a component is its templates.

But I think the problem here is that we're putting too much logic into components, when we should be keeping them as simple as possible (for example with as little logic and as few different paths through the code as possible).

I think the solution to this might be:

  1. Move logic like isHiddenByModerator() into places like annotation-metadata which is not a component but a bag of methods that are each unit tested directly.

  2. Continue to test components like the annotation and moderation-banner components in a more integrated style via their templates as we do currently, but for test isolation's sake consider using a mock annotation-metadata in these tests. (So that for example if annotation-metadata.isHiddenByModerator() is broken then the unit tests for that method in annotation-metadata's tests fail, but dozens of component tests don't).

What do you think? (Sorry for the long explanation, I think it would actually be a small change to this PR.)

if (!vm.annotation.moderation) {
return false;
}
return vm.annotation.moderation.is_hidden;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think there's a test missing here. If I change this function to always return false then renders hidden annotations with a custom text class fails. But if I change it to always return true then nothing fails. I wonder if it needs a does not render the custom text class on not hidden annotations test?

return [204, {}, {}];
});
$httpBackend.flush();
});
Copy link
Contributor

Choose a reason for hiding this comment

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

I wanted to play around with this test but it seems to crash when run on its own. It looks like maybe the before() in these tests is implicitly dependent on some other tests running first and creating the Angular "h" module. It's not a problem with this pull request (which just adds two new tests that are exactly like the existing ones), just something we might want to fix after.

diff --git a/src/sidebar/test/store-test.js b/src/sidebar/test/store-test.js
index b898b29..a55d912 100644
--- a/src/sidebar/test/store-test.js
+++ b/src/sidebar/test/store-test.js
@@ -150,7 +150,7 @@ describe('store', function () {
     $httpBackend.flush();
   });
 
-  it('hides an annotation', function (done) {
+  it.only('hides an annotation', function (done) {
     store.annotation.hide({id: 'an-id'}).then(function () {
       done();
     });
$ make test
npm test

> hypothesis@1.9.0 test /home/seanh/Projects/client
> gulp test

[10:54:16] Using gulpfile ~/Projects/client/gulpfile.js
[10:54:16] Starting 'test'...
decorator { '0': [Function],
  '1': 500,
  '2': true,
  '3': { level: 'debug', format: '%b %T: %m', terminal: true } }

START:
05 04 2017 10:54:25.718:INFO [framework.browserify]: bundle built
05 04 2017 10:54:25.747:INFO [karma]: Karma v1.1.0 server started at http://localhost:9876/
05 04 2017 10:54:25.747:INFO [launcher]: Launching browser PhantomJS with unlimited concurrency
05 04 2017 10:54:25.757:INFO [launcher]: Starting browser PhantomJS
05 04 2017 10:54:25.991:INFO [PhantomJS 2.1.1 (Linux 0.0.0)]: Connected on socket /#iumxM0kD00OwoEd9AAAA with id 97156479

Finished in 0.118 secs / 0 secs

SUMMARY:
✔ 0 tests completed
✖ 1 test failed

FAILED TESTS:
  store
    ✖ "before all" hook
      PhantomJS 2.1.1 (Linux 0.0.0)
    [$injector:nomod] Module 'h' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
    http://errors.angularjs.org/1.5.6/$injector/nomod?p0=h
    /node_modules/angular/angular.js:2103:0 <- /tmp/d6a061fd0af4d6d989b337980871d659.browserify:6818:71
    ensure@/node_modules/angular/angular.js:2025:0 <- /tmp/d6a061fd0af4d6d989b337980871d659.browserify:6740:45
    module@/node_modules/angular/angular.js:2099:0 <- /tmp/d6a061fd0af4d6d989b337980871d659.browserify:6814:20
    /tmp/sidebar/test/store-test.js:14:0 <- /tmp/d6a061fd0af4d6d989b337980871d659.browserify:109988:19

[10:54:26] 'test' errored after 10 s
[10:54:26] Error: 1
  at formatError (/home/seanh/Projects/client/node_modules/gulp/bin/gulp.js:169:10)
  at Gulp.<anonymous> (/home/seanh/Projects/client/node_modules/gulp/bin/gulp.js:195:15)
  at emitOne (events.js:96:13)
  at Gulp.emit (events.js:191:7)
  at Gulp.Orchestrator._emitTaskDone (/home/seanh/Projects/client/node_modules/orchestrator/index.js:264:8)
  at /home/seanh/Projects/client/node_modules/orchestrator/index.js:275:23
  at finish (/home/seanh/Projects/client/node_modules/orchestrator/lib/runTask.js:21:8)
  at cb (/home/seanh/Projects/client/node_modules/orchestrator/lib/runTask.js:29:3)
  at removeAllListeners (/home/seanh/Projects/client/node_modules/karma/lib/server.js:379:7)
  at Server.<anonymous> (/home/seanh/Projects/client/node_modules/karma/lib/server.js:390:9)
  at Object.onceWrapper (events.js:293:19)
  at emitNone (events.js:91:20)
  at Server.emit (events.js:188:7)
  at emitCloseNT (net.js:1562:8)
  at _combinedTickCallback (internal/process/next_tick.js:77:11)
  at process._tickDomainCallback (internal/process/next_tick.js:128:9)

npm ERR! Test failed.  See above for more details.
Makefile:16: recipe for target 'test' failed
make: *** [test] Error 1

@seanh
Copy link
Contributor

seanh commented Apr 5, 2017

This is looking good to me. I think I still need to manually test it, that'll have to wait until tomorrow morning.

The `hide` API has now been implemented on the server. The `unhide` API
has been agreed but not yet implemented.
… annotation

After an annotation has been hidden or unhidden by a moderator, the
moderation state needs to be updated locally to reflect the change.
In the `<moderation-banner>` and `<annotation>` components, read the
moderation state from the annotation object instead of fetching it from
the app state.
Moderation state was originally going to be retrieved via a separate API
call from /api/search but is now returned as a `moderation` field in the
annotation response.

This commit removes the separate `moderation` state from the Redux
store.
The previous code was incorrect because depending on the failure, the
error might not be an `Error`. This needs to be fixed in the API client
but for the moment, sidestep the problem by just displaying a fixed
string.
The `hidden` state has been moved from the `annotation.moderation` key
to a top-level property of the annotation.
Move the flag count to a helper which is easier to test and re-use.
@seanh seanh force-pushed the update-moderation-api-use branch from 90f0fdb to b71a6ff Compare April 6, 2017 12:43
@seanh
Copy link
Contributor

seanh commented Apr 6, 2017

Rebased. I've tested this locally and it all seems to work

@seanh seanh merged commit 7c3ba4c into master Apr 6, 2017
@seanh seanh deleted the update-moderation-api-use branch April 6, 2017 12:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants