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

AO3-4359 Mass import bookmarks #2442

Merged
merged 34 commits into from Jul 10, 2016

Conversation

ariana-paris
Copy link
Contributor

@ariana-paris ariana-paris commented Apr 28, 2016

https://otwarchive.atlassian.net/browse/AO3-4359

Adds bookmark importing capability to the mass import API. Open Doors sometimes import sites which include works that are links to stories on another site which is not being imported (and therefore hasn't granted permission to scrape in the story contents). This will allow those works to be imported as bookmarks on the Archive, referencing the story's currently location rather than importing it.

In order to achieve this, I have also:

  • refactored the existing code so that the authentication handling and work imports are in separate files
  • rationalised the API end points so that instead of api/v1/import, we now have api/v1/works and api/v1/bookmarks (paving the way for, say, collections, users or, idk, videos in future, whatever comes up next)
  • added an original_id field to all the API requests so the remote site can pass in its own id for the story/bookmark it's importing and use that to match up the Archive's response to the relevant item (currently, the OD temporary sites rely on the responses being in the same order as the requests which is risky)
  • converted routes.rb to use Ruby 1.9 hashes where possible

# Conflicts:
#	app/controllers/api/v1/import_controller.rb
#	spec/api/api_spec.rb
…into AO3-4359-Mass-import-bookmarks

# Conflicts:
#	app/controllers/api/v1/bookmarks_controller.rb
#	app/controllers/api/v1/works_controller.rb
#	spec/api/api_spec.rb
@@ -143,12 +136,22 @@ class StoryParser
end

describe "#download_and_parse_chapters_into_story" do

Choose a reason for hiding this comment

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

Extra empty line detected at block body beginning.

# Set messages based on success and error flags
def response_message(messages)
if @some_success && @some_errors
messages << "At least one bookmark was not created. Please check the bookmark array for further information."
Copy link
Member

Choose a reason for hiding this comment

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

Will an archivist know what the "bookmark array" is? (I ask mostly because I'm not looking at the page and I don't know.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point - that's an implementation detail that doesn't need to be in the message. The developer who implements the API will presumably turn the JSON array into something more user friendly, so I'll rephrase that as "individual bookmark messages".

@@ -0,0 +1,117 @@
class Api::V1::BookmarksController < Api::V1::BaseController
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be ExternalBookmarksController? We may well want to add API for regular bookmarks at some point

Copy link
Contributor Author

Choose a reason for hiding this comment

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

With all my focus on the Open Doors use case, I hadn't thought of the two bookmark types possibly being used in future - I'll rename this and have a think about whether the URL end-point needs to be changed as well. (i.e. would it still make sense to POST both external and internal bookmarks to api/v1/bookmarks, or should there be a separate api/v1/external-bookmarks, and if so what happens to GET requests when we get around to them? Should both types be returned by a call to bookmarks?)

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, I was thinking about External Works, I realize now. And the actual bookmark object is the same, so all bookmark creation makes sense in BookmarksController, but where the process of creating external works belongs... IDK, probably it should be here after all and just made conditional on whether the url passed is for an AO3 work or not, that seems most parallel to the existing process since External Works controller just makes the new external work object and passes off the actual creation to the Bookmarks controller via the form.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As discussed previously, I've added some error handling. As a general rule, I like to delegate as much as possible to the back-end when writing an API, as the model should be performing essential data validation before anything goes into the database, and this theoretically ensures consistency between the UI and the API.

However, the ExternalWorks model doesn't check for the presence of a fandom and returns a weird message for a missing author, so I've intercepted some invalid requests in the API controller to return helpful messages from there instead, and added tests to check that this is happening.

end
end


Choose a reason for hiding this comment

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

Extra blank line detected.


def create
archivist = User.find_by_login(params[:archivist])
external_bookmarks = params[:bookmarks]
Copy link
Member

Choose a reason for hiding this comment

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

here is where I think the naming is confusing me -- these are just bookmarks and they happen to be of external works. Can we just call them bookmarks throughout? it looks to me like this code would actually work just fine for a NON external work too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure it would work for the other types as is, because the external_bookmark method in the API bookmark_controller coerces the JSON input into a structure with an external hash in it, which IIRC is the structure passed back by the normal bookmark_controller when creating a bookmark for an external work. I think this brings the external= virtual attribute in the Bookmark model into play.

Having said that, I didn't inspect the other bookmark types, and it's been a while since I unpicked how bookmarks are created, so you might well be right. I'll rename that variable to make it less external-centric which will make it easier to adapt this if we have a use case for creating other types of bookmarks in future.

it "should return 400 Bad Request if an invalid URL is specified" do
post "/api/v1/import",
{ archivist: @user.login,
bookmarks: [ bookmark.merge!(url: "") ]

Choose a reason for hiding this comment

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

Space inside square brackets detected.


# Returns a hash
def import_bookmark(archivist, params)
bookmark_request = external_bookmark(archivist, params)
Copy link
Member

Choose a reason for hiding this comment

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

Sorry to be nitpicky some more: it feels to me like even here it makes more sense to have this remain generic (eg "build_bookmark_request(user, params)") because the only place where it actually becomes specific to external works is in the currently named external_bookmark method and bookmark_errors (which is already named generically) -- and in those two places, I would suggest adding a comment explaining that for the moment this ONLY works for bookmarks for external works. There is basically no difference except if the "external" stuff is passed AFAIK.

WebMock.reset!
end


Choose a reason for hiding this comment

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

Extra blank line detected.

@shalott
Copy link
Member

shalott commented Jun 10, 2016

Looks good to me!

@sarken sarken merged commit af3a2ec into otwcode:master Jul 10, 2016
@ariana-paris ariana-paris deleted the AO3-4359-Mass-import-bookmarks branch September 21, 2016 16:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants