diff --git a/openreview/api/client.py b/openreview/api/client.py index 0e952bd6ac..5cb9dd1c20 100644 --- a/openreview/api/client.py +++ b/openreview/api/client.py @@ -3575,6 +3575,27 @@ def __str__(self): pp = pprint.PrettyPrinter() return pp.pformat(vars(self)) + @property + def authors(self): + """ + Returns the authors as a canonical list of ``{'fullname', 'username'}`` dicts, + regardless of whether the underlying content stores them as a list of objects + (current schema) or as parallel ``authors``/``authorids`` arrays (legacy schema). + """ + if not self.content: + return [] + authors_value = self.content.get('authors', {}).get('value') or [] + if authors_value and isinstance(authors_value[0], dict): + return [{'fullname': a.get('fullname', ''), 'username': a.get('username', '')} for a in authors_value] + authorids_value = self.content.get('authorids', {}).get('value') or [] + return [ + { + 'fullname': authors_value[i] if i < len(authors_value) else '', + 'username': authorids_value[i] if i < len(authorids_value) else '' + } + for i in range(max(len(authors_value), len(authorids_value))) + ] + def to_json(self): """ Converts Note instance to a dictionary. The instance variable names are the keys and their values the values of the dictinary. diff --git a/openreview/profile/management.py b/openreview/profile/management.py index 528880262c..3b9dbff1cc 100644 --- a/openreview/profile/management.py +++ b/openreview/profile/management.py @@ -243,9 +243,9 @@ def set_public_article_invitations(self): signatures = [self.super_user], invitation = openreview.api.Invitation( id=self.public_article_meta_invitation_id, - invitees=[self.arxiv_group_id, self.dblp_group_id, self.orcid_group_id], - readers=[self.arxiv_group_id, self.dblp_group_id, self.orcid_group_id], - signatures=[self.public_article_group_id], + invitees=[self.arxiv_group_id, self.dblp_group_id, self.orcid_group_id, self.support_group_id], + readers=[self.arxiv_group_id, self.dblp_group_id, self.orcid_group_id, self.support_group_id], + signatures=[self.public_article_group_id], edit=True ) ) @@ -296,6 +296,15 @@ def set_public_article_invitations(self): } } }, + 'author_name': { + 'order': 3, + 'description': 'Enter the author name at the given index.', + 'value': { + 'param': { + 'type': 'string', + } + } + }, }, 'note': { 'id': { @@ -304,21 +313,25 @@ def set_public_article_invitations(self): } }, 'content': { - 'authorids': { + 'authors': { 'order': 2, 'value': { 'param': { 'const': { 'replace': { 'index': '${6/content/author_index/value}', - 'value': '${6/content/author_id/value}' + 'value': { + 'fullname': '${7/content/author_name/value}', + 'username': '${7/content/author_id/value}' + } } - } + }, + 'hidden': True } } } } - } + } } ) ) @@ -334,11 +347,11 @@ def set_public_article_invitations(self): writers=[self.public_article_group_id], signatures=[self.public_article_group_id], invitees=['~', self.dblp_group_id, self.arxiv_group_id, self.orcid_group_id, self.support_group_id], - preprocess=self.get_process_content('process/author_coreference_pre_process.js'), + preprocess=self.get_process_content('process/author_removal_pre_process.js'), edit={ 'readers': ['everyone'], - 'signatures': { - 'param': { + 'signatures': { + 'param': { 'items': [ { 'prefix': '~.*', 'optional': True }, { 'value': self.support_group_id, 'optional': True }, @@ -346,7 +359,7 @@ def set_public_article_invitations(self): { 'value': self.arxiv_group_id, 'optional': True }, { 'value': self.orcid_group_id, 'optional': True } ] - } + } }, 'writers': [self.public_article_group_id], 'content': { @@ -369,6 +382,15 @@ def set_public_article_invitations(self): } } }, + 'author_name': { + 'order': 3, + 'description': 'Enter the author name at the given index.', + 'value': { + 'param': { + 'type': 'string', + } + } + }, }, 'note': { 'id': { @@ -377,21 +399,25 @@ def set_public_article_invitations(self): } }, 'content': { - 'authorids': { + 'authors': { 'order': 2, 'value': { 'param': { 'const': { 'replace': { 'index': '${6/content/author_index/value}', - 'value': '${6/content/author_id/value}' + 'value': { + 'fullname': '${7/content/author_name/value}', + 'username': '${7/content/author_id/value}' + } } - } + }, + 'hidden': True } } } } - } + } } ) ) @@ -966,24 +992,19 @@ def set_dblp_invitations(self): }, 'authors': { 'order': 2, + 'description': 'Authors of paper.', 'value': { 'param': { - 'type': 'string[]', - 'regex': '[^;,\\n]+(,[^,\\n]+)*' + 'type': 'author{}', + 'properties': { + 'fullname': { 'param': { 'type': 'string' } }, + 'username': { 'param': { 'type': 'string' } }, + }, } } }, - 'authorids': { - 'order': 3, - 'value': { - 'param': { - 'type': 'string[]', - 'optional': True - } - } - }, 'venue': { - 'order': 4, + 'order': 3, 'description': 'Enter the venue where the paper was published.', 'value': { 'param': { @@ -993,7 +1014,7 @@ def set_dblp_invitations(self): } }, 'venueid': { - 'order': 5, + 'order': 4, 'value': { 'param': { 'type': "string", @@ -1154,27 +1175,22 @@ def set_arxiv_invitations(self): }, 'authors': { 'order': 2, + 'description': 'Authors of paper.', 'value': { 'param': { - 'type': 'string[]', - 'regex': '[^;,\\n]+(,[^,\\n]+)*' + 'type': 'author{}', + 'properties': { + 'fullname': { 'param': { 'type': 'string' } }, + 'username': { 'param': { 'type': 'string' } }, + }, } } }, - 'authorids': { + 'abstract': { 'order': 3, + 'description': 'Abstract of paper.', 'value': { 'param': { - 'type': 'string[]', - 'optional': True - } - } - }, - 'abstract': { - 'order': 4, - 'description': 'Abstract of paper.', - 'value': { - 'param': { 'type': 'string', 'markdown': True, 'input': 'textarea', @@ -1183,7 +1199,7 @@ def set_arxiv_invitations(self): } }, 'subject_areas': { - 'order': 5, + 'order': 4, 'description': 'Subject areas of paper.', 'value': { 'param': { @@ -1195,7 +1211,7 @@ def set_arxiv_invitations(self): } }, 'pdf': { - 'order': 6, + 'order': 5, 'description': 'Link to the PDF paper.', 'value': { 'param': { @@ -1204,9 +1220,9 @@ def set_arxiv_invitations(self): 'optional': True } } - }, + }, 'venue': { - 'order': 7, + 'order': 6, 'description': 'Enter the venue where the paper was published.', 'value': { 'param': { @@ -1217,7 +1233,7 @@ def set_arxiv_invitations(self): } }, 'venueid': { - 'order': 8, + 'order': 7, 'value': { 'param': { 'type': "string", @@ -1333,24 +1349,19 @@ def set_orcid_invitations(self): }, 'authors': { 'order': 2, + 'description': 'Authors of paper.', 'value': { 'param': { - 'type': 'string[]', - 'regex': '[^;,\\n]+(,[^,\\n]+)*' + 'type': 'author{}', + 'properties': { + 'fullname': { 'param': { 'type': 'string' } }, + 'username': { 'param': { 'type': 'string' } }, + }, } } }, - 'authorids': { - 'order': 3, - 'value': { - 'param': { - 'type': 'string[]', - 'optional': True - } - } - }, 'venue': { - 'order': 4, + 'order': 3, 'description': 'Enter the venue where the paper was published.', 'value': { 'param': { @@ -1360,7 +1371,7 @@ def set_orcid_invitations(self): } }, 'venueid': { - 'order': 5, + 'order': 4, 'value': { 'param': { 'type': "string", diff --git a/openreview/profile/process/arxiv_record_process.js b/openreview/profile/process/arxiv_record_process.js index 791f1cba74..42f1da9e31 100644 --- a/openreview/profile/process/arxiv_record_process.js +++ b/openreview/profile/process/arxiv_record_process.js @@ -4,9 +4,26 @@ async function process(client, edit, invitation) { const note = Tools.convertArxivXmlToNote(edit.content?.xml?.value); note.id = edit.note.id; - const authorids = edit.note.content.authorids?.value; - if (authorids) { - note.content.authorids.value = note.content.authorids.value.map((authorid, index) => authorids[index] || authorid); + + // Handle legacy convert format (string arrays) by converting to author{} objects + const authors = note.content.authors?.value || []; + if (authors.length > 0 && typeof authors[0] === 'string') { + const authorids = note.content.authorids?.value || []; + note.content.authors = { + value: authors.map((name, i) => ({ fullname: name, username: authorids[i] || '' })) + }; + delete note.content.authorids; + } + + const existingAuthors = edit.note.content.authors?.value; + if (existingAuthors) { + note.content.authors.value = note.content.authors.value.map((author, index) => { + const existing = existingAuthors[index]; + if (existing?.username) { + return { ...author, username: existing.username }; + } + return author; + }); } note.content.venueid = { diff --git a/openreview/profile/process/author_coreference_pre_process.js b/openreview/profile/process/author_coreference_pre_process.js index 9b4d1ecc75..f93870f8b5 100644 --- a/openreview/profile/process/author_coreference_pre_process.js +++ b/openreview/profile/process/author_coreference_pre_process.js @@ -3,7 +3,12 @@ async function process(client, edit, invitation) { const authorIndex = edit.content.author_index.value; const authorId = edit.content.author_id.value; + const authorName = edit.content.author_name.value; + if (Tools.prettyId(authorId) !== authorName) { + return Promise.reject(new OpenReviewError({ name: 'Error', message: `The author id ${authorId} doesn't match with the author name ${authorName}` })); + } + const { notes } = await client.getNotes({ id: edit.note.id }); const publication = notes[0]; @@ -17,27 +22,19 @@ async function process(client, edit, invitation) { const { profiles } = await client.getProfiles({ id: edit.signatures[0] }); const userProfile = profiles[0]; - - const usernames = userProfile.content.names.map(name => name.username); - const names = userProfile.content.names.map(name => name.fullname); - if (authorId === '') { - const authorName = publication.content.authorids?.value[authorIndex]; - if (!usernames.some(username => username === authorName)) { - return Promise.reject(new OpenReviewError({ name: 'Error', message: `The author name ${authorName} from index ${authorIndex} doesn't match with the names listed in your profile` })); - } - return; - } + const usernames = userProfile.content.names.map(name => name.username).filter(username => !!username); + const names = userProfile.content.names.map(name => name.fullname); const usernameIndex = usernames.indexOf(authorId); if (usernameIndex === -1) { return Promise.reject(new OpenReviewError({ name: 'Error', message: `The author id ${authorId} doesn't match with the names listed in your profile` })); } - - const authorName = publication.content.authors.value[authorIndex]; - const nameIndex = names.indexOf(authorName); + + const publicationAuthorName = publication.content.authors.value[authorIndex]?.fullname; + const nameIndex = names.indexOf(publicationAuthorName); if (nameIndex === -1) { - return Promise.reject(new OpenReviewError({ name: 'Error', message: `The author name ${authorName} from index ${authorIndex} doesn't match with the names listed in your profile` })); + return Promise.reject(new OpenReviewError({ name: 'Error', message: `The author name ${publicationAuthorName} from index ${authorIndex} doesn't match with the names listed in your profile` })); } } \ No newline at end of file diff --git a/openreview/profile/process/author_removal_pre_process.js b/openreview/profile/process/author_removal_pre_process.js index 33bd2bfc3e..3502d3a8aa 100644 --- a/openreview/profile/process/author_removal_pre_process.js +++ b/openreview/profile/process/author_removal_pre_process.js @@ -2,6 +2,7 @@ async function process(client, edit, invitation) { client.throwErrors = true; const authorIndex = edit.content.author_index.value; + const authorName = edit.content.author_name.value; const { notes } = await client.getNotes({ id: edit.note.id }); const publication = notes[0]; @@ -16,12 +17,17 @@ async function process(client, edit, invitation) { const { profiles } = await client.getProfiles({ id: edit.signatures[0] }); const userProfile = profiles[0]; - - const usernames = userProfile.content.names.map(name => name.username); - const authorName = publication.content.authorids?.value[authorIndex]; - if (!usernames.some(username => username === authorName)) { - return Promise.reject(new OpenReviewError({ name: 'Error', message: `The author name ${authorName} from index ${authorIndex} doesn't match with the names listed in your profile` })); + const usernames = userProfile.content.names.map(name => name.username).filter(username => !!username); + + const authorUsername = publication.content.authors.value[authorIndex]?.username; + + if (Tools.prettyId(authorUsername) !== authorName) { + return Promise.reject(new OpenReviewError({ name: 'Error', message: `The author username ${authorUsername} from index ${authorIndex} doesn't match with the author name ${authorName}` })); + } + + if (!usernames.some(username => username === authorUsername)) { + return Promise.reject(new OpenReviewError({ name: 'Error', message: `The author name ${authorUsername} from index ${authorIndex} doesn't match with the names listed in your profile` })); } } \ No newline at end of file diff --git a/openreview/profile/process/dblp_record_post_process.js b/openreview/profile/process/dblp_record_post_process.js index 3e4d59f7e9..3bedfeb2d8 100644 --- a/openreview/profile/process/dblp_record_post_process.js +++ b/openreview/profile/process/dblp_record_post_process.js @@ -14,25 +14,28 @@ async function process(client, edit, invitation) { let updatedFields = {}; - const { abstract, pdf } = await Tools.extractAbstract(html); - console.log('abstract: ' + abstract); - console.log('pdf: ' + pdf); - if (abstract) { - updatedFields.abstract = { value: abstract }; - } - if (pdf) { - updatedFields.pdf = { value: pdf }; - } - - await client.postNoteEdit({ - invitation: `${edit.domain}/-/Edit`, - signatures: [`${edit.domain}/DBLP.org/Uploader`], - readers: ['everyone'], - writers: [`${edit.domain}/DBLP.org`], - note: { - id: noteId, - content: updatedFields + try { + const { abstract, pdf } = await Tools.extractAbstract(html); + console.log('abstract: ' + abstract); + console.log('pdf: ' + pdf); + if (abstract) { + updatedFields.abstract = { value: abstract }; + } + if (pdf) { + updatedFields.pdf = { value: pdf }; } - }); + await client.postNoteEdit({ + invitation: `${edit.domain}/-/Edit`, + signatures: [`${edit.domain}/DBLP.org/Uploader`], + readers: ['everyone'], + writers: [`${edit.domain}/DBLP.org`], + note: { + id: noteId, + content: updatedFields + } + }); + } catch (error) { + console.log(`Error during data extraction or posting note edit: ${error}`); + } } \ No newline at end of file diff --git a/openreview/profile/process/dblp_record_process.js b/openreview/profile/process/dblp_record_process.js index 6d1ecc74b8..c642bc9806 100644 --- a/openreview/profile/process/dblp_record_process.js +++ b/openreview/profile/process/dblp_record_process.js @@ -4,9 +4,26 @@ async function process(client, edit, invitation) { const note = Tools.convertDblpXmlToNote(edit.content?.xml?.value); note.id = edit.note.id; - const authorids = edit.note.content.authorids?.value; - if (authorids) { - note.content.authorids.value = note.content.authorids.value.map((authorid, index) => authorids[index] || authorid); + + // Handle legacy convert format (string arrays) by converting to author{} objects + const authors = note.content.authors?.value || []; + if (authors.length > 0 && typeof authors[0] === 'string') { + const authorids = note.content.authorids?.value || []; + note.content.authors = { + value: authors.map((name, i) => ({ fullname: name, username: authorids[i] || '' })) + }; + delete note.content.authorids; + } + + const existingAuthors = edit.note.content.authors?.value; + if (existingAuthors) { + note.content.authors.value = note.content.authors.value.map((author, index) => { + const existing = existingAuthors[index]; + if (existing?.username) { + return { ...author, username: existing.username }; + } + return author; + }); } note.content.venueid = { diff --git a/openreview/profile/process/deprecated_dblp_author_coreference_pre_process.js b/openreview/profile/process/deprecated_dblp_author_coreference_pre_process.js index 377f61850d..9d80b67ded 100644 --- a/openreview/profile/process/deprecated_dblp_author_coreference_pre_process.js +++ b/openreview/profile/process/deprecated_dblp_author_coreference_pre_process.js @@ -18,7 +18,7 @@ async function process(client, edit, invitation) { const { profiles } = await client.getProfiles({ id: edit.signatures[0] }); const userProfile = profiles[0]; - const usernames = userProfile.content.names.map(name => name.username); + const usernames = userProfile.content.names.map(name => name.username).filter(username => !!username); const names = userProfile.content.names.map(name => name.fullname); if (authorId === '') { diff --git a/openreview/profile/process/deprecated_dblp_record_process.js b/openreview/profile/process/deprecated_dblp_record_process.js index 5b13b700a1..f7969bac3e 100644 --- a/openreview/profile/process/deprecated_dblp_record_process.js +++ b/openreview/profile/process/deprecated_dblp_record_process.js @@ -4,10 +4,21 @@ async function process(client, edit, invitation) { const note = Tools.convertDblpXmlToNote(edit.content?.xml?.value); note.id = edit.note.id; + + const convertedAuthors = note.content.authors?.value || []; + const isObjectFormat = convertedAuthors.length > 0 && typeof convertedAuthors[0] === 'object'; + + if (isObjectFormat) { + note.content.authors = { value: convertedAuthors.map(a => a.fullname) }; + note.content.authorids = { value: convertedAuthors.map(a => a.username || '') }; + } + const authorids = edit.note.content.authorids?.value; if (authorids) { note.content.authorids.value = note.content.authorids.value.map((authorid, index) => authorids[index] || authorid); } + // Remove externalIds to avoid duplicate key errors + delete note.externalId; const html = note.content.html?.value; let abstractError = false; @@ -40,7 +51,4 @@ async function process(client, edit, invitation) { note: note }); - if (abstractError) { - throw abstractError; - } } \ No newline at end of file diff --git a/openreview/profile/process/orcid_record_post_process.js b/openreview/profile/process/orcid_record_post_process.js index 6df6f0dc57..572c0f6ec7 100644 --- a/openreview/profile/process/orcid_record_post_process.js +++ b/openreview/profile/process/orcid_record_post_process.js @@ -41,7 +41,7 @@ async function process(client, edit, invitation) { } }); } catch (error) { - console.log('Error during data extraction or posting note edit: ', error); + console.log(`Error during data extraction or posting note edit: ${error}`); } } \ No newline at end of file diff --git a/openreview/profile/process/orcid_record_process.js b/openreview/profile/process/orcid_record_process.js index c4e28524a9..4273f11492 100644 --- a/openreview/profile/process/orcid_record_process.js +++ b/openreview/profile/process/orcid_record_process.js @@ -1,14 +1,65 @@ async function process(client, edit, invitation) { client.throwErrors = true; - const note = Tools.convertORCIDJsonToNote(edit.content?.json?.value); + const json = edit.content?.json?.value; + const note = Tools.convertORCIDJsonToNote(json); note.id = edit.note.id; + + // TODO: Remove this block once @openreview/client is published with the + // formatCreditName fix (converts "Last, First" to "First Last") and replace + // with the commented-out code below. + // + // --- Start: replace with this once @openreview/client is updated --- + // const convertedAuthors = note.content.authors?.value || []; + // if (convertedAuthors.length > 0 && typeof convertedAuthors[0] === 'string') { + // const authorids = note.content.authorids?.value || []; + // note.content.authors = { + // value: convertedAuthors.map((name, i) => ({ fullname: name, username: authorids[i] || '' })) + // }; + // delete note.content.authorids; + // } + // --- End --- + + // --- Start: remove once @openreview/client is updated --- + // Build authors directly from the ORCID JSON to ensure correct "First Last" + // name ordering regardless of what the convert function returns. + const formatCreditName = (name) => { + if (!name) return ''; + const commaIndex = name.indexOf(','); + if (commaIndex === -1) return name.trim(); + const last = name.substring(0, commaIndex).trim(); + const first = name.substring(commaIndex + 1).trim(); + return first ? `${first} ${last}` : last; + }; + + const contributors = json?.contributors?.contributor || []; + note.content.authors = { + value: contributors.map((p) => { + const fullname = formatCreditName(p['credit-name']?.value); + let username = ''; + if (p['contributor-orcid']?.uri) { + username = p['contributor-orcid'].uri; + } else if (fullname) { + username = `https://orcid.org/orcid-search/search?searchQuery=${encodeURIComponent(fullname)}`; + } + return { fullname, username }; + }) + }; + delete note.content.authorids; + // --- End: remove once @openreview/client is updated --- + const { notes } = await client.getNotes({ id: edit.note.id }); - const authorids = notes[0].content.authorids?.value; + const existingAuthors = notes[0].content.authors?.value; - if (authorids) { - note.content.authorids.value = note.content.authorids.value.map((authorid, index) => authorids[index] || authorid); + if (existingAuthors) { + note.content.authors.value = note.content.authors.value.map((author, index) => { + const existing = existingAuthors[index]; + if (existing?.username) { + return { ...author, username: existing.username }; + } + return author; + }); } note.content.venueid = { diff --git a/openreview/profile/process/request_remove_name_decision_process.py b/openreview/profile/process/request_remove_name_decision_process.py index 54b1522c78..24a0ccfcb8 100644 --- a/openreview/profile/process/request_remove_name_decision_process.py +++ b/openreview/profile/process/request_remove_name_decision_process.py @@ -78,21 +78,34 @@ def replace_group_members(group, current_member, new_member): publications = client.get_all_notes(content={ 'authorids': username}) for publication in publications: - authors = [] - authorids = [] + authors_value = publication.content.get('authors', {}).get('value', []) + authorids_value = publication.content.get('authorids', {}).get('value', []) + is_author_object_format = bool(authors_value) and isinstance(authors_value[0], dict) + + new_authors = [] + new_authorids = [] signatures = None readers = None writers = None needs_change = False - for index, author in enumerate(publication.content.get('authorids', {}).get('value')): - if username == author: - authors.append(preferred_name) - authorids.append(preferred_id) - needs_change = True - else: - if publication.content.get('authors') and len(publication.content['authors']['value']) > index: - authors.append(publication.content['authors']['value'][index]) - authorids.append(publication.content['authorids']['value'][index]) + + if is_author_object_format: + for author in authors_value: + if isinstance(author, dict) and author.get('username') == username: + new_authors.append({ **author, 'fullname': preferred_name, 'username': preferred_id }) + needs_change = True + else: + new_authors.append(author) + else: + for index, author in enumerate(authorids_value): + if username == author: + new_authors.append(preferred_name) + new_authorids.append(preferred_id) + needs_change = True + else: + if authors_value and len(authors_value) > index: + new_authors.append(authors_value[index]) + new_authorids.append(authorids_value[index]) if username in publication.signatures: signatures = [profile.id if g == username else g for g in publication.signatures] @@ -103,9 +116,10 @@ def replace_group_members(group, current_member, new_member): if needs_change: content = { - 'authors': { 'value': authors }, - 'authorids': { 'value': authorids } + 'authors': { 'value': new_authors } } + if not is_author_object_format: + content['authorids'] = { 'value': new_authorids } if '_bibtex' in publication.content: content['_bibtex'] = { 'value': publication.content['_bibtex']['value'].replace(openreview.tools.pretty_id(username), preferred_name) } client.post_note_edit( @@ -117,9 +131,9 @@ def replace_group_members(group, current_member, new_member): "value": "remove name process function", "readers": [SUPPORT_USER_ID] }, - }, + }, note = openreview.api.Note( - id=publication.id, + id=publication.id, content=content, readers=readers, writers=writers, @@ -131,7 +145,7 @@ def replace_group_members(group, current_member, new_member): print('Updating invitation', invitation.id) invitation_content = invitation.edit['note'].get('content', {}) if invitation.edit['note'].get('id') == publication.id and 'authorids' in invitation_content and username in invitation_content['authorids'].get('value', []): - + authors = [] authorids = [] needs_change = False @@ -143,8 +157,8 @@ def replace_group_members(group, current_member, new_member): else: if invitation_content['authors'].get('value') and len(invitation_content['authors']['value']) > index: authors.append(invitation_content['authors']['value'][index]) - authorids.append(invitation_content['authorids']['value'][index]) - + authorids.append(invitation_content['authorids']['value'][index]) + if needs_change: print('Updating invitation', invitation.id) client.post_invitation_edit( @@ -156,7 +170,7 @@ def replace_group_members(group, current_member, new_member): "value": "remove name process function", "readers": [SUPPORT_USER_ID] }, - }, + }, invitation = openreview.api.Invitation( id=invitation.id, edit={ @@ -169,7 +183,7 @@ def replace_group_members(group, current_member, new_member): } ) ) - + print('Change all the notes that contain the name to remove as signatures') signed_notes = client_v1.get_all_notes(signature=username) for note in signed_notes: diff --git a/openreview/profile/process/request_remove_name_process.py b/openreview/profile/process/request_remove_name_process.py index 53b197497d..f1c29894b7 100644 --- a/openreview/profile/process/request_remove_name_process.py +++ b/openreview/profile/process/request_remove_name_process.py @@ -33,7 +33,7 @@ def process(client, edit, invitation): print("Check if the username appears in any publications") for username in usernames: api1_publications = [p for p in client_v1.get_all_notes(content={ 'authorids': username}) if username in p.content['authorids']] - api2_publications = [p for p in client.get_all_notes(content={ 'authorids': username}) if username in p.content.get('authorids', {}).get('value', [])] + api2_publications = [p for p in client.get_all_notes(content={ 'authorids': username}) if any(a.get('username') == username for a in p.authors)] print(f'Publications for {username}: {len(api1_publications) + len(api2_publications)}') if api1_publications or api2_publications: diff --git a/tests/test_abstract_deadline.py b/tests/test_abstract_deadline.py index accf3521ad..4c9808e66b 100644 --- a/tests/test_abstract_deadline.py +++ b/tests/test_abstract_deadline.py @@ -276,6 +276,7 @@ def test_post_submissions(self, openreview_client, test_client, helpers): helpers.await_queue_edit(openreview_client, edit_id=edit['id']) helpers.await_queue_edit(openreview_client, 'ifaamas.org/AAMAS/2026/Workshop/EMAS/-/Full_Submission-0-1', count=4) + helpers.await_queue_edit(openreview_client, 'ifaamas.org/AAMAS/2026/Workshop/EMAS/-/Deletion-0-1', count=4) helpers.await_queue_edit(openreview_client, 'ifaamas.org/AAMAS/2026/Workshop/EMAS/Reviewers/-/Submission_Message-0-1', count=3) full_submission_invitations = pc_client.get_invitations(invitation='ifaamas.org/AAMAS/2026/Workshop/EMAS/-/Full_Submission') diff --git a/tests/test_iclr_conference_v2.py b/tests/test_iclr_conference_v2.py index 034330d17b..67fa280a8f 100644 --- a/tests/test_iclr_conference_v2.py +++ b/tests/test_iclr_conference_v2.py @@ -331,6 +331,17 @@ def test_post_submission(self, client, openreview_client, helpers, test_client): }''' assert submission.content['_bibtex']['value'] == valid_bibtex + guest_client = openreview.api.OpenReviewClient() + search_notes = guest_client.search_notes('Paper title 1 license revision') + assert search_notes + assert 'authors' not in search_notes[0].content + assert 'authorids' not in search_notes[0].content + + search_notes = pc_client_v2.search_notes('Paper title 1 license revision') + assert search_notes + assert 'authors' in search_notes[0].content + assert 'authorids' in search_notes[0].content + assert search_notes[0].content['authors']['value'] == ['SomeFirstName User', 'Peter SomeLastName', 'Andrew Mc', 'SAC ICLROne'] # Assert that activation date of matching invitation == abstract deadline matching_invitation = client.get_invitation(f'openreview.net/Support/-/Request{request_form.number}/Paper_Matching_Setup') diff --git a/tests/test_profile_management.py b/tests/test_profile_management.py index a6278dec5e..079a725f94 100644 --- a/tests/test_profile_management.py +++ b/tests/test_profile_management.py @@ -81,7 +81,7 @@ def test_import_deprecated_dblp_notes(self, client, openreview_client, test_clie ) ) - helpers.await_queue_edit(openreview_client, edit_id=edit['id'], error=True) + helpers.await_queue_edit(openreview_client, edit_id=edit['id']) note = test_client_v2.get_note(edit['note']['id']) assert note.invitations == ['DBLP.org/-/Record', 'DBLP.org/-/Edit'] @@ -92,6 +92,7 @@ def test_import_deprecated_dblp_notes(self, client, openreview_client, test_clie assert 'venue' in note.content assert 'venueid' in note.content assert 'html' in note.content + assert note.external_ids is None andrew_client = helpers.create_user('mccallum@profile.org', 'Andrew', 'McCallum', alternates=[], institution='google.com') @@ -137,7 +138,7 @@ def test_import_deprecated_dblp_notes(self, client, openreview_client, test_clie ) ) - helpers.await_queue_edit(openreview_client, edit_id=edit['id'], error=True) + helpers.await_queue_edit(openreview_client, edit_id=edit['id']) note = andrew_client.get_note(edit['note']['id']) assert note.invitations == ['DBLP.org/-/Record', 'DBLP.org/-/Edit'] @@ -161,6 +162,7 @@ def test_import_deprecated_dblp_notes(self, client, openreview_client, test_clie "", "~Andrew_McCallum1" ] + assert note.external_ids is None haw_shiuan_client = helpers.create_user('haw@profile.org', 'Haw-Shiuan', 'Chang', alternates=[], institution='umass.edu') @@ -434,7 +436,15 @@ def test_import_dblp_notes(self, client, openreview_client, test_client, helpers 'value': 'Bi-level Contrastive Learning for Knowledge-Enhanced Molecule Representations', }, 'authors': { - 'value': ['Pengcheng Jiang', 'Cao Xiao', 'Tianfan Fu', 'Parminder Bhatia', 'Taha A. Kass-Hout', 'Jimeng Sun 0001', 'Jiawei Han 0001'], + 'value': [ + {'fullname': 'Pengcheng Jiang', 'username': ''}, + {'fullname': 'Cao Xiao', 'username': ''}, + {'fullname': 'Tianfan Fu', 'username': ''}, + {'fullname': 'Parminder Bhatia', 'username': ''}, + {'fullname': 'Taha A. Kass-Hout', 'username': ''}, + {'fullname': 'Jimeng Sun 0001', 'username': ''}, + {'fullname': 'Jiawei Han 0001', 'username': ''}, + ], }, 'venue': { 'value': 'JiangXFBK0025', @@ -451,13 +461,13 @@ def test_import_dblp_notes(self, client, openreview_client, test_client, helpers assert note.pdate assert note.external_ids == ['dblp:conf/aaai/JiangXFBK0025'] assert '_bibtex' in note.content - assert 'authorids' in note.content + assert 'authors' in note.content assert 'venue' in note.content assert 'venueid' in note.content assert 'html' in note.content assert 'abstract' not in note.content - helpers.await_queue_edit(openreview_client, edit_id=edit['id'], process_index=1, error=True) + helpers.await_queue_edit(openreview_client, edit_id=edit['id'], process_index=1) andrew_client = helpers.create_user('mccallum@profile.org', 'Andrew', 'McCallum', alternates=[], institution='google.com', dblp_url='https://dblp.org/pid/m/AndrewMcCallum') @@ -492,7 +502,12 @@ def test_import_dblp_notes(self, client, openreview_client, test_client, helpers 'value': 'Identity Theft in AI Conference Peer Review', }, 'authors': { - 'value': ['Nihar B. Shah', 'Melisa Bok', 'Xukun Liu', 'Andrew McCallum'], + 'value': [ + {'fullname': 'Nihar B. Shah', 'username': ''}, + {'fullname': 'Melisa Bok', 'username': ''}, + {'fullname': 'Xukun Liu', 'username': ''}, + {'fullname': 'Andrew McCallum', 'username': ''}, + ], }, 'venue': { 'value': 'CoRR', @@ -502,7 +517,7 @@ def test_import_dblp_notes(self, client, openreview_client, test_client, helpers ) helpers.await_queue_edit(openreview_client, edit_id=edit['id'], process_index=0) - helpers.await_queue_edit(openreview_client, edit_id=edit['id'], process_index=1, error=True) + helpers.await_queue_edit(openreview_client, edit_id=edit['id'], process_index=1) note = andrew_client.get_note(edit['note']['id']) @@ -512,6 +527,7 @@ def test_import_dblp_notes(self, client, openreview_client, test_client, helpers content = { 'author_index': { 'value': 3 }, 'author_id': { 'value': '~Andrew_McCallum1' }, + 'author_name': { 'value': 'Andrew McCallum' }, }, note = openreview.api.Note( id = note.id @@ -519,30 +535,24 @@ def test_import_dblp_notes(self, client, openreview_client, test_client, helpers ) note = andrew_client.get_note(edit['note']['id']) - assert note.invitations == ['openreview.net/Public_Article/DBLP.org/-/Record', - 'openreview.net/Public_Article/-/Edit', + assert note.invitations == ['openreview.net/Public_Article/DBLP.org/-/Record', + 'openreview.net/Public_Article/-/Edit', 'openreview.net/Public_Article/-/Authorship_Claim'] assert note.cdate assert note.pdate assert note.external_ids == ['dblp:journals/corr/abs-2508-04024'] assert '_bibtex' in note.content - assert 'authorids' in note.content + assert 'authors' in note.content assert 'venue' in note.content assert 'venueid' in note.content assert 'html' in note.content assert 'abstract' not in note.content assert note.content['title']['value'] == 'Identity Theft in AI Conference Peer Review' assert note.content['authors']['value'] == [ - "Nihar B. Shah", - "Melisa Bok", - "Xukun Liu", - "Andrew McCallum" - ] - assert note.content['authorids']['value'] == [ - "", - "", - "", - "~Andrew_McCallum1" + {"fullname": "Nihar B. Shah", "username": ""}, + {"fullname": "Melisa Bok", "username": ""}, + {"fullname": "Xukun Liu", "username": ""}, + {"fullname": "Andrew McCallum", "username": "~Andrew_McCallum1"} ] nihar_client = helpers.create_user('nihar@profile.org', 'Nihar B.', 'Shah', alternates=[], institution='google.com') @@ -553,27 +563,43 @@ def test_import_dblp_notes(self, client, openreview_client, test_client, helpers content = { 'author_index': { 'value': 0 }, 'author_id': { 'value': '~Nihar_B._Shah1' }, + 'author_name': { 'value': 'Nihar B. Shah' }, }, note = openreview.api.Note( id = note.id ) - ) + ) note = andrew_client.get_note(edit['note']['id']) - assert note.content['authorids']['value'] == [ - "~Nihar_B._Shah1", - "", - "", - "~Andrew_McCallum1" + assert note.content['authors']['value'] == [ + {"fullname": "Nihar B. Shah", "username": "~Nihar_B._Shah1"}, + {"fullname": "Melisa Bok", "username": ""}, + {"fullname": "Xukun Liu", "username": ""}, + {"fullname": "Andrew McCallum", "username": "~Andrew_McCallum1"} ] + with pytest.raises(openreview.OpenReviewException, match=r'The author username ~Nihar_B._Shah1 from index 0 doesn\'t match with the author name Nihar Shah'): + edit = nihar_client.post_note_edit( + invitation = 'openreview.net/Public_Article/-/Author_Removal', + signatures = ['~Nihar_B._Shah1'], + content = { + 'author_index': { 'value': 0 }, + 'author_id': { 'value': '' }, + 'author_name': { 'value': 'Nihar Shah' }, + }, + note = openreview.api.Note( + id = note.id + ) + ) + edit = nihar_client.post_note_edit( invitation = 'openreview.net/Public_Article/-/Author_Removal', signatures = ['~Nihar_B._Shah1'], content = { 'author_index': { 'value': 0 }, 'author_id': { 'value': '' }, + 'author_name': { 'value': 'Nihar B. Shah' }, }, note = openreview.api.Note( id = note.id @@ -582,12 +608,24 @@ def test_import_dblp_notes(self, client, openreview_client, test_client, helpers note = andrew_client.get_note(edit['note']['id']) - assert note.content['authorids']['value'] == [ - "", - "", - "", - "~Andrew_McCallum1" - ] + assert note.content['authors']['value'] == [ + {"fullname": "Nihar B. Shah", "username": ""}, + {"fullname": "Melisa Bok", "username": ""}, + {"fullname": "Xukun Liu", "username": ""}, + {"fullname": "Andrew McCallum", "username": "~Andrew_McCallum1"} + ] + + notes = andrew_client.search_notes('Identity Theft') + assert len(notes) >= 1 + assert notes[0].id == note.id + assert notes[0].content['authors']['value'] == [ + {"fullname": "Nihar B. Shah", "username": ""}, + {"fullname": "Melisa Bok", "username": ""}, + {"fullname": "Xukun Liu", "username": ""}, + {"fullname": "Andrew McCallum", "username": "~Andrew_McCallum1"} + ] + assert 'authorids' not in notes[0].content + @pytest.mark.skip(reason="Skipping this test until we decide to enable comments") def test_dblp_enable_comments(self, client, openreview_client, test_client, helpers): @@ -840,7 +878,11 @@ def test_import_arxiv_notes(self, client, openreview_client, test_client, helper 'value': 'Personalized item recommendation typically suffers from data sparsity, which is most often addressed by learning vector representations of users and items via low-rank matrix factorization. While this effectively densifies the matrix by assuming users and movies can be represented by linearly dependent latent features, it does not capture more complicated interactions. For example, vector representations struggle with set-theoretic relationships, such as negation and intersection, e.g. recommending a movie that is "comedy and action, but not romance". In this work, we formulate the problem of personalized item recommendation as matrix completion where rows are set-theoretically dependent. To capture this set-theoretic dependence we represent each user and attribute by a hyper-rectangle or box (i.e. a Cartesian product of intervals). Box embeddings can intuitively be understood as trainable Venn diagrams, and thus not only inherently represent similarity (via the Jaccard index), but also naturally and faithfully support arbitrary set-theoretic relationships. Queries involving set-theoretic constraints can be efficiently computed directly on the embedding space by performing geometric operations on the representations. We empirically demonstrate the superiority of box embeddings over vector-based neural methods on both simple and complex item recommendation queries by up to 30 \% overall.' }, 'authors': { - 'value': ['Shib Dasgupta', 'Michael Boratko', 'Andrew McCallum'] + 'value': [ + {'fullname': 'Shib Dasgupta', 'username': ''}, + {'fullname': 'Michael Boratko', 'username': ''}, + {'fullname': 'Andrew McCallum', 'username': ''}, + ] }, 'subject_areas': { 'value': ['cs.IR', 'cs.AI', 'cs.LG'] @@ -855,10 +897,10 @@ def test_import_arxiv_notes(self, client, openreview_client, test_client, helper helpers.await_queue_edit(openreview_client, edit_id=edit['id']) geometric_note = andrew_client.get_note(edit['note']['id']) - assert geometric_note.content['authorids']['value'] == [ - "https://arxiv.org/search/?query=Shib%20Dasgupta&searchtype=all", - "https://arxiv.org/search/?query=Michael%20Boratko&searchtype=all", - "https://arxiv.org/search/?query=Andrew%20McCallum&searchtype=all" + assert geometric_note.content['authors']['value'] == [ + {"fullname": "Shib Dasgupta", "username": "https://arxiv.org/search/?query=Shib%20Dasgupta&searchtype=all"}, + {"fullname": "Michael Boratko", "username": "https://arxiv.org/search/?query=Michael%20Boratko&searchtype=all"}, + {"fullname": "Andrew McCallum", "username": "https://arxiv.org/search/?query=Andrew%20McCallum&searchtype=all"} ] edit = andrew_client.post_note_edit( @@ -867,17 +909,18 @@ def test_import_arxiv_notes(self, client, openreview_client, test_client, helper content = { 'author_index': { 'value': 2 }, 'author_id': { 'value': '~Andrew_McCallum1' }, - }, + 'author_name': { 'value': 'Andrew McCallum' }, + }, note = openreview.api.Note( id = geometric_note.id ) - ) + ) geometric_note = andrew_client.get_note(edit['note']['id']) - assert geometric_note.content['authorids']['value'] == [ - "https://arxiv.org/search/?query=Shib%20Dasgupta&searchtype=all", - "https://arxiv.org/search/?query=Michael%20Boratko&searchtype=all", - "~Andrew_McCallum1" + assert geometric_note.content['authors']['value'] == [ + {"fullname": "Shib Dasgupta", "username": "https://arxiv.org/search/?query=Shib%20Dasgupta&searchtype=all"}, + {"fullname": "Michael Boratko", "username": "https://arxiv.org/search/?query=Michael%20Boratko&searchtype=all"}, + {"fullname": "Andrew McCallum", "username": "~Andrew_McCallum1"} ] # Do not merge publications for now @@ -946,7 +989,12 @@ def test_import_arxiv_notes(self, client, openreview_client, test_client, helper 'value': 'Ensembling BERT models often significantly improves accuracy, but at the cost of significantly more computation and memory footprint. In this work, we propose Multi-CLS BERT, a novel ensembling method for CLS-based prediction tasks that is almost as efficient as a single BERT model. Multi-CLS BERT uses multiple CLS tokens with a parameterization and objective that encourages their diversity. Thus instead of fine-tuning each BERT model in an ensemble (and running them all at test time), we need only fine-tune our single Multi-CLS BERT model (and run the one model at test time, ensembling just the multiple final CLS embeddings). To test its effectiveness, we build Multi-CLS BERT on top of a state-of-the-art pretraining method for BERT (Aroca-Ouellette and Rudzicz, 2020). In experiments on GLUE and SuperGLUE we show that our Multi-CLS BERT reliably improves both overall accuracy and confidence estimation. When only 100 training samples are available in GLUE, the Multi-CLS BERT_Base model can even outperform the corresponding BERT_Large model. We analyze the behavior of our Multi-CLS BERT, showing that it has many of the same characteristics and behavior as a typical BERT 5-way ensemble, but with nearly 4-times less computation and memory.' }, 'authors': { - 'value': ['Haw-Shiuan Chang', 'Ruei-Yao Sun', 'Kathryn Ricci', 'Andrew McCallum'] + 'value': [ + {'fullname': 'Haw-Shiuan Chang', 'username': ''}, + {'fullname': 'Ruei-Yao Sun', 'username': ''}, + {'fullname': 'Kathryn Ricci', 'username': ''}, + {'fullname': 'Andrew McCallum', 'username': ''}, + ] }, 'subject_areas': { 'value': ['cs.CL', 'cs.LG'] @@ -962,10 +1010,12 @@ def test_import_arxiv_notes(self, client, openreview_client, test_client, helper updated_note = andrew_client.get_note(edit['note']['id']) assert updated_note.external_ids == ['arxiv:2210.05043v2'] - assert 'https://arxiv.org/search/?query=Andrew%20McCallum&searchtype=all' in updated_note.content['authorids']['value'] - assert 'https://arxiv.org/search/?query=Haw-Shiuan%20Chang&searchtype=all' in updated_note.content['authorids']['value'] - assert 'https://arxiv.org/search/?query=Ruei-Yao%20Sun&searchtype=all' in updated_note.content['authorids']['value'] - assert 'https://arxiv.org/search/?query=Kathryn%20Ricci&searchtype=all' in updated_note.content['authorids']['value'] + assert updated_note.content['authors']['value'] == [ + {"fullname": "Haw-Shiuan Chang", "username": "https://arxiv.org/search/?query=Haw-Shiuan%20Chang&searchtype=all"}, + {"fullname": "Ruei-Yao Sun", "username": "https://arxiv.org/search/?query=Ruei-Yao%20Sun&searchtype=all"}, + {"fullname": "Kathryn Ricci", "username": "https://arxiv.org/search/?query=Kathryn%20Ricci&searchtype=all"}, + {"fullname": "Andrew McCallum", "username": "https://arxiv.org/search/?query=Andrew%20McCallum&searchtype=all"} + ] edit = andrew_client.post_note_edit( invitation = 'openreview.net/Public_Article/-/Authorship_Claim', @@ -973,14 +1023,20 @@ def test_import_arxiv_notes(self, client, openreview_client, test_client, helper content = { 'author_index': { 'value': 3 }, 'author_id': { 'value': '~Andrew_McCallum1' }, - }, + 'author_name': { 'value': 'Andrew McCallum' }, + }, note = openreview.api.Note( id = updated_note.id ) - ) + ) updated_note = andrew_client.get_note(edit['note']['id']) - assert '~Andrew_McCallum1' in updated_note.content['authorids']['value'] + assert updated_note.content['authors']['value'] == [ + {"fullname": "Haw-Shiuan Chang", "username": "https://arxiv.org/search/?query=Haw-Shiuan%20Chang&searchtype=all"}, + {"fullname": "Ruei-Yao Sun", "username": "https://arxiv.org/search/?query=Ruei-Yao%20Sun&searchtype=all"}, + {"fullname": "Kathryn Ricci", "username": "https://arxiv.org/search/?query=Kathryn%20Ricci&searchtype=all"}, + {"fullname": "Andrew McCallum", "username": "~Andrew_McCallum1"} + ] # Update an existing arxiv note @@ -1452,10 +1508,13 @@ def test_import_orcid_notes(self, client, openreview_client, test_client, helper 'value': 'Possibility of entanglement of purification to be less than half of the reflected entropy', }, 'authors': { - 'value': ['Josiah Couch', 'Nguyen, Phuc', 'Racz, Sarah', 'Stratis, Georgios', 'Zhang, Yuxuan'], - }, - 'authorids': { - 'value': ['~Josiah_Couch1', '', '', '', ''], + 'value': [ + {'fullname': 'Josiah Couch', 'username': '~Josiah_Couch1'}, + {'fullname': 'Nguyen, Phuc', 'username': ''}, + {'fullname': 'Racz, Sarah', 'username': ''}, + {'fullname': 'Stratis, Georgios', 'username': ''}, + {'fullname': 'Zhang, Yuxuan', 'username': ''}, + ], }, 'venue': { 'value': 'Phys.Rev.A', @@ -1469,30 +1528,38 @@ def test_import_orcid_notes(self, client, openreview_client, test_client, helper note = josiah_client.get_note(edit['note']['id']) assert note.external_ids == ['doi:10.1103/physreva.109.022426'] - assert '~Josiah_Couch1' == note.content['authorids']['value'][0] + assert note.content['authors']['value'][0] == {'fullname': 'Josiah Couch', 'username': '~Josiah_Couch1'} + assert note.content['authors']['value'][2] == {'fullname': 'Sarah Racz', 'username': 'https://orcid.org/orcid-search/search?searchQuery=Sarah%20Racz'} sarah_client = helpers.create_user('sarah@profile.org', 'Sarah', 'Racz', alternates=[], institution='google.com') - with pytest.raises(openreview.OpenReviewException, match=r'The author name Racz Sarah from index 2 doesn\'t match with the names listed in your profile'): + with pytest.raises(openreview.OpenReviewException, match=r'The author id ~Sarah_Middle_Racz1 doesn\'t match with the names listed in your profile'): edit = sarah_client.post_note_edit( invitation = 'openreview.net/Public_Article/-/Authorship_Claim', signatures = ['~Sarah_Racz1'], content = { 'author_index': { 'value': 2 }, - 'author_id': { 'value': '~Sarah_Racz1' }, - }, + 'author_id': { 'value': '~Sarah_Middle_Racz1' }, + 'author_name': { 'value': 'Sarah Middle Racz' }, + }, note = openreview.api.Note( id = note.id ) ) - profile = sarah_client.get_profile(sarah_client.profile.id) - - profile.content['homepage'] = 'https://sarah.google.com' - profile.content['names'].append({ - 'fullname': 'Racz Sarah', - }) - sarah_client.post_profile(profile) + with pytest.raises(openreview.OpenReviewException, match=r'The author id ~Sarah_Racz1 doesn\'t match with the author name Sarah Middle Racz'): + edit = sarah_client.post_note_edit( + invitation = 'openreview.net/Public_Article/-/Authorship_Claim', + signatures = ['~Sarah_Racz1'], + content = { + 'author_index': { 'value': 2 }, + 'author_id': { 'value': '~Sarah_Racz1' }, + 'author_name': { 'value': 'Sarah Middle Racz' }, + }, + note = openreview.api.Note( + id = note.id + ) + ) edit = sarah_client.post_note_edit( invitation = 'openreview.net/Public_Article/-/Authorship_Claim', @@ -1500,16 +1567,17 @@ def test_import_orcid_notes(self, client, openreview_client, test_client, helper content = { 'author_index': { 'value': 2 }, 'author_id': { 'value': '~Sarah_Racz1' }, - }, + 'author_name': { 'value': 'Sarah Racz' }, + }, note = openreview.api.Note( id = note.id ) - ) + ) note = josiah_client.get_note(edit['note']['id']) assert note.external_ids == ['doi:10.1103/physreva.109.022426'] - assert '~Josiah_Couch1' == note.content['authorids']['value'][0] - assert '~Sarah_Racz1' == note.content['authorids']['value'][2] + assert note.content['authors']['value'][0] == {'fullname': 'Josiah Couch', 'username': '~Josiah_Couch1'} + assert note.content['authors']['value'][2] == {'fullname': 'Sarah Racz', 'username': '~Sarah_Racz1'} def test_remove_alternate_name(self, openreview_client, support_client, helpers): @@ -1895,6 +1963,132 @@ def test_remove_alternate_name(self, openreview_client, support_client, helpers) assert note.content['status']['value'] == 'Accepted' + def test_remove_name_with_dblp_publication(self, openreview_client, support_client, test_client, helpers): + + edith_client = helpers.create_user('edith@profile.org', 'Edith', 'Last', alternates=[], institution='google.com') + + profile = edith_client.get_profile(edith_client.profile.id) + profile.content['names'].append({ + 'first': 'Edith', + 'middle': 'Alternate', + 'last': 'Last' + }) + edith_client.post_profile(profile) + + profile = edith_client.get_profile(email_or_id='~Edith_Last1') + assert len(profile.content['names']) == 2 + assert profile.content['names'][1]['username'] == '~Edith_Alternate_Last1' + + ## Import a DBLP publication where Edith's alternate name appears in the authors array + test_client_v2 = openreview.api.OpenReviewClient(username='test@mail.com', password=helpers.strong_password) + + xml = ''' +Edith Alternate Last +Test Coauthor +A Paper About Removing Names From DBLP. +1-10 +2025 +TestConf +db/conf/test/test2025.html#EdithRemoveName2025 + +''' + + edit = test_client_v2.post_note_edit( + invitation = 'openreview.net/Public_Article/DBLP.org/-/Record', + signatures = ['~SomeFirstName_User1'], + content = { + 'xml': { 'value': xml } + }, + note = openreview.api.Note( + external_id = 'dblp:conf/test/EdithRemoveName2025', + content={ + 'title': { + 'value': 'A Paper About Removing Names From DBLP', + }, + 'authors': { + 'value': [ + {'fullname': 'Edith Alternate Last', 'username': ''}, + {'fullname': 'Test Coauthor', 'username': ''}, + ], + }, + 'venue': { + 'value': 'TestConf', + } + } + ) + ) + + helpers.await_queue_edit(openreview_client, edit_id=edit['id'], process_index=0) + helpers.await_queue_edit(openreview_client, edit_id=edit['id'], process_index=1) + + dblp_note_id = edit['note']['id'] + + ## Edith claims authorship of the DBLP record using her alternate name + edit = edith_client.post_note_edit( + invitation = 'openreview.net/Public_Article/-/Authorship_Claim', + signatures = ['~Edith_Alternate_Last1'], + content = { + 'author_index': { 'value': 0 }, + 'author_id': { 'value': '~Edith_Alternate_Last1' }, + 'author_name': { 'value': 'Edith Alternate Last' }, + }, + note = openreview.api.Note( + id = dblp_note_id + ) + ) + + note = edith_client.get_note(dblp_note_id) + assert note.content['authors']['value'][0] == {'fullname': 'Edith Alternate Last', 'username': '~Edith_Alternate_Last1'} + assert 'authorids' not in note.content + + ## Request to remove the alternate name + request_note = edith_client.post_note_edit( + invitation='openreview.net/Support/-/Profile_Name_Removal', + signatures=['~Edith_Last1'], + note = openreview.api.Note( + content={ + 'name': { 'value': 'Edith Alternate Last' }, + 'usernames': { 'value': ['~Edith_Alternate_Last1'] }, + 'comment': { 'value': 'no longer use this name' } + } + ) + ) + + helpers.await_queue_edit(openreview_client, edit_id=request_note['id']) + + ## Accept the request + decision_note = support_client.post_note_edit( + invitation='openreview.net/Support/-/Profile_Name_Removal_Decision', + signatures=['openreview.net/Support'], + note = openreview.api.Note( + id = request_note['note']['id'], + content={ + 'status': { 'value': 'Accepted' } + } + ) + ) + + helpers.await_queue_edit(openreview_client, edit_id=decision_note['id']) + + edith_client = openreview.api.OpenReviewClient(username='edith@profile.org', password=helpers.strong_password) + note = edith_client.get_note(request_note['note']['id']) + assert note.content['status']['value'] == 'Accepted' + + profile = edith_client.get_profile(email_or_id='~Edith_Last1') + assert len(profile.content['names']) == 1 + assert profile.content['names'][0]['username'] == '~Edith_Last1' + + with pytest.raises(openreview.OpenReviewException, match=r'Group Not Found: ~Edith_Alternate_Last1'): + openreview_client.get_group('~Edith_Alternate_Last1') + + ## The alternate username embedded in the DBLP authors array should be replaced + ## with the preferred name and id of the profile. + note = edith_client.get_note(dblp_note_id) + assert note.content['authors']['value'][0] == {'fullname': 'Edith Last', 'username': '~Edith_Last1'} + assert note.content['authors']['value'][1] == {'fullname': 'Test Coauthor', 'username': ''} + assert 'authorids' not in note.content + + def test_remove_name_and_rename_profile_id(self, support_client, openreview_client, helpers): ana_client = helpers.create_user('ana@profile.org', 'Ana', 'Last', alternates=[], institution='google.com')