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
AO3-4359 Mass import bookmarks #2442
Conversation
# 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 | |||
|
There was a problem hiding this comment.
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." |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
?)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
…-4359-Mass-import-bookmarks
end | ||
end | ||
|
||
|
There was a problem hiding this comment.
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] |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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: "") ] |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
…ExternalWork model
WebMock.reset! | ||
end | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra blank line detected.
Looks good to me! |
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:
api/v1/import
, we now haveapi/v1/works
andapi/v1/bookmarks
(paving the way for, say,collections
,users
or, idk,videos
in future, whatever comes up next)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)routes.rb
to use Ruby 1.9 hashes where possible