Repository: internetarchive/openlibrary

Issue:
Migrate "Request Merge" from the Search Results page to the Merge UI
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->

### Describe the problem that you'd like solved
<!-- A clear and concise description of what you want to happen. -->
Today, members of usergroup `librarians` are able to suggest work and author merges from search result pages.  `super-librarians` later use our merge UIs to review, edit, and submit (or reject) the merge requests (MRs).
Members of both groups would likely be better served if `librarians` submitted MRs from our merge UIs.  This would likely improve the quality of our MRs, and give `librarians` practice using the merge tools.

### Proposal & Constraints
<!-- What is the proposed solution / implementation? Is there a precedent of this approach succeeding elsewhere? -->
Grant `librarians` access to our author and work merge UIs.  If a `librarian` is viewing a merge UI, the call to action for the merge buttons should be something like "Request Merge", and the red merge rejection button should not appear.  After selecting the primary and duplicate records, a `librarian` can suggest an MR by clicking the "Request Merge" button.

<!-- Which suggestions or requirements should be considered for how feature needs to appear or be implemented? -->

### Additional context
<!-- Add any other context or screenshots about the feature request here. -->

### Stakeholders
<!-- @ tag stakeholders of this bug -->


Discussion:

Resolving patch:
diff --git a/openlibrary/plugins/upstream/code.py b/openlibrary/plugins/upstream/code.py
--- a/openlibrary/plugins/upstream/code.py
+++ b/openlibrary/plugins/upstream/code.py
@@ -29,6 +29,10 @@
 if not config.get('coverstore_url'):
     config.coverstore_url = "https://covers.openlibrary.org"  # type: ignore[attr-defined]
 
+import logging
+
+logger = logging.getLogger('openlibrary.plugins.upstream.code')
+
 
 class static(delegate.page):
     path = "/images/.*"
@@ -100,16 +104,24 @@ class merge_work(delegate.page):
     path = "/works/merge"
 
     def GET(self):
-        i = web.input(records='', mrid=None)
+        i = web.input(records='', mrid=None, primary=None)
         user = web.ctx.site.get_user()
         has_access = user and (
             (user.is_admin() or user.is_librarian())
-            and user.is_usergroup_member('/usergroup/super-librarians')
+            or user.is_usergroup_member('/usergroup/super-librarians')
         )
         if not has_access:
             raise web.HTTPError('403 Forbidden')
 
-        return render_template('merge/works', mrid=i.mrid)
+        optional_kwargs = {}
+        if not (
+            user.is_usergroup_member('/usergroup/super-librarians') or user.is_admin()
+        ):
+            optional_kwargs['can_merge'] = 'false'
+
+        return render_template(
+            'merge/works', mrid=i.mrid, primary=i.primary, **optional_kwargs
+        )
 
 
 @web.memoize
diff --git a/openlibrary/plugins/upstream/edits.py b/openlibrary/plugins/upstream/edits.py
--- a/openlibrary/plugins/upstream/edits.py
+++ b/openlibrary/plugins/upstream/edits.py
@@ -15,7 +15,6 @@ def response(status='ok', **kwargs):
 
 
 def process_merge_request(rtype, data):
-
     user = accounts.get_current_user()
     username = user['key'].split('/')[-1]
     # Request types can be: create-request, update-request
@@ -66,7 +65,12 @@ def POST(self):
 
     @staticmethod
     def create_request(
-        username, action='', mr_type=None, olids='', comment: str = None
+        username,
+        action='',
+        mr_type=None,
+        olids='',
+        comment: str = None,
+        primary: str = None,
     ):
         def is_valid_action(action):
             return action in ('create-pending', 'create-merged')
@@ -81,7 +85,7 @@ def needs_unique_url(mr_type):
             olid_list = olids.split(',')
 
             title = community_edits_queue.create_title(mr_type, olid_list)
-            url = community_edits_queue.create_url(mr_type, olid_list)
+            url = community_edits_queue.create_url(mr_type, olid_list, primary=primary)
 
             # Validate URL
             is_valid_url = True
@@ -161,9 +165,10 @@ def update_request(username, action='', mrid=None, comment=None):
         return resp
 
     @staticmethod
-    def create_url(mr_type: int, olids: list[str]) -> str:
+    def create_url(mr_type: int, olids: list[str], primary: str = None) -> str:
         if mr_type == CommunityEditsQueue.TYPE['WORK_MERGE']:
-            return f'/works/merge?records={",".join(olids)}'
+            primary_param = f'&primary={primary}' if primary else ''
+            return f'/works/merge?records={",".join(olids)}{primary_param}'
         elif mr_type == CommunityEditsQueue.TYPE['AUTHOR_MERGE']:
             return f'/authors/merge?key={"&key=".join(olids)}'
         return ''
diff --git a/openlibrary/plugins/upstream/merge_authors.py b/openlibrary/plugins/upstream/merge_authors.py
--- a/openlibrary/plugins/upstream/merge_authors.py
+++ b/openlibrary/plugins/upstream/merge_authors.py
@@ -8,6 +8,7 @@
 from infogami.infobase.client import ClientException
 from infogami.utils import delegate
 from infogami.utils.view import render_template, safeint
+from openlibrary.accounts import get_current_user
 from openlibrary.plugins.upstream.edits import process_merge_request
 from openlibrary.plugins.worksearch.code import top_books_from_author
 from openlibrary.utils import uniq, dicthash
@@ -277,11 +278,17 @@ def GET(self):
 
         # filter bad keys
         keys = self.filter_authors(keys)
+
+        user = get_current_user()
+        can_merge = user and (
+            user.is_admin() or user.is_usergroup_member('/usergroup/super-librarians')
+        )
         return render_template(
             'merge/authors',
             keys,
             top_books_from_author=top_books_from_author,
             mrid=i.mrid,
+            can_merge=can_merge,
         )
 
     def POST(self):
@@ -289,6 +296,14 @@ def POST(self):
         keys = uniq(i.key)
         selected = uniq(i.merge_key)
 
+        user = get_current_user()
+        can_merge = user and (
+            user.is_admin() or user.is_usergroup_member('/usergroup/super-librarians')
+        )
+        can_request_merge = not can_merge and (
+            user and user.is_usergroup_member('/usergroup/librarians')
+        )
+
         # filter bad keys
         keys = self.filter_authors(keys)
 
@@ -306,6 +321,21 @@ def POST(self):
                 formdata=formdata,
                 mrid=i.mrid,
             )
+        elif can_request_merge:
+            # Create merge author request:
+            selected.insert(0, i.master)
+            data = {
+                'mr_type': 2,
+                'action': 'create-pending',
+                'olids': ','.join(selected),
+            }
+
+            if i.comment:
+                data['comment'] = i.comment
+
+            process_merge_request('create-request', data)
+
+            raise web.seeother('/search/authors')
         else:
             # redirect to the master. The master will display a progressbar and call the merge_authors_json to trigger the merge.
             redir_url = (


Test patch: