From 3eaac4afaf8dad82e3a95f2306a9e57ab87e0e3e Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Sun, 30 Nov 2025 16:56:58 -0500 Subject: [PATCH 01/20] Add group creation --- openreview/arr/arr.py | 24 ++++++- openreview/arr/helpers.py | 140 +++++++++++++++++++++++++++++++++++++ tests/test_arr_venue_v2.py | 76 ++++++++++++++++++++ 3 files changed, 239 insertions(+), 1 deletion(-) diff --git a/openreview/arr/arr.py b/openreview/arr/arr.py index 9ac18c5cf1..ade42a9612 100644 --- a/openreview/arr/arr.py +++ b/openreview/arr/arr.py @@ -17,13 +17,16 @@ from openreview.api import Invitation from openreview.venue.recruitment import Recruitment from openreview.arr.helpers import ( - setup_arr_invitations + setup_arr_invitations, + setup_arr_root_groups, + ARR_ROLE_NAMES ) from openreview.stages.arr_content import hide_fields, arr_withdrawal_content SHORT_BUFFER_MIN = 30 LONG_BUFFER_DAYS = 10 SENIORITY_PUBLICATION_COUNT = 8 +ROOT_DOMAIN = 'aclweb.org/ACL/ARR' class ARR(object): @@ -363,6 +366,25 @@ def expire_invitation(self, invitation_id): def setup(self, program_chair_ids=[], publication_chairs_ids=[]): setup_value = self.venue.setup(program_chair_ids, publication_chairs_ids) + # Initialize Shared Groups (root-level ARR groups) if not populated + setup_arr_root_groups(self.client, self.support_user) + + # Add missing program chairs missing from the EIC group + members = self.client.get_group(f'{ROOT_DOMAIN}/Editors_In_Chief').members + for program_chair_id in program_chair_ids: + if program_chair_id not in members: + members.append(program_chair_id) + self.client.add_members_to_group(f'{ROOT_DOMAIN}/Editors_In_Chief', program_chair_ids) + + # Synchronize groups + for role in ARR_ROLE_NAMES: + # skip EIC group + if role == 'Editors_In_Chief': + continue + source_group = self.client.get_group(f'{ROOT_DOMAIN}/{role}') + target_group_id = f'{self.venue_id}/{role}' + self.client.add_members_to_group(target_group_id, source_group.members) + with open(os.path.join(os.path.dirname(__file__), 'webfield/homepageWebfield.js')) as f: content = f.read() self.client.post_group_edit( diff --git a/openreview/arr/helpers.py b/openreview/arr/helpers.py index 251dba2843..862567ab3a 100644 --- a/openreview/arr/helpers.py +++ b/openreview/arr/helpers.py @@ -1,4 +1,5 @@ import openreview +import os import time from enum import Enum from datetime import datetime, timedelta @@ -45,6 +46,15 @@ from openreview.stages.default_content import comment_v2 +ARR_ROLE_NAMES = [ + 'Reviewers', + 'Area_Chairs', + 'Senior_Area_Chairs', + 'Ethics_Reviewers', + 'Ethics_Chairs', + 'Editors_In_Chief' +] + class ARRWorkflow(object): UPDATE_WAIT_TIME = 5000 CONFIGURATION_INVITATION_CONTENT = { @@ -2101,3 +2111,133 @@ def get_resubmissions(submissions, previous_url_field): lambda s: previous_url_field in s.content and 'value' in s.content[previous_url_field] and len(s.content[previous_url_field]['value']) > 0, submissions )) + +def setup_arr_root_groups(client, support_user): + """ + Creates the root ARR groups and meta-invitation. + Domain: aclweb.org/ACL/ARR + Roles: Reviewers, Area_Chairs, Senior_Area_Chairs, Ethics_Reviewers, Ethics_Chairs, Editors_In_Chief + + This function: + 1. Ensures the domain group aclweb.org/ACL/ARR exists + 2. Creates the meta invitation aclweb.org/ACL/ARR/-/Edit + 3. Creates role groups (e.g. aclweb.org/ACL/ARR/Reviewers) + """ + # TODO: Add pointer to the current cycle for edge posting + domain = 'aclweb.org/ACL/ARR' + roles = ARR_ROLE_NAMES + super_meta_invitation_id = support_user.split('/')[0] + '/-/Edit' + + # 1. Build domain group path (aclweb.org, aclweb.org/ACL, aclweb.org/ACL/ARR) + path_components = domain.split('/') + paths = ['/'.join(path_components[0:index+1]) for index, path in enumerate(path_components)] + + for p in paths: + group = openreview.tools.get_group(client, id=p) + if group is None: + client.post_group_edit( + invitation=super_meta_invitation_id, + readers=['everyone'], + writers=['~Super_User1'], + signatures=['~Super_User1'], + group=openreview.api.Group( + id=p, + readers=['everyone'], + nonreaders=[], + writers=[p], + signatories=[p], + signatures=['~Super_User1'], + members=[], + details={'writable': True} + ) + ) + + # 2. Create meta invitation (aclweb.org/ACL/ARR/-/Edit) + meta_invitation_id = f'{domain}/-/Edit' + meta_invitation = openreview.tools.get_invitation(client, meta_invitation_id) + + if meta_invitation is None: + # Get process content from venue module + process_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'venue', 'process') + invitation_edit_process_path = os.path.join(process_dir, 'invitation_edit_process.py') + group_edit_process_path = os.path.join(process_dir, 'group_edit_process.py') + + invitation_edit_script = '' + group_edit_script = '' + + with open(invitation_edit_process_path) as f: + invitation_edit_script = f.read() + + with open(group_edit_process_path) as f: + group_edit_script = f.read() + + client.post_invitation_edit( + invitations=None, + readers=[domain], + writers=[domain], + signatures=['~Super_User1'], + invitation=openreview.api.Invitation( + id=meta_invitation_id, + invitees=[domain], + readers=[domain], + signatures=['~Super_User1'], + content={ + 'invitation_edit_script': { + 'value': invitation_edit_script + }, + 'group_edit_script': { + 'value': group_edit_script + } + }, + edit=True + ) + ) + + editors_in_chief_id = f'{domain}/Editors_In_Chief' + + for role in roles: + role_group_id = f'{domain}/{role}' + role_group = openreview.tools.get_group(client, id=role_group_id) + + if role_group is None: + if role == 'Editors_In_Chief': + readers = [domain, role_group_id] + writers = [domain, role_group_id] + signatories = [domain, role_group_id] + else: + readers = [domain, editors_in_chief_id, role_group_id] + writers = [domain, editors_in_chief_id] + signatories = [domain, role_group_id] + + client.post_group_edit( + invitation=meta_invitation_id, + readers=[domain], + writers=[domain], + signatures=[domain], + group=openreview.api.Group( + id=role_group_id, + readers=readers, + writers=writers, + signatures=[domain], + signatories=signatories, + members=[] + ) + ) + + # Add Editors_In_Chief to the domain group as members + domain_group = openreview.tools.get_group(client, id=domain) + + # Add Editors_In_Chief group to domain group members if not already present + if editors_in_chief_id not in domain_group.members: + client.post_group_edit( + invitation=meta_invitation_id, + readers=[domain], + writers=[domain], + signatures=[domain], + group=openreview.api.Group( + id=domain, + members={ + 'add': [editors_in_chief_id] + } + ) + ) diff --git a/tests/test_arr_venue_v2.py b/tests/test_arr_venue_v2.py index 55a9f02dd2..8fe8e33452 100644 --- a/tests/test_arr_venue_v2.py +++ b/tests/test_arr_venue_v2.py @@ -27,6 +27,7 @@ arr_ac_max_load_task, arr_sac_max_load_task ) +from openreview.arr.helpers import setup_arr_root_groups # API2 template from ICML class TestARRVenueV2(): @@ -173,6 +174,38 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req license = 'CC BY-SA 4.0' )) + # Create top-level groups first (assume this will be done before deployment) + setup_arr_root_groups(openreview_client, 'openreview.net/Support') + + # Add all users to top-level role groups + openreview_client.add_members_to_group('aclweb.org/ACL/ARR/Reviewers', [ + '~Reviewer_ARROne1', + '~Reviewer_ARRTwo1', + '~Reviewer_ARRTwoMerge1', + '~Reviewer_ARRThree1', + '~Reviewer_ARRFour1', + '~Reviewer_ARRFive1', + '~Reviewer_ARRSix1', + '~Reviewer_ARRSeven1', + '~Reviewer_ARRNA1', + ]) + openreview_client.add_members_to_group('aclweb.org/ACL/ARR/Area_Chairs', [ + '~AC_ARROne1', + '~AC_ARRTwo1', + '~AC_ARRThree1', + ]) + openreview_client.add_members_to_group('aclweb.org/ACL/ARR/Senior_Area_Chairs', [ + '~SAC_ARROne1', + '~SAC_ARRTwo1', + '~SAC_ARRThree1', + ]) + openreview_client.add_members_to_group('aclweb.org/ACL/ARR/Ethics_Reviewers', [ + '~EthicsReviewer_ARROne1', + ]) + openreview_client.add_members_to_group('aclweb.org/ACL/ARR/Ethics_Chairs', [ + '~EthicsChair_ARROne1', + ]) + request_form_note = pc_client.post_note(openreview.Note( invitation='openreview.net/Support/-/Request_Form', signatures=['~Program_ARRChair1'], @@ -241,6 +274,49 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req writers=['openreview.net/Support'] )) + helpers.await_queue() + + # Verify Root Domain Group exists + domain_group = openreview_client.get_group('aclweb.org/ACL/ARR') + assert domain_group is not None + + # Verify PC is in the EIC group + eic_group = openreview_client.get_group('aclweb.org/ACL/ARR/Editors_In_Chief') + assert 'pc@aclrollingreview.org' in eic_group.members + + # Verify Meta Invitation exists + meta_invitation = openreview_client.get_invitation('aclweb.org/ACL/ARR/-/Edit') + assert meta_invitation is not None + assert 'invitation_edit_script' in meta_invitation.content + assert 'group_edit_script' in meta_invitation.content + + # Verify Role Groups exist + roles = ['Reviewers', 'Area_Chairs', 'Senior_Area_Chairs', 'Ethics_Reviewers', 'Editors_In_Chief'] + for role in roles: + role_group = openreview_client.get_group(f'aclweb.org/ACL/ARR/{role}') + assert role_group is not None + assert role_group.id == f'aclweb.org/ACL/ARR/{role}' + + # Verify Editors_In_Chief has correct permissions + eic_group = openreview_client.get_group('aclweb.org/ACL/ARR/Editors_In_Chief') + assert 'aclweb.org/ACL/ARR/Editors_In_Chief' in eic_group.writers + + august_cycle_group = openreview_client.get_group('aclweb.org/ACL/ARR/2023/August/Reviewers') + # Use an existing member from the August cycle group + reviewer_profile_id = august_cycle_group.members[0] + + # Verify shared group now has the member + shared_group = openreview_client.get_group('aclweb.org/ACL/ARR/Reviewers') + assert reviewer_profile_id in shared_group.members + + # Test that calling setup_arr_root_groups again doesn't break (idempotent) + setup_arr_root_groups(openreview_client, 'openreview.net/Support') + + # Verify groups still exist + for role in roles: + role_group = openreview_client.get_group(f'aclweb.org/ACL/ARR/{role}') + assert role_group is not None + helpers.await_queue_edit(client, invitation='openreview.net/Support/-/Request{}/Deploy'.format(request_form_note.number)) group = openreview_client.get_group('aclweb.org/ACL/ARR/2023/August') From 423990021b75aeec557ac8889f0df1d4d882979d Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Wed, 3 Dec 2025 16:47:12 +0100 Subject: [PATCH 02/20] Scripts read from root invitations --- openreview/arr/management/setup_ae_matching.py | 15 +++++++-------- .../arr/management/setup_reviewer_matching.py | 15 +++++++-------- openreview/arr/management/setup_sae_matching.py | 11 +++++++---- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/openreview/arr/management/setup_ae_matching.py b/openreview/arr/management/setup_ae_matching.py index 18c9783eac..e41300bf34 100644 --- a/openreview/arr/management/setup_ae_matching.py +++ b/openreview/arr/management/setup_ae_matching.py @@ -10,7 +10,7 @@ def process(client, invitation): from openreview.venue import matching from openreview.arr.helpers import get_resubmissions - from openreview.arr.arr import SENIORITY_PUBLICATION_COUNT + from openreview.arr.arr import SENIORITY_PUBLICATION_COUNT, ROOT_DOMAIN from collections import defaultdict def replace_edge(existing_edge=None, edge_inv=None, new_weight=None, submission_id=None, profile_id=None, edge_readers=None): @@ -59,6 +59,7 @@ def replace_edge(existing_edge=None, edge_inv=None, new_weight=None, submission_ ae_cmp_inv = domain.content['area_chairs_custom_max_papers_id']['value'] reviewers_id = domain.content['reviewers_id']['value'] area_chairs_id = domain.content['area_chairs_id']['value'] + area_chairs_name = domain.content['area_chairs_name']['value'] area_chairs_group = client.get_group(area_chairs_id).members senior_area_chairs_id = domain.content['senior_area_chairs_id']['value'] tracks_field_name = 'research_area' @@ -107,7 +108,8 @@ def replace_edge(existing_edge=None, edge_inv=None, new_weight=None, submission_ # Build load map print(f"num profiles {len(all_profiles)}") id_to_load_note = {} - for role_id in [area_chairs_id]: + root_role_id = f"{ROOT_DOMAIN}/{area_chairs_name}" + for role_id in [root_role_id]: load_notes = client.get_all_notes(invitation=f"{role_id}/-/{max_load_name}") ## Assume only 1 note per user for note in load_notes: if note.signatures[0] not in name_to_id: @@ -118,14 +120,14 @@ def replace_edge(existing_edge=None, edge_inv=None, new_weight=None, submission_ # Build track map track_to_ids = {} for role_id in [area_chairs_id]: - track_to_ids[role_id] = defaultdict(list) - registration_notes = client.get_all_notes(invitation=f"{role_id}/-/{registration_name}") + track_to_ids[role_id] = defaultdict(set) + registration_notes = client.get_all_notes(invitation=f"{root_role_id}/-/{registration_name}") for note in registration_notes: if note.signatures[0] not in name_to_id: continue note_signature_id = name_to_id[note.signatures[0]] for track in note.content[tracks_field_name]['value']: - track_to_ids[role_id][track].append(note_signature_id) + track_to_ids[role_id][track].add(note_signature_id) # Build research area invitation matching.Matching(venue, client.get_group(role_id), None)._create_edge_invitation( @@ -141,9 +143,6 @@ def replace_edge(existing_edge=None, edge_inv=None, new_weight=None, submission_ role_cmp_inv = f"{role_id}/-/Custom_Max_Papers" print(f"num of notes {len(id_to_load_note)}") for id, note in id_to_load_note.items(): - load_invitation = [inv for inv in note.invitations if max_load_name in inv][0] - if role_id not in load_invitation: - continue cmp_to_post.append( openreview.api.Edge( diff --git a/openreview/arr/management/setup_reviewer_matching.py b/openreview/arr/management/setup_reviewer_matching.py index 1cf259c1a1..029de040f3 100644 --- a/openreview/arr/management/setup_reviewer_matching.py +++ b/openreview/arr/management/setup_reviewer_matching.py @@ -10,7 +10,7 @@ def process(client, invitation): from openreview.venue import matching from openreview.arr.helpers import get_resubmissions - from openreview.arr.arr import SENIORITY_PUBLICATION_COUNT + from openreview.arr.arr import SENIORITY_PUBLICATION_COUNT, ROOT_DOMAIN from collections import defaultdict def get_title(profile): @@ -131,6 +131,7 @@ def replace_edge(existing_edge=None, edge_inv=None, new_weight=None, submission_ rev_affinity_inv = domain.content['reviewers_affinity_score_id']['value'] rev_cmp_inv = domain.content['reviewers_custom_max_papers_id']['value'] reviewers_id = domain.content['reviewers_id']['value'] + reviewers_name = domain.content['reviewers_name']['value'] reviewers_group = client.get_group(reviewers_id).members area_chairs_id = domain.content['area_chairs_id']['value'] #area_chairs_group = client.get_group(area_chairs_id).members @@ -181,7 +182,8 @@ def replace_edge(existing_edge=None, edge_inv=None, new_weight=None, submission_ # Build load map id_to_load_note = {} - for role_id in [reviewers_id]: + root_role_id = f"{ROOT_DOMAIN}/{reviewers_name}" + for role_id in [root_role_id]: load_notes = client.get_all_notes(invitation=f"{role_id}/-/{max_load_name}") ## Assume only 1 note per user for note in load_notes: if note.signatures[0] not in name_to_id: @@ -192,14 +194,14 @@ def replace_edge(existing_edge=None, edge_inv=None, new_weight=None, submission_ # Build track map track_to_ids = {} for role_id in [reviewers_id]: - track_to_ids[role_id] = defaultdict(list) - registration_notes = client.get_all_notes(invitation=f"{role_id}/-/{registration_name}") + track_to_ids[role_id] = defaultdict(set) + registration_notes = client.get_all_notes(invitation=f"{root_role_id}/-/{registration_name}") for note in registration_notes: if note.signatures[0] not in name_to_id: continue note_signature_id = name_to_id[note.signatures[0]] for track in note.content[tracks_field_name]['value']: - track_to_ids[role_id][track].append(note_signature_id) + track_to_ids[role_id][track].add(note_signature_id) # Build research area invitation matching.Matching(venue, client.get_group(role_id), None)._create_edge_invitation( @@ -239,9 +241,6 @@ def replace_edge(existing_edge=None, edge_inv=None, new_weight=None, submission_ cmp_to_post = [] role_cmp_inv = f"{role_id}/-/Custom_Max_Papers" for id, note in id_to_load_note.items(): - load_invitation = [inv for inv in note.invitations if max_load_name in inv][0] - if role_id not in load_invitation: - continue cmp_to_post.append( openreview.api.Edge( diff --git a/openreview/arr/management/setup_sae_matching.py b/openreview/arr/management/setup_sae_matching.py index d9d044bd83..dc3c7f08d6 100644 --- a/openreview/arr/management/setup_sae_matching.py +++ b/openreview/arr/management/setup_sae_matching.py @@ -12,6 +12,7 @@ def process(client, invitation): from openreview.venue import matching from openreview.arr.helpers import get_resubmissions from openreview.stages.arr_content import arr_tracks + from openreview.arr.arr import ROOT_DOMAIN from collections import defaultdict domain = client.get_group(invitation.domain) @@ -20,6 +21,7 @@ def process(client, invitation): previous_url_field = 'previous_URL' reviewers_id = domain.content['reviewers_id']['value'] senior_area_chairs_id = domain.content['senior_area_chairs_id']['value'] + senior_area_chairs_name = domain.content['senior_area_chairs_name']['value'] tracks_field_name = 'research_area' tracks_inv_name = 'Research_Area' @@ -61,7 +63,8 @@ def process(client, invitation): # Build load map id_to_load_note = {} - for role_id in [senior_area_chairs_id]: + root_role_id = f"{ROOT_DOMAIN}/{senior_area_chairs_name}" + for role_id in [root_role_id]: load_notes = client.get_all_notes(invitation=f"{role_id}/-/{max_load_name}") ## Assume only 1 note per user for note in load_notes: if note.signatures[0] not in name_to_id: @@ -72,8 +75,8 @@ def process(client, invitation): # Build track map track_to_ids = {} for role_id in [senior_area_chairs_id]: - track_to_ids[role_id] = defaultdict(list) - registration_notes = client.get_all_notes(invitation=f"{role_id}/-/{registration_name}") + track_to_ids[role_id] = defaultdict(set) + registration_notes = client.get_all_notes(invitation=f"{root_role_id}/-/{registration_name}") for note in registration_notes: if note.signatures[0] not in name_to_id: continue @@ -85,7 +88,7 @@ def process(client, invitation): availability_string = load_note.content.get('availability_this_cycle', {}).get('value', 'will NOT be able to serve') ## Assume default not available if 'I confirm that I will serve' in availability_string: for track in note.content[tracks_field_name]['value']: - track_to_ids[role_id][track].append(note_signature_id) + track_to_ids[role_id][track].add(note_signature_id) # Build research area invitation matching.Matching(venue, client.get_group(role_id), None)._create_edge_invitation( From 20854566c5ba31a992f07a9b7ade60db9256b99b Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Thu, 4 Dec 2025 14:29:40 +0100 Subject: [PATCH 03/20] Add extra edges --- .../arr/management/setup_ae_matching.py | 38 ++++++++ .../arr/management/setup_reviewer_matching.py | 91 +++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/openreview/arr/management/setup_ae_matching.py b/openreview/arr/management/setup_ae_matching.py index e41300bf34..567b3be4a7 100644 --- a/openreview/arr/management/setup_ae_matching.py +++ b/openreview/arr/management/setup_ae_matching.py @@ -162,6 +162,44 @@ def replace_edge(existing_edge=None, edge_inv=None, new_weight=None, submission_ ) print(f"posting {len(cmp_to_post)} custom max papers for {role_id}") openreview.tools.post_bulk_edges(client=client, edges=cmp_to_post) + + # Create Reviewing_Resubmissions edges from root domain load notes + for role_id in [area_chairs_id]: + resubmissions_to_post = [] + role_resubmissions_inv = f"{role_id}/-/Reviewing_Resubmissions" + + + for id, note in id_to_load_note.items(): + max_load = int(note.content['maximum_load_this_cycle']['value']) + for_resubmissions = note.content.get('maximum_load_this_cycle_for_resubmissions', {}).get('value', '') + + availability_label = None + if 'yes' in for_resubmissions.lower() and max_load == 0: + availability_label = 'Only Reviewing Resubmissions' + elif 'yes' in for_resubmissions.lower(): + availability_label = 'Yes' + elif 'no' in for_resubmissions.lower(): + availability_label = 'No' + + if availability_label: + resubmissions_to_post.append( + openreview.api.Edge( + invitation=role_resubmissions_inv, + head=role_id, + tail=id, + label=availability_label, + readers=track_edge_readers[role_id] + [id], + writers=[venue_id], + signatures=[venue_id] + ) + ) + + client.delete_edges( + invitation=role_resubmissions_inv, + soft_delete=True, + wait_to_finish=True + ) + openreview.tools.post_bulk_edges(client=client, edges=resubmissions_to_post) ae_exceptions = {} for submission in resubmissions: diff --git a/openreview/arr/management/setup_reviewer_matching.py b/openreview/arr/management/setup_reviewer_matching.py index 029de040f3..b240912af6 100644 --- a/openreview/arr/management/setup_reviewer_matching.py +++ b/openreview/arr/management/setup_reviewer_matching.py @@ -259,6 +259,97 @@ def replace_edge(existing_edge=None, edge_inv=None, new_weight=None, submission_ wait_to_finish=True ) openreview.tools.post_bulk_edges(client=client, edges=cmp_to_post) + + # Create Reviewing_Resubmissions edges from root domain load notes + for role_id in [reviewers_id]: + resubmissions_to_post = [] + role_resubmissions_inv = f"{role_id}/-/Reviewing_Resubmissions" + + for id, note in id_to_load_note.items(): + max_load = int(note.content['maximum_load_this_cycle']['value']) + for_resubmissions = note.content.get('maximum_load_this_cycle_for_resubmissions', {}).get('value', '') + + availability_label = None + if 'yes' in for_resubmissions.lower() and max_load == 0: + availability_label = 'Only Reviewing Resubmissions' + elif 'yes' in for_resubmissions.lower(): + availability_label = 'Yes' + elif 'no' in for_resubmissions.lower(): + availability_label = 'No' + + if availability_label: + resubmissions_to_post.append( + openreview.api.Edge( + invitation=role_resubmissions_inv, + head=role_id, + tail=id, + label=availability_label, + readers=track_edge_readers[role_id] + [id], + writers=[venue_id], + signatures=[venue_id] + ) + ) + + client.delete_edges( + invitation=role_resubmissions_inv, + soft_delete=True, + wait_to_finish=True + ) + openreview.tools.post_bulk_edges(client=client, edges=resubmissions_to_post) + + # Handle Ethics Reviewers Custom_Max_Papers (no Reviewing_Resubmissions for ethics reviewers) + ethics_reviewers_name = domain.content['ethics_reviewers_name']['value'] + ethics_reviewers_id = domain.content['ethics_chairs_id']['value'].replace( + domain.content['ethics_chairs_name']['value'], + ethics_reviewers_name + ) + root_ethics_role_id = f"{ROOT_DOMAIN}/{ethics_reviewers_name}" + + # Build ethics load map + ethics_id_to_load_note = {} + ethics_profiles = openreview.tools.get_profiles(client, client.get_group(ethics_reviewers_id).members) + ethics_name_to_id = {} + for profile in ethics_profiles: + filtered_names = filter( + lambda obj: 'username' in obj and len(obj['username']) > 0, + profile.content.get('names', []) + ) + for name_obj in filtered_names: + ethics_name_to_id[name_obj['username']] = profile.id + + ethics_load_notes = client.get_all_notes(invitation=f"{root_ethics_role_id}/-/{max_load_name}") + for note in ethics_load_notes: + if note.signatures[0] not in ethics_name_to_id: + continue + note_signature_id = ethics_name_to_id[note.signatures[0]] + ethics_id_to_load_note[note_signature_id] = note + + # Post Ethics Custom_Max_Papers edges + ethics_cmp_to_post = [] + ethics_cmp_inv = f"{ethics_reviewers_id}/-/Custom_Max_Papers" + matching.Matching(venue, client.get_group(ethics_reviewers_id), None)._create_edge_invitation( + edge_id=ethics_cmp_inv + ) + + for id, note in ethics_id_to_load_note.items(): + ethics_cmp_to_post.append( + openreview.api.Edge( + invitation=ethics_cmp_inv, + head=ethics_reviewers_id, + tail=id, + weight=int(note.content['maximum_load_this_cycle']['value']), + readers=[venue_id, id], + writers=[venue_id], + signatures=[venue_id] + ) + ) + + client.delete_edges( + invitation=ethics_cmp_inv, + soft_delete=True, + wait_to_finish=True + ) + openreview.tools.post_bulk_edges(client=client, edges=ethics_cmp_to_post) reviewer_exceptions = {} for submission in resubmissions: From 9fe90ea755ffc647ad619bda5397ec397e1ad536 Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Thu, 4 Dec 2025 14:31:01 +0100 Subject: [PATCH 04/20] Add tests and invitation builder --- openreview/arr/arr.py | 2 +- openreview/arr/root_invitation.py | 184 +++++ tests/test_arr_venue_v2.py | 1040 +++++++++-------------------- 3 files changed, 519 insertions(+), 707 deletions(-) create mode 100644 openreview/arr/root_invitation.py diff --git a/openreview/arr/arr.py b/openreview/arr/arr.py index ade42a9612..131006aeae 100644 --- a/openreview/arr/arr.py +++ b/openreview/arr/arr.py @@ -366,7 +366,7 @@ def expire_invitation(self, invitation_id): def setup(self, program_chair_ids=[], publication_chairs_ids=[]): setup_value = self.venue.setup(program_chair_ids, publication_chairs_ids) - # Initialize Shared Groups (root-level ARR groups) if not populated + # Initialize root-level ARR groups if not populated setup_arr_root_groups(self.client, self.support_user) # Add missing program chairs missing from the EIC group diff --git a/openreview/arr/root_invitation.py b/openreview/arr/root_invitation.py new file mode 100644 index 0000000000..33d097b88f --- /dev/null +++ b/openreview/arr/root_invitation.py @@ -0,0 +1,184 @@ +import openreview +import time +import datetime + +from openreview.arr import ROOT_DOMAIN +from openreview.api import ( + Invitation, + Note +) +from openreview import tools +import os + + +class RootInvitationBuilder(object): + def __init__(self, client, update_wait_time=5000): + self.client = client + self.root_domain = ROOT_DOMAIN + self.meta_invitation_id = f"{self.root_domain}/-/Edit" + self.update_wait_time = 1000 if 'localhost' in client.baseurl else update_wait_time + self.spleep_time_for_logs = 0.5 if 'localhost' in client.baseurl else 10 + self.update_date_string = "#{4/mdate} + " + str(self.update_wait_time) + + def save_invitation(self, invitation, replacement=None): + self.client.post_invitation_edit(invitations=self.meta_invitation_id, + readers=[self.root_domain], + writers=[self.root_domain], + signatures=[self.root_domain], + replacement=replacement, + invitation=invitation + ) + invitation = self.client.get_invitation(invitation.id) + + if invitation.date_processes and len(invitation.date_processes[0]['dates']) > 1 and self.update_date_string == invitation.date_processes[0]['dates'][1]: + process_logs = self.client.get_process_logs(id=invitation.id + '-0-1', min_sdate = invitation.tmdate + self.update_wait_time - 1000) + count = 0 + max_count = 1800 / self.spleep_time_for_logs + while len(process_logs) == 0 and count < max_count: ## wait up to 30 minutes + time.sleep(self.spleep_time_for_logs) + process_logs = self.client.get_process_logs(id=invitation.id + '-0-1', min_sdate = invitation.tmdate + self.update_wait_time - 1000) + count += 1 + + if len(process_logs) == 0: + raise openreview.OpenReviewException('Time out waiting for invitation to complete: ' + invitation.id) + + if process_logs[0]['status'] == 'error': + raise openreview.OpenReviewException('Error saving invitation: ' + invitation.id) + + return invitation + + def set_registration_invitation(self, registration_stage): + + client = self.client + venue_id = self.root_domain + + meta_invitation_id = f"{self.root_domain}/-/Edit" + support_user = 'openreview.net/Support' + + committee_id = registration_stage.committee_id + + readers = [venue_id, committee_id] + + registration_parent_invitation_id = f"{committee_id}/-/{registration_stage.name}_Form" + invitation = Invitation( + id = registration_parent_invitation_id, + readers = ['everyone'], + writers = [venue_id], + signatures = [venue_id], + invitees = [venue_id, support_user], + edit = { + 'signatures': [venue_id], + 'readers': [venue_id], + 'writers': [venue_id], + 'note': { + 'id': { + 'param': { + 'withInvitation': registration_parent_invitation_id, + 'optional': True + } + }, + 'ddate': { + 'param': { + 'range': [ 0, 9999999999999 ], + 'optional': True, + 'deletable': True + } + }, + 'readers': readers, + 'writers': [venue_id], + 'signatures': [venue_id], + 'content': { + 'title': { + 'order': 1, + 'value': { + 'param': { + 'type': 'string', + 'maxLength': 250 + } + } + }, + 'instructions': { + 'order': 2, + 'value': { + 'param': { + 'type': 'string', + 'maxLength': 250000, + 'markdown': True, + 'input': 'textarea' + } + } + } + } + } + } + ) + self.save_invitation(invitation, replacement=True) + + registration_notes = client.get_notes(invitation=registration_parent_invitation_id) + if registration_notes: + print('Updating existing registration note') + forum_edit = client.post_note_edit(invitation = meta_invitation_id, + signatures=[venue_id], + note = Note( + id = registration_notes[0].id, + content = { + 'instructions': { 'value': registration_stage.instructions }, + 'title': { 'value': registration_stage.title} + } + )) + else: + forum_edit = client.post_note_edit(invitation=invitation.id, + signatures=[venue_id], + note = Note( + signatures = [venue_id], + content = { + 'instructions': { 'value': registration_stage.instructions }, + 'title': { 'value': registration_stage.title} + } + ) + ) + forum_note_id = forum_edit['note']['id'] + start_date = registration_stage.start_date + due_date = registration_stage.due_date + expdate = registration_stage.expdate if registration_stage.expdate else due_date + + registration_content = registration_stage.get_content(api_version='2') + + registration_invitation_id = f"{committee_id}/-/{registration_stage.name}" + invitation=Invitation(id=registration_invitation_id, + invitees=[committee_id], + readers=readers, + writers=[venue_id], + signatures=[venue_id], + cdate = tools.datetime_millis(start_date) if start_date else None, + duedate = tools.datetime_millis(due_date) if due_date else None, + expdate = tools.datetime_millis(expdate) if expdate else None, + maxReplies = 1, + minReplies = 1, + edit={ + 'signatures': { 'param': { 'items': [ { 'prefix': '~.*' } ] }}, + 'readers': [venue_id, '${2/signatures}'], + 'note': { + 'id': { + 'param': { + 'withInvitation': registration_invitation_id, + 'optional': True + } + }, + 'ddate': { + 'param': { + 'range': [ 0, 9999999999999 ], + 'optional': True, + 'deletable': True + } + }, + 'forum': forum_note_id, + 'replyto': forum_note_id, + 'signatures': ['${3/signatures}'], + 'readers': [venue_id, '${3/signatures}'], + 'writers': [venue_id, '${3/signatures}'], + 'content': registration_content + } + } + ) + self.save_invitation(invitation, replacement=True) \ No newline at end of file diff --git a/tests/test_arr_venue_v2.py b/tests/test_arr_venue_v2.py index 8fe8e33452..37131c7b5a 100644 --- a/tests/test_arr_venue_v2.py +++ b/tests/test_arr_venue_v2.py @@ -25,9 +25,15 @@ arr_max_load_task_forum, arr_reviewer_max_load_task, arr_ac_max_load_task, - arr_sac_max_load_task + arr_sac_max_load_task, + arr_reviewer_emergency_load_task_forum, + arr_reviewer_emergency_load_task, + arr_ac_emergency_load_task_forum, + arr_ac_emergency_load_task ) from openreview.arr.helpers import setup_arr_root_groups +from openreview.arr.root_invitation import RootInvitationBuilder +from openreview.arr.arr import ROOT_DOMAIN # API2 template from ICML class TestARRVenueV2(): @@ -174,10 +180,12 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req license = 'CC BY-SA 4.0' )) - # Create top-level groups first (assume this will be done before deployment) + # Create top-level groups first + # NOTE: (assume this will be done before deployment) setup_arr_root_groups(openreview_client, 'openreview.net/Support') # Add all users to top-level role groups + # NOTE: (assume this will be done before deployment) openreview_client.add_members_to_group('aclweb.org/ACL/ARR/Reviewers', [ '~Reviewer_ARROne1', '~Reviewer_ARRTwo1', @@ -206,6 +214,155 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req '~EthicsChair_ARROne1', ]) + # Create root maximum load invitations + # NOTE: (assume this will be done before deployment) + reg_start_date = datetime.datetime.now() + reg_due_date = reg_start_date + datetime.timedelta(days=1) + reg_exp_date = reg_due_date + datetime.timedelta(days=1) + root_invitation_builder = RootInvitationBuilder(openreview_client) + + reviewer_load_stage = openreview.stages.RegistrationStage( + committee_id='aclweb.org/ACL/ARR/Reviewers', + name='Max_Load_And_Unavailability_Request', + start_date=reg_start_date, + due_date=reg_due_date, + expdate=reg_exp_date, + title = 'Reviewers' + ' ' + arr_max_load_task_forum['title'], + instructions = arr_max_load_task_forum['instructions'], + remove_fields = ['profile_confirmed', 'expertise_confirmed'], + additional_fields = arr_reviewer_max_load_task + ) + root_invitation_builder.set_registration_invitation( + reviewer_load_stage + ) + + area_chair_load_stage = openreview.stages.RegistrationStage( + committee_id='aclweb.org/ACL/ARR/Area_Chairs', + name='Max_Load_And_Unavailability_Request', + start_date=reg_start_date, + due_date=reg_due_date, + expdate=reg_exp_date, + title = 'Area Chairs' + ' ' + arr_max_load_task_forum['title'], + instructions = arr_max_load_task_forum['instructions'], + remove_fields = ['profile_confirmed', 'expertise_confirmed'], + additional_fields = arr_ac_max_load_task + ) + root_invitation_builder.set_registration_invitation( + area_chair_load_stage + ) + senior_area_chair_load_stage = openreview.stages.RegistrationStage( + committee_id='aclweb.org/ACL/ARR/Senior_Area_Chairs', + name='Max_Load_And_Unavailability_Request', + start_date=reg_start_date, + due_date=reg_due_date, + expdate=reg_exp_date, + title = 'Senior Area Chairs' + ' ' + arr_max_load_task_forum['title'], + instructions = arr_max_load_task_forum['instructions'], + remove_fields = ['profile_confirmed', 'expertise_confirmed'], + additional_fields = arr_sac_max_load_task + ) + root_invitation_builder.set_registration_invitation( + senior_area_chair_load_stage + ) + + # Create root registration invitations + # NOTE: (assume this will be done before deployment) + reviewer_reg_stage = openreview.stages.RegistrationStage( + committee_id='aclweb.org/ACL/ARR/Reviewers', + name='Registration', + start_date=reg_start_date, + due_date=reg_due_date, + expdate=reg_exp_date, + title = 'Reviewers' + ' ' + arr_registration_task_forum['title'], + instructions = arr_registration_task_forum['instructions'], + additional_fields = arr_registration_task + ) + root_invitation_builder.set_registration_invitation( + reviewer_reg_stage + ) + area_chair_reg_stage = openreview.stages.RegistrationStage( + committee_id='aclweb.org/ACL/ARR/Area_Chairs', + name='Registration', + start_date=reg_start_date, + due_date=reg_due_date, + expdate=reg_exp_date, + title = 'Area Chairs' + ' ' + arr_registration_task_forum['title'], + instructions = arr_registration_task_forum['instructions'], + additional_fields = arr_registration_task + ) + root_invitation_builder.set_registration_invitation( + area_chair_reg_stage + ) + senior_area_chair_reg_stage = openreview.stages.RegistrationStage( + committee_id='aclweb.org/ACL/ARR/Senior_Area_Chairs', + name='Registration', + start_date=reg_start_date, + due_date=reg_due_date, + expdate=reg_exp_date, + title = 'Senior Area Chairs' + ' ' + arr_registration_task_forum['title'], + instructions = arr_registration_task_forum['instructions'], + additional_fields = arr_registration_task + ) + root_invitation_builder.set_registration_invitation( + senior_area_chair_reg_stage + ) + reviewer_license_stage = openreview.stages.RegistrationStage( + committee_id='aclweb.org/ACL/ARR/Reviewers', + name='License_Agreement', + start_date=reg_start_date, + due_date=reg_due_date, + expdate=reg_exp_date, + title = 'Reviewers' + ' ' + arr_content_license_task_forum['title'], + instructions = arr_content_license_task_forum['instructions'], + additional_fields = arr_content_license_task, + remove_fields = ['profile_confirmed', 'expertise_confirmed'] + ) + root_invitation_builder.set_registration_invitation( + reviewer_license_stage + ) + area_chair_license_stage = openreview.stages.RegistrationStage( + committee_id='aclweb.org/ACL/ARR/Area_Chairs', + name='License_Agreement', + start_date=reg_start_date, + due_date=reg_due_date, + expdate=reg_exp_date, + title = 'Area Chairs' + ' ' + arr_content_license_task_forum['title'], + instructions = arr_content_license_task_forum['instructions'], + additional_fields = arr_content_license_task, + remove_fields = ['profile_confirmed', 'expertise_confirmed'] + ) + root_invitation_builder.set_registration_invitation( + area_chair_license_stage + ) + reviewer_emergency_agreement_stage = openreview.stages.RegistrationStage( + committee_id='aclweb.org/ACL/ARR/Reviewers', + name='Emergency_Reviewer_Agreement', + start_date=reg_start_date, + due_date=reg_due_date, + expdate=reg_exp_date, + title = 'Reviewers' + ' ' + arr_reviewer_emergency_load_task_forum['title'], + instructions = arr_reviewer_emergency_load_task_forum['instructions'], + additional_fields = arr_reviewer_emergency_load_task, + remove_fields = ['profile_confirmed', 'expertise_confirmed'] + ) + root_invitation_builder.set_registration_invitation( + reviewer_emergency_agreement_stage + ) + area_chair_emergency_agreement_stage = openreview.stages.RegistrationStage( + committee_id='aclweb.org/ACL/ARR/Area_Chairs', + name='Emergency_Metareviewer_Agreement', + start_date=reg_start_date, + due_date=reg_due_date, + expdate=reg_exp_date, + title = 'Area Chairs' + ' ' + arr_ac_emergency_load_task_forum['title'], + instructions = arr_ac_emergency_load_task_forum['instructions'], + additional_fields = arr_ac_emergency_load_task, + remove_fields = ['profile_confirmed', 'expertise_confirmed'] + ) + root_invitation_builder.set_registration_invitation( + area_chair_emergency_agreement_stage + ) + request_form_note = pc_client.post_note(openreview.Note( invitation='openreview.net/Support/-/Request_Form', signatures=['~Program_ARRChair1'], @@ -821,10 +978,7 @@ def test_june_cycle(self, client, openreview_client, helpers, test_client): helpers.await_queue_edit(openreview_client, edit_id='aclweb.org/ACL/ARR/2023/June/Ethics_Reviewers/-/Submission_Group-0-1', count=1) - ethics_review_invitations = openreview_client.get_all_invitations(invitation='aclweb.org/ACL/ARR/2023/August/-/Ethics_Review') - assert len(ethics_review_invitations) == 0 - - flag_invitation = openreview_client.get_invitation('aclweb.org/ACL/ARR/2023/August/-/Ethics_Review_Flag') + flag_invitation = openreview_client.get_invitation('aclweb.org/ACL/ARR/2023/June/-/Ethics_Review_Flag') assert flag_invitation.process assert 'for invitation_name in [review_name, ae_checklist_name, reviewer_checklist_name]:' in flag_invitation.process assert 'ae_checklist_name' in flag_invitation.content @@ -833,108 +987,6 @@ def test_june_cycle(self, client, openreview_client, helpers, test_client): venue = openreview.helpers.get_conference(client, request_form_note.id, 'openreview.net/Support') venue.create_ethics_review_stage() - # Create past registration stages - - # Pin 2023 and 2024 into next available year - for content in [ - arr_reviewer_max_load_task, - arr_ac_max_load_task, - arr_sac_max_load_task, - ]: - content['next_available_year']['value']['param']['enum'] = list(set([2022, 2023, 2024] + content['next_available_year']['value']['param']['enum'])) - - registration_name = 'Registration' - max_load_name = 'Max_Load_And_Unavailability_Request' - venue.registration_stages.append( - openreview.stages.RegistrationStage(committee_id = venue.get_reviewers_id(), - name = registration_name, - start_date = None, - due_date = due_date, - instructions = arr_registration_task_forum['instructions'], - title = venue.get_reviewers_name() + ' ' + arr_registration_task_forum['title'], - additional_fields=arr_registration_task) - ) - venue.registration_stages.append( - openreview.stages.RegistrationStage(committee_id = venue.get_reviewers_id(), - name = max_load_name, - start_date = None, - due_date = due_date, - instructions = arr_max_load_task_forum['instructions'], - title = venue.get_reviewers_name() + ' ' + arr_max_load_task_forum['title'], - additional_fields=arr_reviewer_max_load_task, - remove_fields=['profile_confirmed', 'expertise_confirmed']) - ) - venue.registration_stages.append( - openreview.stages.RegistrationStage(committee_id = venue.get_reviewers_id(), - name = 'License_Agreement', - start_date = None, - due_date = due_date, - instructions = arr_content_license_task_forum['instructions'], - title = arr_content_license_task_forum['title'], - additional_fields=arr_content_license_task, - remove_fields=['profile_confirmed', 'expertise_confirmed']) - ) - - venue.registration_stages.append( - openreview.stages.RegistrationStage(committee_id = venue.get_area_chairs_id(), - name = registration_name, - start_date = None, - due_date = due_date, - instructions = arr_registration_task_forum['instructions'], - title = venue.get_area_chairs_name() + ' ' + arr_registration_task_forum['title'], - additional_fields=arr_registration_task) - ) - venue.registration_stages.append( - openreview.stages.RegistrationStage(committee_id = venue.get_area_chairs_id(), - name = max_load_name, - start_date = None, - due_date = due_date, - instructions = arr_max_load_task_forum['instructions'], - title = venue.get_area_chairs_name() + ' ' + arr_max_load_task_forum['title'], - additional_fields=arr_ac_max_load_task, - remove_fields=['profile_confirmed', 'expertise_confirmed']) - ) - - venue.registration_stages.append( - openreview.stages.RegistrationStage(committee_id = venue.get_senior_area_chairs_id(), - name = registration_name, - start_date = None, - due_date = due_date, - instructions = arr_registration_task_forum['instructions'], - title = venue.senior_area_chairs_name.replace('_', ' ') + ' ' + arr_registration_task_forum['title'], - additional_fields=arr_registration_task) - ) - venue.registration_stages.append( - openreview.stages.RegistrationStage(committee_id = venue.get_senior_area_chairs_id(), - name = max_load_name, - start_date = None, - due_date = due_date, - instructions = arr_max_load_task_forum['instructions'], - title = venue.senior_area_chairs_name.replace('_', ' ') + ' ' + arr_max_load_task_forum['title'], - additional_fields=arr_sac_max_load_task, - remove_fields=['profile_confirmed', 'expertise_confirmed']) - ) - venue.create_registration_stages() - - # Add max load preprocess validation - invitation_builder = openreview.arr.InvitationBuilder(venue) - venue_roles = [ - venue.get_reviewers_id(), - venue.get_area_chairs_id(), - venue.get_senior_area_chairs_id() - ] - for role in venue_roles: - openreview_client.post_invitation_edit( - invitations=venue.get_meta_invitation_id(), - readers=[venue.id], - writers=[venue.id], - signatures=[venue.id], - invitation=openreview.api.Invitation( - id=f"{role}/-/{max_load_name}", - preprocess=invitation_builder.get_process_content('process/max_load_preprocess.py') - ) - ) - # Post some past registration notes reviewer_client = openreview.api.OpenReviewClient(username = 'reviewer1@aclrollingreview.com', password=helpers.strong_password) reviewer_two_client = openreview.api.OpenReviewClient(username = 'reviewer2@aclrollingreview.com', password=helpers.strong_password) @@ -947,8 +999,20 @@ def test_june_cycle(self, client, openreview_client, helpers, test_client): sac_client = openreview.api.OpenReviewClient(username = 'sac1@aclrollingreview.com', password=helpers.strong_password) sac_two_client = openreview.api.OpenReviewClient(username = 'sac2@aclrollingreview.com', password=helpers.strong_password) sac_three_client = openreview.api.OpenReviewClient(username = 'sac3@aclrollingreview.com', password=helpers.strong_password) + + registration_name = 'Registration' + max_load_name = 'Max_Load_And_Unavailability_Request' + + root_reviewers_reg_inv = f'{ROOT_DOMAIN}/Reviewers/-/{registration_name}' + root_area_chairs_reg_inv = f'{ROOT_DOMAIN}/Area_Chairs/-/{registration_name}' + root_senior_area_chairs_reg_inv = f'{ROOT_DOMAIN}/Senior_Area_Chairs/-/{registration_name}' + root_reviewers_max_load_inv = f'{ROOT_DOMAIN}/Reviewers/-/{max_load_name}' + root_area_chairs_max_load_inv = f'{ROOT_DOMAIN}/Area_Chairs/-/{max_load_name}' + root_senior_area_chairs_max_load_inv = f'{ROOT_DOMAIN}/Senior_Area_Chairs/-/{max_load_name}' + root_license_agreement_inv = f'{ROOT_DOMAIN}/Reviewers/-/License_Agreement' + reviewer_client.post_note_edit( - invitation=f'{venue.get_reviewers_id()}/-/{registration_name}', + invitation=root_reviewers_reg_inv, signatures=['~Reviewer_Alternate_ARROne1'], note=openreview.api.Note( content = { @@ -963,7 +1027,7 @@ def test_june_cycle(self, client, openreview_client, helpers, test_client): ) ) reviewer_two_client.post_note_edit( - invitation=f'{venue.get_reviewers_id()}/-/{registration_name}', + invitation=root_reviewers_reg_inv, signatures=['~Reviewer_ARRTwo1'], note=openreview.api.Note( content = { @@ -980,7 +1044,7 @@ def test_june_cycle(self, client, openreview_client, helpers, test_client): # Post duplicate and merge profiles reviewer_two_merge_client.post_note_edit( - invitation=f'{venue.get_reviewers_id()}/-/{registration_name}', + invitation=root_reviewers_reg_inv, signatures=['~Reviewer_ARRTwoMerge1'], note=openreview.api.Note( content = { @@ -1004,7 +1068,7 @@ def test_june_cycle(self, client, openreview_client, helpers, test_client): profile.content['names'][2]['username'] == '~Reviewer_ARRTwoMerge1' ac_client.post_note_edit( - invitation=f'{venue.get_area_chairs_id()}/-/{registration_name}', + invitation=root_area_chairs_reg_inv, signatures=['~AC_ARROne1'], note=openreview.api.Note( content = { @@ -1019,7 +1083,7 @@ def test_june_cycle(self, client, openreview_client, helpers, test_client): ) ) sac_client.post_note_edit( - invitation=f'{venue.get_senior_area_chairs_id()}/-/{registration_name}', + invitation=root_senior_area_chairs_reg_inv, signatures=['~SAC_ARROne1'], note=openreview.api.Note( content = { @@ -1034,7 +1098,7 @@ def test_june_cycle(self, client, openreview_client, helpers, test_client): ) ) sac_two_client.post_note_edit( - invitation=f'{venue.get_senior_area_chairs_id()}/-/{registration_name}', + invitation=root_senior_area_chairs_reg_inv, signatures=['~SAC_ARRTwo1'], note=openreview.api.Note( content = { @@ -1049,7 +1113,7 @@ def test_june_cycle(self, client, openreview_client, helpers, test_client): ) ) sac_three_client.post_note_edit( - invitation=f'{venue.get_senior_area_chairs_id()}/-/{registration_name}', + invitation=root_senior_area_chairs_reg_inv, signatures=['~SAC_ARRThree1'], note=openreview.api.Note( content = { @@ -1064,211 +1128,9 @@ def test_june_cycle(self, client, openreview_client, helpers, test_client): ) ) - - # Post past unavailability notes - reviewer_client.post_note_edit( ## Reviewer should be available - next available date is now - invitation=f'{venue.get_reviewers_id()}/-/{max_load_name}', - signatures=['~Reviewer_Alternate_ARROne1'], - note=openreview.api.Note( - content = { - 'maximum_load_this_cycle': { 'value': 0 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'No' }, - 'meta_data_donation': { 'value': 'Yes, I consent to donating anonymous metadata of my review for research.' }, - 'next_available_month': { 'value': 'August'}, - 'next_available_year': { 'value': 2023} - } - ) - ) - with pytest.raises(openreview.OpenReviewException, match=r'Please provide both your next available year and month'): - reviewer_two_client.post_note_edit( - invitation=f'{venue.get_reviewers_id()}/-/{max_load_name}', - signatures=['~Reviewer_ARRTwo1'], - note=openreview.api.Note( - content = { - 'maximum_load_this_cycle': { 'value': 0 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'No' }, - 'meta_data_donation': { 'value': 'Yes, I consent to donating anonymous metadata of my review for research.' }, - 'next_available_month': { 'value': 'August'} - } - ) - ) - with pytest.raises(openreview.OpenReviewException, match=r'Please provide both your next available year and month'): - reviewer_two_client.post_note_edit( - invitation=f'{venue.get_reviewers_id()}/-/{max_load_name}', - signatures=['~Reviewer_ARRTwo1'], - note=openreview.api.Note( - content = { - 'maximum_load_this_cycle': { 'value': 0 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'No' }, - 'meta_data_donation': { 'value': 'Yes, I consent to donating anonymous metadata of my review for research.' }, - 'next_available_year': { 'value': 2024} - } - ) - ) - with pytest.raises(openreview.OpenReviewException, match="Please only provide your next available year and month if you are unavailable this cycle. Click Cancel to reset these fields and fill out the form again."): - reviewer_two_client.post_note_edit( - invitation=f'{venue.get_reviewers_id()}/-/{max_load_name}', - signatures=['~Reviewer_ARRTwo1'], - note=openreview.api.Note( - content = { - 'maximum_load_this_cycle': { 'value': 4 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'No' }, - 'meta_data_donation': { 'value': 'Yes, I consent to donating anonymous metadata of my review for research.' }, - 'next_available_year': { 'value': 2024} - } - ) - ) - with pytest.raises(openreview.OpenReviewException, match="Please only provide your next available year and month if you are unavailable this cycle. Click Cancel to reset these fields and fill out the form again."): - reviewer_two_client.post_note_edit( - invitation=f'{venue.get_reviewers_id()}/-/{max_load_name}', - signatures=['~Reviewer_ARRTwo1'], - note=openreview.api.Note( - content = { - 'maximum_load_this_cycle': { 'value': 4 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'No' }, - 'meta_data_donation': { 'value': 'Yes, I consent to donating anonymous metadata of my review for research.' }, - 'next_available_month': { 'value': 'August'} - } - ) - ) - - reviewer_two_client.post_note_edit( ## Reviewer should not be available - 1 month past next cycle - invitation=f'{venue.get_reviewers_id()}/-/{max_load_name}', - signatures=['~Reviewer_ARRTwo1'], - note=openreview.api.Note( - content = { - 'maximum_load_this_cycle': { 'value': 0 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'No' }, - 'meta_data_donation': { 'value': 'Yes, I consent to donating anonymous metadata of my review for research.' }, - 'next_available_month': { 'value': 'September'}, - 'next_available_year': { 'value': 2023} - } - ) - ) - reviewer_three_client.post_note_edit( ## Reviewer should be available - 1 month in the past - invitation=f'{venue.get_reviewers_id()}/-/{max_load_name}', - signatures=['~Reviewer_ARRThree1'], - note=openreview.api.Note( - content = { - 'maximum_load_this_cycle': { 'value': 0 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'No' }, - 'meta_data_donation': { 'value': 'No, I do not consent to donating anonymous metadata of my review for research.' }, - 'next_available_month': { 'value': 'July'}, - 'next_available_year': { 'value': 2023} - } - ) - ) - reviewer_four_client.post_note_edit( ## Reviewer should be available - 1 year in the past - invitation=f'{venue.get_reviewers_id()}/-/{max_load_name}', - signatures=['~Reviewer_ARRFour1'], - note=openreview.api.Note( - content = { - 'maximum_load_this_cycle': { 'value': 0 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'No' }, - 'meta_data_donation': { 'value': 'Yes, I consent to donating anonymous metadata of my review for research.' }, - 'next_available_month': { 'value': 'August'}, - 'next_available_year': { 'value': 2022} - } - ) - ) - reviewer_five_client.post_note_edit( ## Reviewer should be available - 1 year in the past + 1 month in the past - invitation=f'{venue.get_reviewers_id()}/-/{max_load_name}', - signatures=['~Reviewer_ARRFive1'], - note=openreview.api.Note( - content = { - 'maximum_load_this_cycle': { 'value': 0 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'No' }, - 'meta_data_donation': { 'value': 'Yes, I consent to donating anonymous metadata of my review for research.' }, - 'next_available_month': { 'value': 'July'}, - 'next_available_year': { 'value': 2022} - } - ) - ) - ac_client.post_note_edit( ## AC should not be available - 1 year into future - invitation=f'{venue.get_area_chairs_id()}/-/{max_load_name}', - signatures=['~AC_ARROne1'], - note=openreview.api.Note( - content = { - 'maximum_load_this_cycle': { 'value': 0 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'No' }, - 'next_available_month': { 'value': 'August'}, - 'next_available_year': { 'value': 2024} - } - ) - ) - sac_client.post_note_edit( ## SAC should not be available - 1 month + 1 year into future - invitation=f'{venue.get_senior_area_chairs_id()}/-/{max_load_name}', - signatures=['~SAC_ARROne1'], - note=openreview.api.Note( - content = { - 'availability_this_cycle': { 'value': "I confirm that I will serve as SAC in this cycle, with the review load shared equally with other SACs (computed per track in conference-associated cycles)." }, - 'next_available_month': { 'value': 'September'}, - 'next_available_year': { 'value': 2024} - } - ) - ) - sac_two_client.post_note_edit( ## SAC should be available - invitation=f'{venue.get_senior_area_chairs_id()}/-/{max_load_name}', - signatures=['~SAC_ARRTwo1'], - note=openreview.api.Note( - content = { - 'availability_this_cycle': { 'value': "I confirm that I will serve as SAC in this cycle, with the review load shared equally with other SACs (computed per track in conference-associated cycles)." } - } - ) - ) - - # Create past expertise edges - user_client = openreview.api.OpenReviewClient(username='reviewer1@aclrollingreview.com', password=helpers.strong_password) - archive_note = user_client.post_note_edit( - invitation='openreview.net/Archive/-/Direct_Upload', - signatures=['~Reviewer_Alternate_ARROne1'], - note = openreview.api.Note( - pdate = openreview.tools.datetime_millis(datetime.datetime(2019, 4, 30)), - content = { - 'title': { 'value': 'Paper title 2' }, - 'abstract': { 'value': 'Paper abstract 2' }, - 'authors': { 'value': ['Reviewer ARR', 'Test2 Client'] }, - 'authorids': { 'value': ['~Reviewer_Alternate_ARROne1', 'test2@mail.com'] }, - 'venue': { 'value': 'Arxiv' } - }, - license = 'CC BY-SA 4.0' - )) - user_client.post_edge( - openreview.api.Edge( - invitation = venue.get_expertise_selection_id(committee_id = venue.get_reviewers_id()), - readers = [venue.id, '~Reviewer_ARROne1'], - writers = [venue.id, '~Reviewer_ARROne1'], - signatures = ['~Reviewer_ARROne1'], - head = archive_note['note']['id'], - tail = '~Reviewer_ARROne1', - label = 'Exclude' - )) - user_client = openreview.api.OpenReviewClient(username='ac1@aclrollingreview.com', password=helpers.strong_password) - user_client.post_edge( - openreview.api.Edge( - invitation = venue.get_expertise_selection_id(committee_id = venue.get_area_chairs_id()), - readers = [venue.id, '~AC_ARROne1'], - writers = [venue.id, '~AC_ARROne1'], - signatures = ['~AC_ARROne1'], - head = archive_note['note']['id'], - tail = '~AC_ARROne1', - label = 'Exclude' - )) - user_client = openreview.api.OpenReviewClient(username='sac1@aclrollingreview.com', password=helpers.strong_password) - user_client.post_edge( - openreview.api.Edge( - invitation = venue.get_expertise_selection_id(committee_id = venue.get_senior_area_chairs_id()), - readers = [venue.id, '~SAC_ARROne1'], - writers = [venue.id, '~SAC_ARROne1'], - signatures = ['~SAC_ARROne1'], - head = archive_note['note']['id'], - tail = '~SAC_ARROne1', - label = 'Exclude' - )) - - # Create past reviewer license + # Create reviewer license agreements license_edit = reviewer_four_client.post_note_edit( - invitation='aclweb.org/ACL/ARR/2023/June/Reviewers/-/License_Agreement', + invitation=root_license_agreement_inv, signatures=['~Reviewer_ARRFour1'], note=openreview.api.Note( content = { @@ -1281,7 +1143,7 @@ def test_june_cycle(self, client, openreview_client, helpers, test_client): assert reviewer_four_client.get_note(license_edit['note']['id']) license_edit = reviewer_five_client.post_note_edit( - invitation='aclweb.org/ACL/ARR/2023/June/Reviewers/-/License_Agreement', + invitation=root_license_agreement_inv, signatures=['~Reviewer_ARRFive1'], note=openreview.api.Note( content = { @@ -1316,7 +1178,7 @@ def test_ac_recruitment(self, client, openreview_client, helpers, request_page, helpers.await_queue() - assert len(openreview_client.get_group('aclweb.org/ACL/ARR/2023/August/Area_Chairs').members) == 0 + assert len(openreview_client.get_group('aclweb.org/ACL/ARR/2023/August/Area_Chairs').members) == 3 assert len(openreview_client.get_group('aclweb.org/ACL/ARR/2023/August/Area_Chairs/Invited').members) == 2 messages = openreview_client.get_messages(subject = '[ARR August 2023] Invitation to serve as Area Chair') @@ -1852,377 +1714,6 @@ def test_no_assignment_preprocess(self, client, openreview_client, test_client, weight = 0, label = "Invitation Sent" )) - - - - - - def test_copy_members(self, client, openreview_client, helpers): - # Create a previous cycle (2023/June) and test the script that copies all roles - # (reviewers/ACs/SACs/ethics reviewers/ethics chairs) into the current cycle (2023/August) - - # Create groups for previous cycle - pc_client=openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) - pc_client_v2=openreview.api.OpenReviewClient(username='pc@aclrollingreview.org', password=helpers.strong_password) - request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[0] - june_venue = openreview.helpers.get_conference(client, request_form.id, 'openreview.net/Support') - request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] - august_venue = openreview.helpers.get_conference(client, request_form.id, 'openreview.net/Support') - - now = datetime.datetime.now() - - pc_client.post_note( - openreview.Note( - content={ - 'previous_cycle': 'aclweb.org/ACL/ARR/2023/June', - 'setup_shared_data_date': (openreview.tools.datetime.datetime.now() - datetime.timedelta(minutes=10)).strftime('%Y/%m/%d %H:%M') - }, - invitation=f'openreview.net/Support/-/Request{request_form.number}/ARR_Configuration', - forum=request_form.id, - readers=['aclweb.org/ACL/ARR/2023/August/Program_Chairs', 'openreview.net/Support'], - referent=request_form.id, - replyto=request_form.id, - signatures=['~Program_ARRChair1'], - writers=[], - ) - ) - - helpers.await_queue_edit(openreview_client, 'aclweb.org/ACL/ARR/2023/August/-/Share_Data-0-1', count=1) - - # Call twice to ensure data only gets copied once - pc_client.post_note( - openreview.Note( - content={ - 'previous_cycle': 'aclweb.org/ACL/ARR/2023/June', - 'setup_shared_data_date': (openreview.tools.datetime.datetime.now() - datetime.timedelta(minutes=3)).strftime('%Y/%m/%d %H:%M') - }, - invitation=f'openreview.net/Support/-/Request{request_form.number}/ARR_Configuration', - forum=request_form.id, - readers=['aclweb.org/ACL/ARR/2023/August/Program_Chairs', 'openreview.net/Support'], - referent=request_form.id, - replyto=request_form.id, - signatures=['~Program_ARRChair1'], - writers=[], - ) - ) - - helpers.await_queue_edit(openreview_client, 'aclweb.org/ACL/ARR/2023/August/-/Share_Data-0-1', count=2) - - # Find August in readers of groups and registration notes - assert set(pc_client_v2.get_group(june_venue.get_reviewers_id()).members).difference({'~AC_ARROne1', '~SAC_ARROne1'}) == set(pc_client_v2.get_group(august_venue.get_reviewers_id()).members) - assert set(pc_client_v2.get_group(june_venue.get_area_chairs_id()).members).difference({'~SAC_ARROne1'}) == set(pc_client_v2.get_group(august_venue.get_area_chairs_id()).members) - assert set(pc_client_v2.get_group(june_venue.get_senior_area_chairs_id()).members) == set(pc_client_v2.get_group(august_venue.get_senior_area_chairs_id()).members) - assert set(pc_client_v2.get_group(june_venue.get_ethics_reviewers_id()).members) == set(pc_client_v2.get_group(august_venue.get_ethics_reviewers_id()).members) - assert set(pc_client_v2.get_group(june_venue.get_ethics_chairs_id()).members) == set(pc_client_v2.get_group(august_venue.get_ethics_chairs_id()).members) - - june_reviewer_registration_notes = pc_client_v2.get_all_notes(invitation=f"{june_venue.get_reviewers_id()}/-/Registration") - august_reviewer_registration_notes = pc_client_v2.get_all_notes(invitation=f"{august_venue.get_reviewers_id()}/-/Registration") - august_reviewer_signatures = [a.signatures[0] for a in august_reviewer_registration_notes] - assert set(august_reviewer_signatures) == set([ - '~Reviewer_ARRTwo1', - '~Reviewer_Alternate_ARROne1' - ]) - - ## Check that signatures only have 1 from Reviewer 2 - assert '~Reviewer_ARRTwoMerge1' not in august_reviewer_signatures - - june_ac_registration_notes = pc_client_v2.get_all_notes(invitation=f"{june_venue.get_area_chairs_id()}/-/Registration") - august_ac_registration_notes = pc_client_v2.get_all_notes(invitation=f"{august_venue.get_area_chairs_id()}/-/Registration") - august_ac_signatures = [a.signatures[0] for a in august_ac_registration_notes] - assert all(j.signatures[0] in august_ac_signatures for j in june_ac_registration_notes) - - june_sac_registration_notes = pc_client_v2.get_all_notes(invitation=f"{june_venue.get_senior_area_chairs_id()}/-/Registration") - august_sac_registration_notes = pc_client_v2.get_all_notes(invitation=f"{august_venue.get_senior_area_chairs_id()}/-/Registration") - august_sac_signatures = [a.signatures[0] for a in august_sac_registration_notes] - assert all(j.signatures[0] in august_sac_signatures for j in june_sac_registration_notes) - - # Load and check for August in readers of edges - june_reviewers_with_edges = {o['id']['tail']: o['values'][0]['head'] for o in pc_client_v2.get_grouped_edges(invitation=f"{june_venue.get_reviewers_id()}/-/Expertise_Selection", groupby='tail', select='head')} - june_acs_with_edges = {o['id']['tail']: o['values'][0]['head'] for o in pc_client_v2.get_grouped_edges(invitation=f"{june_venue.get_area_chairs_id()}/-/Expertise_Selection", groupby='tail', select='head')} - june_sacs_with_edges = {o['id']['tail']: o['values'][0]['head'] for o in pc_client_v2.get_grouped_edges(invitation=f"{june_venue.get_senior_area_chairs_id()}/-/Expertise_Selection", groupby='tail', select='head')} - - august_reviewers_with_edges = {o['id']['tail']: o['values'][0]['head'] for o in pc_client_v2.get_grouped_edges(invitation=f"{august_venue.get_reviewers_id()}/-/Expertise_Selection", groupby='tail', select='head')} - august_acs_with_edges = {o['id']['tail']: o['values'][0]['head'] for o in pc_client_v2.get_grouped_edges(invitation=f"{august_venue.get_area_chairs_id()}/-/Expertise_Selection", groupby='tail', select='head')} - august_sacs_with_edges = {o['id']['tail']: o['values'][0]['head'] for o in pc_client_v2.get_grouped_edges(invitation=f"{august_venue.get_senior_area_chairs_id()}/-/Expertise_Selection", groupby='tail', select='head')} - - for reviewer, edges in june_reviewers_with_edges.items(): - assert reviewer in august_reviewers_with_edges - assert set(edges) == set(august_reviewers_with_edges[reviewer]) - - for ac, edges in june_acs_with_edges.items(): - assert ac in august_acs_with_edges - assert set(edges) == set(august_acs_with_edges[ac]) - - for sac, edges in june_sacs_with_edges.items(): - assert sac in august_sacs_with_edges - assert set(edges) == set(august_sacs_with_edges[sac]) - - ## Add overlap for deduplication test - assert all(overlap not in openreview_client.get_group(august_venue.get_reviewers_id()).members for overlap in ['~AC_ARROne1', '~SAC_ARROne1']) - assert all(overlap not in openreview_client.get_group(august_venue.get_area_chairs_id()).members for overlap in ['~SAC_ARROne1']) - - # Check reviewer license notes - june_reviewer_license_notes = pc_client_v2.get_all_notes(invitation=f"{june_venue.get_reviewers_id()}/-/License_Agreement") - august_reviewer_license_notes = pc_client_v2.get_all_notes(invitation=f"{august_venue.get_reviewers_id()}/-/License_Agreement") - assert len(june_reviewer_license_notes) > len(august_reviewer_license_notes) ## One June reviewer did not agree - assert '~Reviewer_ARRFour1' in [note.signatures[0] for note in august_reviewer_license_notes] - assert '~Reviewer_ARRFive1' not in [note.signatures[0] for note in august_reviewer_license_notes] - - def test_unavailability_process_functions(self, client, openreview_client, helpers): - # Update the process functions for each of the unavailability forms, set up the custom max papers - # invitations and test that each note posts an edge - - # Load the venues - now = datetime.datetime.now() - pc_client=openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) - pc_client_v2=openreview.api.OpenReviewClient(username='pc@aclrollingreview.org', password=helpers.strong_password) - request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[0] - june_venue = openreview.helpers.get_conference(client, request_form.id, 'openreview.net/Support') - request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] - august_venue = openreview.helpers.get_conference(client, request_form.id, 'openreview.net/Support') - - registration_name = 'Registration' - max_load_name = 'Max_Load_And_Unavailability_Request' - # r1 r3 r4 r5 should have no notes + edges | r2 ac1 sac1 should have notes + edges (unavailable) - migrated_reviewers = {'~Reviewer_ARRTwo1'} - august_reviewer_notes = pc_client_v2.get_all_notes(invitation=f"{august_venue.get_reviewers_id()}/-/{max_load_name}") - assert len(august_reviewer_notes) == len(migrated_reviewers) - assert set([note.signatures[0] for note in august_reviewer_notes]) == migrated_reviewers - assert all(note.content['maximum_load_this_cycle']['value'] == 0 for note in august_reviewer_notes) - - migrated_acs = {'~AC_ARROne1'} - august_ac_notes = pc_client_v2.get_all_notes(invitation=f"{august_venue.get_area_chairs_id()}/-/{max_load_name}") - assert len(august_ac_notes) == len(migrated_acs) - assert set([note.signatures[0] for note in august_ac_notes]) == migrated_acs - assert all(note.content['maximum_load_this_cycle']['value'] == 0 for note in august_ac_notes) - - migrated_sacs = {'~SAC_ARROne1'} - august_sacs_notes = pc_client_v2.get_all_notes(invitation=f"{august_venue.get_senior_area_chairs_id()}/-/{max_load_name}") - assert len(august_sacs_notes) == len(migrated_sacs) - assert set([note.signatures[0] for note in august_sacs_notes]) == migrated_sacs - assert all('will NOT be able to serve' in note.content['availability_this_cycle']['value'] for note in august_sacs_notes) - - august_reviewer_edges = {o['id']['tail']: [j['weight'] for j in o['values']] for o in pc_client_v2.get_grouped_edges(invitation=f"{august_venue.get_reviewers_id()}/-/Custom_Max_Papers", groupby='tail', select='weight')} - august_ac_edges = {o['id']['tail']: [j['weight'] for j in o['values']] for o in pc_client_v2.get_grouped_edges(invitation=f"{august_venue.get_area_chairs_id()}/-/Custom_Max_Papers", groupby='tail', select='weight')} - august_sac_edges = {o['id']['tail']: [j['weight'] for j in o['values']] for o in pc_client_v2.get_grouped_edges(invitation=f"{august_venue.get_senior_area_chairs_id()}/-/Custom_Max_Papers", groupby='tail', select='weight')} - - assert migrated_reviewers == set(august_reviewer_edges.keys()) - assert migrated_acs == set(august_ac_edges.keys()) - assert migrated_sacs == set(august_sac_edges.keys()) - assert all(len(weight_list) == 1 for weight_list in august_reviewer_edges.values()) - assert all(len(weight_list) == 1 for weight_list in august_ac_edges.values()) - assert all(len(weight_list) == 1 for weight_list in august_sac_edges.values()) - assert all( - all(value == 0 for value in weight_list) - for weight_list in august_reviewer_edges.values() - ) - assert all( - all(value == 0 for value in weight_list) - for weight_list in august_ac_edges.values() - ) - assert all( - all(value == 0 for value in weight_list) - for weight_list in august_sac_edges.values() - ) - - # Test posting new notes and finding the edges - ethics_reviewer_client = openreview.api.OpenReviewClient(username = 'reviewerethics@aclrollingreview.com', password=helpers.strong_password) - reviewer_client = openreview.api.OpenReviewClient(username = 'reviewer1@aclrollingreview.com', password=helpers.strong_password) - ac_client = openreview.api.OpenReviewClient(username = 'ac2@aclrollingreview.com', password=helpers.strong_password) - sac_client = openreview.api.OpenReviewClient(username = 'sac2@aclrollingreview.com', password=helpers.strong_password) - - ethics_reviewer_note_edit = ethics_reviewer_client.post_note_edit( - invitation=f'{august_venue.get_ethics_reviewers_id()}/-/{max_load_name}', - signatures=['~EthicsReviewer_ARROne1'], - note=openreview.api.Note( - content = { - 'maximum_load_this_cycle': { 'value': 4 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'No' }, - } - ) - ) - reviewer_note_edit = reviewer_client.post_note_edit( - invitation=f'{august_venue.get_reviewers_id()}/-/{max_load_name}', - signatures=['~Reviewer_Alternate_ARROne1'], - note=openreview.api.Note( - content = { - 'maximum_load_this_cycle': { 'value': 4 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'No' }, - 'meta_data_donation': { 'value': 'Yes, I consent to donating anonymous metadata of my review for research.' }, - } - ) - ) - ac_note_edit = ac_client.post_note_edit( - invitation=f'{august_venue.get_area_chairs_id()}/-/{max_load_name}', - signatures=['~AC_ARRTwo1'], - note=openreview.api.Note( - content = { - 'maximum_load_this_cycle': { 'value': 6 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'Yes' }, - } - ) - ) - sac_note_edit = sac_client.post_note_edit( - invitation=f'{august_venue.get_senior_area_chairs_id()}/-/{max_load_name}', - signatures=['~SAC_ARRTwo1'], - note=openreview.api.Note( - content = { - 'availability_this_cycle': { 'value': 'I confirm that I will serve as SAC in this cycle, with the review load shared equally with other SACs (computed per track in conference-associated cycles).' }, - } - ) - ) - - helpers.await_queue_edit(openreview_client, edit_id=ethics_reviewer_note_edit['id']) - helpers.await_queue_edit(openreview_client, edit_id=reviewer_note_edit['id']) - helpers.await_queue_edit(openreview_client, edit_id=ac_note_edit['id']) - helpers.await_queue_edit(openreview_client, edit_id=sac_note_edit['id']) - - august_ethics_reviewer_edges = {o['id']['tail']: [j['weight'] for j in o['values']] for o in pc_client_v2.get_grouped_edges(invitation=f"{august_venue.get_ethics_reviewers_id()}/-/Custom_Max_Papers", groupby='tail', select='weight')} - august_reviewer_edges = {o['id']['tail']: [j['weight'] for j in o['values']] for o in pc_client_v2.get_grouped_edges(invitation=f"{august_venue.get_reviewers_id()}/-/Custom_Max_Papers", groupby='tail', select='weight')} - august_ac_edges = {o['id']['tail']: [j['weight'] for j in o['values']] for o in pc_client_v2.get_grouped_edges(invitation=f"{august_venue.get_area_chairs_id()}/-/Custom_Max_Papers", groupby='tail', select='weight')} - assert '~EthicsReviewer_ARROne1' in august_ethics_reviewer_edges - assert len(august_ethics_reviewer_edges['~EthicsReviewer_ARROne1']) == 1 and set(august_ethics_reviewer_edges['~EthicsReviewer_ARROne1']) == {4} - assert '~Reviewer_ARROne1' in august_reviewer_edges - assert len(august_reviewer_edges['~Reviewer_ARROne1']) == 1 and set(august_reviewer_edges['~Reviewer_ARROne1']) == {4} - assert '~AC_ARRTwo1' in august_ac_edges - assert len(august_ac_edges['~AC_ARRTwo1']) == 1 and set(august_ac_edges['~AC_ARRTwo1']) == {6} - - # Test editing - ethics_reviewer_note_edit = ethics_reviewer_client.post_note_edit( - invitation=f'{august_venue.get_ethics_reviewers_id()}/-/{max_load_name}', - signatures=['~EthicsReviewer_ARROne1'], - note=openreview.api.Note( - id = ethics_reviewer_note_edit['note']['id'], - content = { - 'maximum_load_this_cycle': { 'value': 5 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'No' }, - } - ) - ) - reviewer_note_edit = reviewer_client.post_note_edit( - invitation=f'{august_venue.get_reviewers_id()}/-/{max_load_name}', - signatures=['~Reviewer_Alternate_ARROne1'], - note=openreview.api.Note( - id = reviewer_note_edit['note']['id'], - content = { - 'maximum_load_this_cycle': { 'value': 5 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'No' }, - 'meta_data_donation': { 'value': 'Yes, I consent to donating anonymous metadata of my review for research.' }, - } - ) - ) - ac_note_edit = ac_client.post_note_edit( - invitation=f'{august_venue.get_area_chairs_id()}/-/{max_load_name}', - signatures=['~AC_ARRTwo1'], - note=openreview.api.Note( - id = ac_note_edit['note']['id'], - content = { - 'maximum_load_this_cycle': { 'value': 7 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'Yes' } - } - ) - ) - - helpers.await_queue_edit(openreview_client, edit_id=ethics_reviewer_note_edit['id']) - helpers.await_queue_edit(openreview_client, edit_id=reviewer_note_edit['id']) - helpers.await_queue_edit(openreview_client, edit_id=ac_note_edit['id']) - - august_ethics_reviewer_edges = {o['id']['tail']: [j['weight'] for j in o['values']] for o in pc_client_v2.get_grouped_edges(invitation=f"{august_venue.get_ethics_reviewers_id()}/-/Custom_Max_Papers", groupby='tail', select='weight')} - august_reviewer_edges = {o['id']['tail']: [j['weight'] for j in o['values']] for o in pc_client_v2.get_grouped_edges(invitation=f"{august_venue.get_reviewers_id()}/-/Custom_Max_Papers", groupby='tail', select='weight')} - august_ac_edges = {o['id']['tail']: [j['weight'] for j in o['values']] for o in pc_client_v2.get_grouped_edges(invitation=f"{august_venue.get_area_chairs_id()}/-/Custom_Max_Papers", groupby='tail', select='weight')} - assert '~EthicsReviewer_ARROne1' in august_ethics_reviewer_edges - assert len(august_ethics_reviewer_edges['~EthicsReviewer_ARROne1']) == 1 and set(august_ethics_reviewer_edges['~EthicsReviewer_ARROne1']) == {5} - assert '~Reviewer_ARROne1' in august_reviewer_edges - assert len(august_reviewer_edges['~Reviewer_ARROne1']) == 1 and set(august_reviewer_edges['~Reviewer_ARROne1']) == {5} - assert '~AC_ARRTwo1' in august_ac_edges - assert len(august_ac_edges['~AC_ARRTwo1']) == 1 and set(august_ac_edges['~AC_ARRTwo1']) == {7} - - # Test deleting - ethics_reviewer_note_edit = ethics_reviewer_client.post_note_edit( - invitation=f'{august_venue.get_ethics_reviewers_id()}/-/{max_load_name}', - signatures=['~EthicsReviewer_ARROne1'], - note=openreview.api.Note( - id = ethics_reviewer_note_edit['note']['id'], - ddate = openreview.tools.datetime_millis(now), - content = { - 'maximum_load_this_cycle': { 'value': 5 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'No' }, - } - ) - ) - reviewer_note_edit = reviewer_client.post_note_edit( - invitation=f'{august_venue.get_reviewers_id()}/-/{max_load_name}', - signatures=['~Reviewer_Alternate_ARROne1'], - note=openreview.api.Note( - id = reviewer_note_edit['note']['id'], - ddate = openreview.tools.datetime_millis(now), - content = { - 'maximum_load_this_cycle': { 'value': 5 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'No' }, - 'meta_data_donation': { 'value': 'Yes, I consent to donating anonymous metadata of my review for research.' }, - } - ) - ) - ac_note_edit = ac_client.post_note_edit( - invitation=f'{august_venue.get_area_chairs_id()}/-/{max_load_name}', - signatures=['~AC_ARRTwo1'], - note=openreview.api.Note( - id = ac_note_edit['note']['id'], - ddate = openreview.tools.datetime_millis(now), - content = { - 'maximum_load_this_cycle': { 'value': 7 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'Yes' } - } - ) - ) - helpers.await_queue_edit(openreview_client, edit_id=ethics_reviewer_note_edit['id']) - helpers.await_queue_edit(openreview_client, edit_id=reviewer_note_edit['id']) - helpers.await_queue_edit(openreview_client, edit_id=ac_note_edit['id']) - - august_ethics_reviewer_edges = {o['id']['tail']: [j['weight'] for j in o['values']] for o in pc_client_v2.get_grouped_edges(invitation=f"{august_venue.get_ethics_reviewers_id()}/-/Custom_Max_Papers", groupby='tail', select='weight')} - august_reviewer_edges = {o['id']['tail']: [j['weight'] for j in o['values']] for o in pc_client_v2.get_grouped_edges(invitation=f"{august_venue.get_reviewers_id()}/-/Custom_Max_Papers", groupby='tail', select='weight')} - august_ac_edges = {o['id']['tail']: [j['weight'] for j in o['values']] for o in pc_client_v2.get_grouped_edges(invitation=f"{august_venue.get_area_chairs_id()}/-/Custom_Max_Papers", groupby='tail', select='weight')} - assert '~EthicsReviewer_ARROne1' not in august_ethics_reviewer_edges - assert '~Reviewer_ARROne1' not in august_reviewer_edges - assert '~AC_ARRTwo1' not in august_ac_edges - - # Set data for resubmission unavailability - reviewer_five_client = openreview.api.OpenReviewClient(username = 'reviewer5@aclrollingreview.com', password=helpers.strong_password) - ac_three_client = openreview.api.OpenReviewClient(username = 'ac3@aclrollingreview.com', password=helpers.strong_password) - - reviewer_five_client.post_note_edit( - invitation=f'{august_venue.get_reviewers_id()}/-/{max_load_name}', - signatures=['~Reviewer_ARRFive1'], - note=openreview.api.Note( - content = { - 'maximum_load_this_cycle': { 'value': 0 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'Yes' }, - 'meta_data_donation': { 'value': 'Yes, I consent to donating anonymous metadata of my review for research.' }, - } - ) - ) - ac_three_client.post_note_edit( - invitation=f'{august_venue.get_area_chairs_id()}/-/{max_load_name}', - signatures=['~AC_ARRThree1'], - note=openreview.api.Note( - content = { - 'maximum_load_this_cycle': { 'value': 0 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'Yes' } - } - ) - ) - - # Increase load again for AC2 - ac_note_edit = ac_client.post_note_edit( - invitation=f'{august_venue.get_area_chairs_id()}/-/{max_load_name}', - signatures=['~AC_ARRTwo1'], - note=openreview.api.Note( - content = { - 'maximum_load_this_cycle': { 'value': 6 }, - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'Yes' } - } - ) - ) def test_reviewer_tasks(self, client, openreview_client, helpers): reviewer_client = openreview.api.OpenReviewClient(username = 'reviewer1@aclrollingreview.com', password=helpers.strong_password) @@ -3118,13 +2609,148 @@ def test_metadata_edit(self, client, openreview_client, helpers, test_client, re ) def test_setup_matching(self, client, openreview_client, helpers, test_client, request_page, selenium): - pc_client=openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) pc_client_v2=openreview.api.OpenReviewClient(username='pc@aclrollingreview.org', password=helpers.strong_password) request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] august_venue = openreview.helpers.get_conference(client, request_form.id, 'openreview.net/Support') test_client = openreview.api.OpenReviewClient(token=test_client.token) + # Preserve state of maximum load notes from before the migration + openreview_client.post_note_edit( + invitation = "aclweb.org/ACL/ARR/Reviewers/-/Max_Load_And_Unavailability_Request", + signatures = ['~Reviewer_ARRFive1'], + note = openreview.api.Note( + content = { + "maximum_load_this_cycle": { + "value": 0 + }, + "maximum_load_this_cycle_for_resubmissions": { + "value": "Yes" + }, + "meta_data_donation": { + "value": "Yes, I consent to donating anonymous metadata of my review for research." + } + } + ) + ) + openreview_client.post_note_edit( + invitation = "aclweb.org/ACL/ARR/Reviewers/-/Max_Load_And_Unavailability_Request", + signatures = ['~Reviewer_ARRTwo1'], + note = openreview.api.Note( + content = { + "maximum_load_this_cycle": { + "value": 0 + }, + "maximum_load_this_cycle_for_resubmissions": { + "value": "No" + }, + "meta_data_donation": { + "value": "Yes, I consent to donating anonymous metadata of my review for research." + } + } + ) + ) + openreview_client.post_note_edit( + invitation = "aclweb.org/ACL/ARR/Area_Chairs/-/Max_Load_And_Unavailability_Request", + signatures = ['~AC_ARRTwo1'], + note = openreview.api.Note( + content = { + "maximum_load_this_cycle": { + "value": 6 + }, + "maximum_load_this_cycle_for_resubmissions": { + "value": "Yes" + } + } + ) + ) + openreview_client.post_note_edit( + invitation = "aclweb.org/ACL/ARR/Area_Chairs/-/Max_Load_And_Unavailability_Request", + signatures = ['~AC_ARRThree1'], + note = openreview.api.Note( + content = { + "maximum_load_this_cycle": { + "value": 0 + }, + "maximum_load_this_cycle_for_resubmissions": { + "value": "Yes" + } + } + ) + ) + openreview_client.post_note_edit( + invitation = "aclweb.org/ACL/ARR/Area_Chairs/-/Max_Load_And_Unavailability_Request", + signatures = ['~AC_ARROne1'], + note = openreview.api.Note( + content = { + "maximum_load_this_cycle": { + "value": 0 + }, + "maximum_load_this_cycle_for_resubmissions": { + "value": "No" + } + } + ) + ) + openreview_client.post_note_edit( ## SAC should be available + invitation="aclweb.org/ACL/ARR/Senior_Area_Chairs/-/Max_Load_And_Unavailability_Request", + signatures=['~SAC_ARRTwo1'], + note=openreview.api.Note( + content = { + 'availability_this_cycle': { 'value': "I confirm that I will serve as SAC in this cycle, with the review load shared equally with other SACs (computed per track in conference-associated cycles)." } + } + ) + ) + + # Post expertise edges + user_client = openreview.api.OpenReviewClient(username='reviewer1@aclrollingreview.com', password=helpers.strong_password) + archive_note = user_client.post_note_edit( + invitation='openreview.net/Archive/-/Direct_Upload', + signatures=['~Reviewer_Alternate_ARROne1'], + note = openreview.api.Note( + pdate = openreview.tools.datetime_millis(datetime.datetime(2019, 4, 30)), + content = { + 'title': { 'value': 'Paper title 2' }, + 'abstract': { 'value': 'Paper abstract 2' }, + 'authors': { 'value': ['Reviewer ARR', 'Test2 Client'] }, + 'authorids': { 'value': ['~Reviewer_Alternate_ARROne1', 'test2@mail.com'] }, + 'venue': { 'value': 'Arxiv' } + }, + license = 'CC BY-SA 4.0' + )) + user_client.post_edge( + openreview.api.Edge( + invitation = august_venue.get_expertise_selection_id(committee_id = august_venue.get_reviewers_id()), + readers = [august_venue.id, '~Reviewer_ARROne1'], + writers = [august_venue.id, '~Reviewer_ARROne1'], + signatures = ['~Reviewer_ARROne1'], + head = archive_note['note']['id'], + tail = '~Reviewer_ARROne1', + label = 'Exclude' + )) + user_client = openreview.api.OpenReviewClient(username='ac1@aclrollingreview.com', password=helpers.strong_password) + user_client.post_edge( + openreview.api.Edge( + invitation = august_venue.get_expertise_selection_id(committee_id = august_venue.get_area_chairs_id()), + readers = [august_venue.id, '~AC_ARROne1'], + writers = [august_venue.id, '~AC_ARROne1'], + signatures = ['~AC_ARROne1'], + head = archive_note['note']['id'], + tail = '~AC_ARROne1', + label = 'Exclude' + )) + user_client = openreview.api.OpenReviewClient(username='sac1@aclrollingreview.com', password=helpers.strong_password) + user_client.post_edge( + openreview.api.Edge( + invitation = august_venue.get_expertise_selection_id(committee_id = august_venue.get_senior_area_chairs_id()), + readers = [august_venue.id, '~SAC_ARROne1'], + writers = [august_venue.id, '~SAC_ARROne1'], + signatures = ['~SAC_ARROne1'], + head = archive_note['note']['id'], + tail = '~SAC_ARROne1', + label = 'Exclude' + )) + # Create review stages now = datetime.datetime.now() due_date = now + datetime.timedelta(days=3) @@ -3272,12 +2898,13 @@ def test_setup_matching(self, client, openreview_client, helpers, test_client, r assert openreview_client.get_invitation('aclweb.org/ACL/ARR/2023/August/Reviewers/-/Conflict') - assert openreview_client.get_edges_count(invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Conflict') == 14 # All 7 reviewers will conflict with submissions 1/101 because of domain of SAC + assert openreview_client.get_edges_count(invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Conflict') == 16 # All 8 reviewers will conflict with submissions 1/101 because of domain of SAC ## Extra 101 conflicts from new reviewer which is an author of all submissions + # NOTE: All reviewers included, no excluding affinity_scores = openreview_client.get_grouped_edges(invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Affinity_Score', groupby='id') assert affinity_scores - assert len(affinity_scores) == 101 * 7 ## submissions * reviewers + assert len(affinity_scores) == 101 * 8 ## submissions * reviewers # Post assignment configuration notes openreview_client.post_note_edit( @@ -3386,7 +3013,7 @@ def test_setup_matching(self, client, openreview_client, helpers, test_client, r openreview.tools.post_bulk_edges(openreview_client, ac_edges_to_post) assert openreview_client.get_edges_count(invitation='aclweb.org/ACL/ARR/2023/August/Area_Chairs/-/Aggregate_Score', label='ae-assignments') == 101 * 3 - assert openreview_client.get_edges_count(invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Aggregate_Score', label='reviewer-assignments') == 101 * 7 + assert openreview_client.get_edges_count(invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Aggregate_Score', label='reviewer-assignments') == 101 * 8 def test_resubmission_and_track_matching_data(self, client, openreview_client, helpers, test_client, request_page, selenium): @@ -6062,10 +5689,11 @@ def test_email_options(self, client, openreview_client, helpers, test_client, re helpers.create_user('reviewer7@aclrollingreview.com', 'Reviewer', 'ARRSeven') helpers.create_user('reviewer8@aclrollingreview.com', 'Reviewer', 'ARREight') openreview_client.add_members_to_group('aclweb.org/ACL/ARR/2023/August/Reviewers', ['~Reviewer_ARRSeven1', '~Reviewer_ARREight1']) + openreview_client.add_members_to_group('aclweb.org/ACL/ARR/Reviewers', ['~Reviewer_ARRSeven1', '~Reviewer_ARREight1']) rev_client = openreview.api.OpenReviewClient(username = 'reviewer7@aclrollingreview.com', password=helpers.strong_password) rev_two_client = openreview.api.OpenReviewClient(username = 'reviewer2@aclrollingreview.com', password=helpers.strong_password) rev_client.post_note_edit( - invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Max_Load_And_Unavailability_Request', + invitation='aclweb.org/ACL/ARR/Reviewers/-/Max_Load_And_Unavailability_Request', signatures=['~Reviewer_ARRSeven1'], note=openreview.api.Note( content = { @@ -6076,7 +5704,7 @@ def test_email_options(self, client, openreview_client, helpers, test_client, re ) ) rev_client.post_note_edit( - invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Emergency_Reviewer_Agreement', + invitation='aclweb.org/ACL/ARR/Reviewers/-/Emergency_Reviewer_Agreement', signatures=['~Reviewer_ARRSeven1'], note=openreview.api.Note( content = { @@ -6088,7 +5716,7 @@ def test_email_options(self, client, openreview_client, helpers, test_client, re ) rev_client = openreview.api.OpenReviewClient(username = 'reviewer8@aclrollingreview.com', password=helpers.strong_password) rev_client.post_note_edit( - invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Max_Load_And_Unavailability_Request', + invitation='aclweb.org/ACL/ARR/Reviewers/-/Max_Load_And_Unavailability_Request', signatures=['~Reviewer_ARREight1'], note=openreview.api.Note( content = { @@ -6100,19 +5728,19 @@ def test_email_options(self, client, openreview_client, helpers, test_client, re ) # Update reviewer two's fields to cover more cases - load_note = rev_two_client.get_all_notes(invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Max_Load_And_Unavailability_Request')[0] + load_note = rev_two_client.get_all_notes(invitation='aclweb.org/ACL/ARR/Reviewers/-/Max_Load_And_Unavailability_Request')[0] openreview_client.post_note_edit( - invitation='aclweb.org/ACL/ARR/2023/August/-/Edit', - readers=['aclweb.org/ACL/ARR/2023/August'], - writers=['aclweb.org/ACL/ARR/2023/August'], - signatures=['aclweb.org/ACL/ARR/2023/August'], + invitation='aclweb.org/ACL/ARR/-/Edit', + readers=['aclweb.org/ACL/ARR'], + writers=['aclweb.org/ACL/ARR'], + signatures=['aclweb.org/ACL/ARR'], note=openreview.api.Note( id=load_note.id, ddate=now_millis, ) ) rev_two_client.post_note_edit( - invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Max_Load_And_Unavailability_Request', + invitation='aclweb.org/ACL/ARR/Reviewers/-/Max_Load_And_Unavailability_Request', signatures=['~Reviewer_ARRTwo1'], note=openreview.api.Note( content = { @@ -6123,7 +5751,7 @@ def test_email_options(self, client, openreview_client, helpers, test_client, re ) ) rev_two_client.post_note_edit( - invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Emergency_Reviewer_Agreement', + invitation='aclweb.org/ACL/ARR/Reviewers/-/Emergency_Reviewer_Agreement', signatures=['~Reviewer_ARRTwo1'], note=openreview.api.Note( content = { From a9044ce7b9fc4be5f6c101c96ffca847f5a16bd7 Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Mon, 8 Dec 2025 14:56:00 +0100 Subject: [PATCH 05/20] Remove updating EICs with PCs --- openreview/arr/arr.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/openreview/arr/arr.py b/openreview/arr/arr.py index 131006aeae..acc38f564d 100644 --- a/openreview/arr/arr.py +++ b/openreview/arr/arr.py @@ -369,13 +369,6 @@ def setup(self, program_chair_ids=[], publication_chairs_ids=[]): # Initialize root-level ARR groups if not populated setup_arr_root_groups(self.client, self.support_user) - # Add missing program chairs missing from the EIC group - members = self.client.get_group(f'{ROOT_DOMAIN}/Editors_In_Chief').members - for program_chair_id in program_chair_ids: - if program_chair_id not in members: - members.append(program_chair_id) - self.client.add_members_to_group(f'{ROOT_DOMAIN}/Editors_In_Chief', program_chair_ids) - # Synchronize groups for role in ARR_ROLE_NAMES: # skip EIC group From 4da98d9d66ea5d45ec91366880b38eb765ce7c3e Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Mon, 8 Dec 2025 14:57:06 +0100 Subject: [PATCH 06/20] Remove propagation scripts --- openreview/arr/helpers.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/openreview/arr/helpers.py b/openreview/arr/helpers.py index 25a3940f86..6519898077 100644 --- a/openreview/arr/helpers.py +++ b/openreview/arr/helpers.py @@ -2158,18 +2158,6 @@ def setup_arr_root_groups(client, support_user): if meta_invitation is None: # Get process content from venue module - process_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'venue', 'process') - invitation_edit_process_path = os.path.join(process_dir, 'invitation_edit_process.py') - group_edit_process_path = os.path.join(process_dir, 'group_edit_process.py') - - invitation_edit_script = '' - group_edit_script = '' - - with open(invitation_edit_process_path) as f: - invitation_edit_script = f.read() - - with open(group_edit_process_path) as f: - group_edit_script = f.read() client.post_invitation_edit( invitations=None, @@ -2181,14 +2169,6 @@ def setup_arr_root_groups(client, support_user): invitees=[domain], readers=[domain], signatures=['~Super_User1'], - content={ - 'invitation_edit_script': { - 'value': invitation_edit_script - }, - 'group_edit_script': { - 'value': group_edit_script - } - }, edit=True ) ) From bd24c21ae7912f659562acab004d8cfb9a10a98f Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Mon, 8 Dec 2025 22:14:54 +0100 Subject: [PATCH 07/20] Respond to feedback --- openreview/arr/arr.py | 17 +++ .../arr/management/setup_ae_matching.py | 120 +++++++-------- .../arr/management/setup_reviewer_matching.py | 140 +++++++++--------- .../arr/management/setup_sae_matching.py | 54 ++++--- tests/test_arr_venue_v2.py | 5 +- 5 files changed, 170 insertions(+), 166 deletions(-) diff --git a/openreview/arr/arr.py b/openreview/arr/arr.py index acc38f564d..77ccc9246f 100644 --- a/openreview/arr/arr.py +++ b/openreview/arr/arr.py @@ -366,6 +366,23 @@ def expire_invitation(self, invitation_id): def setup(self, program_chair_ids=[], publication_chairs_ids=[]): setup_value = self.venue.setup(program_chair_ids, publication_chairs_ids) + # Add root role names to domain content + cycle_roles = [r for r in ARR_ROLE_NAMES if r != 'Editors_In_Chief'] + root_content = {} + for role in cycle_roles: + key = f"root_{role.lower()}_id" + root_content[key] = { 'value': f'{ROOT_DOMAIN}/{role}' } + self.client.post_group_edit( + invitation=self.get_meta_invitation_id(), + readers=[self.venue_id], + writers=[self.venue_id], + signatures=[self.venue_id], + group=openreview.api.Group( + id=self.venue_id, + content=root_content + ) + ) + # Initialize root-level ARR groups if not populated setup_arr_root_groups(self.client, self.support_user) diff --git a/openreview/arr/management/setup_ae_matching.py b/openreview/arr/management/setup_ae_matching.py index 567b3be4a7..71223b33e2 100644 --- a/openreview/arr/management/setup_ae_matching.py +++ b/openreview/arr/management/setup_ae_matching.py @@ -106,51 +106,45 @@ def replace_edge(existing_edge=None, edge_inv=None, new_weight=None, submission_ name_to_id[name_obj['username']] = profile.id # Build load map - print(f"num profiles {len(all_profiles)}") id_to_load_note = {} - root_role_id = f"{ROOT_DOMAIN}/{area_chairs_name}" - for role_id in [root_role_id]: - load_notes = client.get_all_notes(invitation=f"{role_id}/-/{max_load_name}") ## Assume only 1 note per user - for note in load_notes: - if note.signatures[0] not in name_to_id: - continue - note_signature_id = name_to_id[note.signatures[0]] - id_to_load_note[note_signature_id] = note + root_role_id = domain.content['root_area_chairs_id']['value'] + load_notes = client.get_all_notes(invitation=f"{root_role_id}/-/{max_load_name}") ## Assume only 1 note per user + for note in load_notes: + if note.signatures[0] not in name_to_id: + continue + note_signature_id = name_to_id[note.signatures[0]] + id_to_load_note[note_signature_id] = note # Build track map track_to_ids = {} - for role_id in [area_chairs_id]: - track_to_ids[role_id] = defaultdict(set) - registration_notes = client.get_all_notes(invitation=f"{root_role_id}/-/{registration_name}") - for note in registration_notes: - if note.signatures[0] not in name_to_id: - continue - note_signature_id = name_to_id[note.signatures[0]] - for track in note.content[tracks_field_name]['value']: - track_to_ids[role_id][track].add(note_signature_id) + track_to_ids[area_chairs_id] = defaultdict(set) + registration_notes = client.get_all_notes(invitation=f"{root_role_id}/-/{registration_name}") + for note in registration_notes: + if note.signatures[0] not in name_to_id: + continue + note_signature_id = name_to_id[note.signatures[0]] + for track in note.content[tracks_field_name]['value']: + track_to_ids[area_chairs_id][track].add(note_signature_id) # Build research area invitation - matching.Matching(venue, client.get_group(role_id), None)._create_edge_invitation( - edge_id=f"{role_id}/-/{tracks_inv_name}" + matching.Matching(venue, client.get_group(area_chairs_id), None)._create_edge_invitation( + edge_id=f"{area_chairs_id}/-/{tracks_inv_name}" ) track_edge_readers = { area_chairs_id: [venue_id, senior_area_chairs_id] } # Reset custom max papers to ground truth notes - for role_id in [area_chairs_id]: - cmp_to_post = [] - role_cmp_inv = f"{role_id}/-/Custom_Max_Papers" - print(f"num of notes {len(id_to_load_note)}") - for id, note in id_to_load_note.items(): - - cmp_to_post.append( - openreview.api.Edge( - invitation=role_cmp_inv, - head=role_id, + cmp_to_post = [] + role_cmp_inv = f"{area_chairs_id}/-/Custom_Max_Papers" + for id, note in id_to_load_note.items(): + cmp_to_post.append( + openreview.api.Edge( + invitation=role_cmp_inv, + head=area_chairs_id, tail=id, weight=int(note.content['maximum_load_this_cycle']['value']), - readers=track_edge_readers[role_id] + [id], + readers=track_edge_readers[area_chairs_id] + [id], writers=[venue_id], signatures=[venue_id] ) @@ -164,42 +158,40 @@ def replace_edge(existing_edge=None, edge_inv=None, new_weight=None, submission_ openreview.tools.post_bulk_edges(client=client, edges=cmp_to_post) # Create Reviewing_Resubmissions edges from root domain load notes - for role_id in [area_chairs_id]: - resubmissions_to_post = [] - role_resubmissions_inv = f"{role_id}/-/Reviewing_Resubmissions" + resubmissions_to_post = [] + role_resubmissions_inv = f"{area_chairs_id}/-/Reviewing_Resubmissions" + for id, note in id_to_load_note.items(): + max_load = int(note.content['maximum_load_this_cycle']['value']) + for_resubmissions = note.content.get('maximum_load_this_cycle_for_resubmissions', {}).get('value', '') - for id, note in id_to_load_note.items(): - max_load = int(note.content['maximum_load_this_cycle']['value']) - for_resubmissions = note.content.get('maximum_load_this_cycle_for_resubmissions', {}).get('value', '') - - availability_label = None - if 'yes' in for_resubmissions.lower() and max_load == 0: - availability_label = 'Only Reviewing Resubmissions' - elif 'yes' in for_resubmissions.lower(): - availability_label = 'Yes' - elif 'no' in for_resubmissions.lower(): - availability_label = 'No' - - if availability_label: - resubmissions_to_post.append( - openreview.api.Edge( - invitation=role_resubmissions_inv, - head=role_id, - tail=id, - label=availability_label, - readers=track_edge_readers[role_id] + [id], - writers=[venue_id], - signatures=[venue_id] - ) - ) + availability_label = None + if 'yes' in for_resubmissions.lower() and max_load == 0: + availability_label = 'Only Reviewing Resubmissions' + elif 'yes' in for_resubmissions.lower(): + availability_label = 'Yes' + elif 'no' in for_resubmissions.lower(): + availability_label = 'No' - client.delete_edges( - invitation=role_resubmissions_inv, - soft_delete=True, - wait_to_finish=True - ) - openreview.tools.post_bulk_edges(client=client, edges=resubmissions_to_post) + if availability_label: + resubmissions_to_post.append( + openreview.api.Edge( + invitation=role_resubmissions_inv, + head=area_chairs_id, + tail=id, + label=availability_label, + readers=track_edge_readers[area_chairs_id] + [id], + writers=[venue_id], + signatures=[venue_id] + ) + ) + + client.delete_edges( + invitation=role_resubmissions_inv, + soft_delete=True, + wait_to_finish=True + ) + openreview.tools.post_bulk_edges(client=client, edges=resubmissions_to_post) ae_exceptions = {} for submission in resubmissions: diff --git a/openreview/arr/management/setup_reviewer_matching.py b/openreview/arr/management/setup_reviewer_matching.py index b240912af6..ec3a1778d0 100644 --- a/openreview/arr/management/setup_reviewer_matching.py +++ b/openreview/arr/management/setup_reviewer_matching.py @@ -182,30 +182,28 @@ def replace_edge(existing_edge=None, edge_inv=None, new_weight=None, submission_ # Build load map id_to_load_note = {} - root_role_id = f"{ROOT_DOMAIN}/{reviewers_name}" - for role_id in [root_role_id]: - load_notes = client.get_all_notes(invitation=f"{role_id}/-/{max_load_name}") ## Assume only 1 note per user - for note in load_notes: - if note.signatures[0] not in name_to_id: - continue - note_signature_id = name_to_id[note.signatures[0]] - id_to_load_note[note_signature_id] = note + root_role_id = domain.content['root_reviewers_id']['value'] + load_notes = client.get_all_notes(invitation=f"{root_role_id}/-/{max_load_name}") ## Assume only 1 note per user + for note in load_notes: + if note.signatures[0] not in name_to_id: + continue + note_signature_id = name_to_id[note.signatures[0]] + id_to_load_note[note_signature_id] = note # Build track map track_to_ids = {} - for role_id in [reviewers_id]: - track_to_ids[role_id] = defaultdict(set) - registration_notes = client.get_all_notes(invitation=f"{root_role_id}/-/{registration_name}") - for note in registration_notes: - if note.signatures[0] not in name_to_id: - continue - note_signature_id = name_to_id[note.signatures[0]] - for track in note.content[tracks_field_name]['value']: - track_to_ids[role_id][track].add(note_signature_id) + track_to_ids[reviewers_id] = defaultdict(set) + registration_notes = client.get_all_notes(invitation=f"{root_role_id}/-/{registration_name}") + for note in registration_notes: + if note.signatures[0] not in name_to_id: + continue + note_signature_id = name_to_id[note.signatures[0]] + for track in note.content[tracks_field_name]['value']: + track_to_ids[reviewers_id][track].add(note_signature_id) # Build research area invitation matching.Matching(venue, client.get_group(role_id), None)._create_edge_invitation( - edge_id=f"{role_id}/-/{tracks_inv_name}" + edge_id=f"{reviewers_id}/-/{tracks_inv_name}" ) track_edge_readers = { reviewers_id: [venue_id, senior_area_chairs_id, area_chairs_id] @@ -237,65 +235,63 @@ def replace_edge(existing_edge=None, edge_inv=None, new_weight=None, submission_ ) # Reset custom max papers to ground truth notes - for role_id in [reviewers_id]: - cmp_to_post = [] - role_cmp_inv = f"{role_id}/-/Custom_Max_Papers" - for id, note in id_to_load_note.items(): + cmp_to_post = [] + role_cmp_inv = f"{reviewers_id}/-/Custom_Max_Papers" + for id, note in id_to_load_note.items(): - cmp_to_post.append( - openreview.api.Edge( - invitation=role_cmp_inv, - head=role_id, - tail=id, - weight=int(note.content['maximum_load_this_cycle']['value']), - readers=track_edge_readers[role_id] + [id], - writers=[venue_id], - signatures=[venue_id] - ) + cmp_to_post.append( + openreview.api.Edge( + invitation=role_cmp_inv, + head=reviewers_id, + tail=id, + weight=int(note.content['maximum_load_this_cycle']['value']), + readers=track_edge_readers[reviewers_id] + [id], + writers=[venue_id], + signatures=[venue_id] ) - client.delete_edges( - invitation=role_cmp_inv, - soft_delete=True, - wait_to_finish=True ) - openreview.tools.post_bulk_edges(client=client, edges=cmp_to_post) + client.delete_edges( + invitation=role_cmp_inv, + soft_delete=True, + wait_to_finish=True + ) + openreview.tools.post_bulk_edges(client=client, edges=cmp_to_post) # Create Reviewing_Resubmissions edges from root domain load notes - for role_id in [reviewers_id]: - resubmissions_to_post = [] - role_resubmissions_inv = f"{role_id}/-/Reviewing_Resubmissions" + resubmissions_to_post = [] + role_resubmissions_inv = f"{reviewers_id}/-/Reviewing_Resubmissions" + + for id, note in id_to_load_note.items(): + max_load = int(note.content['maximum_load_this_cycle']['value']) + for_resubmissions = note.content.get('maximum_load_this_cycle_for_resubmissions', {}).get('value', '') - for id, note in id_to_load_note.items(): - max_load = int(note.content['maximum_load_this_cycle']['value']) - for_resubmissions = note.content.get('maximum_load_this_cycle_for_resubmissions', {}).get('value', '') - - availability_label = None - if 'yes' in for_resubmissions.lower() and max_load == 0: - availability_label = 'Only Reviewing Resubmissions' - elif 'yes' in for_resubmissions.lower(): - availability_label = 'Yes' - elif 'no' in for_resubmissions.lower(): - availability_label = 'No' - - if availability_label: - resubmissions_to_post.append( - openreview.api.Edge( - invitation=role_resubmissions_inv, - head=role_id, - tail=id, - label=availability_label, - readers=track_edge_readers[role_id] + [id], - writers=[venue_id], - signatures=[venue_id] - ) - ) + availability_label = None + if 'yes' in for_resubmissions.lower() and max_load == 0: + availability_label = 'Only Reviewing Resubmissions' + elif 'yes' in for_resubmissions.lower(): + availability_label = 'Yes' + elif 'no' in for_resubmissions.lower(): + availability_label = 'No' - client.delete_edges( - invitation=role_resubmissions_inv, - soft_delete=True, - wait_to_finish=True - ) - openreview.tools.post_bulk_edges(client=client, edges=resubmissions_to_post) + if availability_label: + resubmissions_to_post.append( + openreview.api.Edge( + invitation=role_resubmissions_inv, + head=reviewers_id, + tail=id, + label=availability_label, + readers=track_edge_readers[reviewers_id] + [id], + writers=[venue_id], + signatures=[venue_id] + ) + ) + + client.delete_edges( + invitation=role_resubmissions_inv, + soft_delete=True, + wait_to_finish=True + ) + openreview.tools.post_bulk_edges(client=client, edges=resubmissions_to_post) # Handle Ethics Reviewers Custom_Max_Papers (no Reviewing_Resubmissions for ethics reviewers) ethics_reviewers_name = domain.content['ethics_reviewers_name']['value'] @@ -303,7 +299,7 @@ def replace_edge(existing_edge=None, edge_inv=None, new_weight=None, submission_ domain.content['ethics_chairs_name']['value'], ethics_reviewers_name ) - root_ethics_role_id = f"{ROOT_DOMAIN}/{ethics_reviewers_name}" + root_ethics_reviewers_id = domain.content['root_ethics_reviewers_id']['value'] # Build ethics load map ethics_id_to_load_note = {} @@ -317,7 +313,7 @@ def replace_edge(existing_edge=None, edge_inv=None, new_weight=None, submission_ for name_obj in filtered_names: ethics_name_to_id[name_obj['username']] = profile.id - ethics_load_notes = client.get_all_notes(invitation=f"{root_ethics_role_id}/-/{max_load_name}") + ethics_load_notes = client.get_all_notes(invitation=f"{root_ethics_reviewers_id}/-/{max_load_name}") for note in ethics_load_notes: if note.signatures[0] not in ethics_name_to_id: continue diff --git a/openreview/arr/management/setup_sae_matching.py b/openreview/arr/management/setup_sae_matching.py index dc3c7f08d6..f4799766ee 100644 --- a/openreview/arr/management/setup_sae_matching.py +++ b/openreview/arr/management/setup_sae_matching.py @@ -63,37 +63,35 @@ def process(client, invitation): # Build load map id_to_load_note = {} - root_role_id = f"{ROOT_DOMAIN}/{senior_area_chairs_name}" - for role_id in [root_role_id]: - load_notes = client.get_all_notes(invitation=f"{role_id}/-/{max_load_name}") ## Assume only 1 note per user - for note in load_notes: - if note.signatures[0] not in name_to_id: - continue - note_signature_id = name_to_id[note.signatures[0]] - id_to_load_note[note_signature_id] = note + root_role_id = domain.content['root_senior_area_chairs_id']['value'] + load_notes = client.get_all_notes(invitation=f"{root_role_id}/-/{max_load_name}") ## Assume only 1 note per user + for note in load_notes: + if note.signatures[0] not in name_to_id: + continue + note_signature_id = name_to_id[note.signatures[0]] + id_to_load_note[note_signature_id] = note # Build track map track_to_ids = {} - for role_id in [senior_area_chairs_id]: - track_to_ids[role_id] = defaultdict(set) - registration_notes = client.get_all_notes(invitation=f"{root_role_id}/-/{registration_name}") - for note in registration_notes: - if note.signatures[0] not in name_to_id: - continue - - note_signature_id = name_to_id[note.signatures[0]] - load_note = id_to_load_note.get(note_signature_id) - ## Don't add to tracks if no note available or not available, will create ghost loads - if load_note is not None: - availability_string = load_note.content.get('availability_this_cycle', {}).get('value', 'will NOT be able to serve') ## Assume default not available - if 'I confirm that I will serve' in availability_string: - for track in note.content[tracks_field_name]['value']: - track_to_ids[role_id][track].add(note_signature_id) - - # Build research area invitation - matching.Matching(venue, client.get_group(role_id), None)._create_edge_invitation( - edge_id=f"{role_id}/-/{tracks_inv_name}" - ) + track_to_ids[senior_area_chairs_id] = defaultdict(set) + registration_notes = client.get_all_notes(invitation=f"{root_role_id}/-/{registration_name}") + for note in registration_notes: + if note.signatures[0] not in name_to_id: + continue + + note_signature_id = name_to_id[note.signatures[0]] + load_note = id_to_load_note.get(note_signature_id) + ## Don't add to tracks if no note available or not available, will create ghost loads + if load_note is not None: + availability_string = load_note.content.get('availability_this_cycle', {}).get('value', 'will NOT be able to serve') ## Assume default not available + if 'I confirm that I will serve' in availability_string: + for track in note.content[tracks_field_name]['value']: + track_to_ids[senior_area_chairs_id][track].add(note_signature_id) + + # Build research area invitation + matching.Matching(venue, client.get_group(senior_area_chairs_id), None)._create_edge_invitation( + edge_id=f"{senior_area_chairs_id}/-/{tracks_inv_name}" + ) track_edge_readers = { senior_area_chairs_id: [venue_id] } diff --git a/tests/test_arr_venue_v2.py b/tests/test_arr_venue_v2.py index 248b504b9b..affc3e29e4 100644 --- a/tests/test_arr_venue_v2.py +++ b/tests/test_arr_venue_v2.py @@ -213,6 +213,9 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req openreview_client.add_members_to_group('aclweb.org/ACL/ARR/Ethics_Chairs', [ '~EthicsChair_ARROne1', ]) + openreview_client.add_members_to_group('aclweb.org/ACL/ARR/Editors_In_Chief', [ + 'pc@aclrollingreview.org', + ]) # Create root maximum load invitations # NOTE: (assume this will be done before deployment) @@ -444,8 +447,6 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req # Verify Meta Invitation exists meta_invitation = openreview_client.get_invitation('aclweb.org/ACL/ARR/-/Edit') assert meta_invitation is not None - assert 'invitation_edit_script' in meta_invitation.content - assert 'group_edit_script' in meta_invitation.content # Verify Role Groups exist roles = ['Reviewers', 'Area_Chairs', 'Senior_Area_Chairs', 'Ethics_Reviewers', 'Editors_In_Chief'] From 846ae2ce0d4ae2c177452bc292992fa968d5b20b Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Mon, 8 Dec 2025 22:15:08 +0100 Subject: [PATCH 08/20] Add additional webfield configuration --- openreview/arr/webfield/areachairsWebfield.js | 1 + openreview/arr/webfield/programChairsWebfield.js | 6 ++++++ openreview/arr/webfield/seniorAreaChairsWebfield.js | 3 ++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/openreview/arr/webfield/areachairsWebfield.js b/openreview/arr/webfield/areachairsWebfield.js index b4f8ca9ea0..0bb00cefb5 100644 --- a/openreview/arr/webfield/areachairsWebfield.js +++ b/openreview/arr/webfield/areachairsWebfield.js @@ -91,5 +91,6 @@ return { anonReviewerName: domain.content.reviewers_anon_name?.value, preferredEmailInvitationId: preferredEmailInvitationId, ithenticateInvitationId: (domain.content.iThenticate_plagiarism_check_committee_readers?.value || []).includes(domain.content.area_chairs_name?.value) ? domain.content.iThenticate_plagiarism_check_invitation_id?.value : null, + additionalDomains: ["aclweb.org/ACL/ARR"] } } diff --git a/openreview/arr/webfield/programChairsWebfield.js b/openreview/arr/webfield/programChairsWebfield.js index d55af16cac..1949e2cb99 100644 --- a/openreview/arr/webfield/programChairsWebfield.js +++ b/openreview/arr/webfield/programChairsWebfield.js @@ -120,6 +120,12 @@ return { ithenticateInvitationId: domain.content.iThenticate_plagiarism_check_invitation_id?.value, customMaxPapersName: 'Custom_Max_Papers', + additionalRegistrationDomains: ["aclweb.org/ACL/ARR"], + additionalRegistrationPrefixes: [ + "aclweb.org/ACL/ARR/Reviewers", + "aclweb.org/ACL/ARR/Area_Chairs", + "aclweb.org/ACL/ARR/Senior_Area_Chairs" + ], customStageInvitations: [ { name:'Action_Editor_Checklist', diff --git a/openreview/arr/webfield/seniorAreaChairsWebfield.js b/openreview/arr/webfield/seniorAreaChairsWebfield.js index 21c547e0fb..ff988043e5 100644 --- a/openreview/arr/webfield/seniorAreaChairsWebfield.js +++ b/openreview/arr/webfield/seniorAreaChairsWebfield.js @@ -121,6 +121,7 @@ return { }) return checklistReplies?.length??0; ` - } + }, + additionalDomains: ["aclweb.org/ACL/ARR"] } } \ No newline at end of file From 79bf80f0e244bcc5814b27ae14471590e8423d28 Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Tue, 16 Dec 2025 15:30:05 -0500 Subject: [PATCH 09/20] Add dateprocess --- openreview/arr/arr.py | 59 +++++++++++--- .../arr/process/sync_cmp_dateprocess.py | 81 +++++++++++++++++++ 2 files changed, 128 insertions(+), 12 deletions(-) create mode 100644 openreview/arr/process/sync_cmp_dateprocess.py diff --git a/openreview/arr/arr.py b/openreview/arr/arr.py index 77ccc9246f..933b4ad7bb 100644 --- a/openreview/arr/arr.py +++ b/openreview/arr/arr.py @@ -367,9 +367,8 @@ def setup(self, program_chair_ids=[], publication_chairs_ids=[]): setup_value = self.venue.setup(program_chair_ids, publication_chairs_ids) # Add root role names to domain content - cycle_roles = [r for r in ARR_ROLE_NAMES if r != 'Editors_In_Chief'] root_content = {} - for role in cycle_roles: + for role in ARR_ROLE_NAMES: key = f"root_{role.lower()}_id" root_content[key] = { 'value': f'{ROOT_DOMAIN}/{role}' } self.client.post_group_edit( @@ -384,16 +383,14 @@ def setup(self, program_chair_ids=[], publication_chairs_ids=[]): ) # Initialize root-level ARR groups if not populated - setup_arr_root_groups(self.client, self.support_user) + # setup_arr_root_groups(self.client, self.support_user) # Synchronize groups - for role in ARR_ROLE_NAMES: - # skip EIC group - if role == 'Editors_In_Chief': - continue - source_group = self.client.get_group(f'{ROOT_DOMAIN}/{role}') - target_group_id = f'{self.venue_id}/{role}' - self.client.add_members_to_group(target_group_id, source_group.members) + if self.venue_id != ROOT_DOMAIN: + for role in ARR_ROLE_NAMES: + source_group = self.client.get_group(f'{ROOT_DOMAIN}/{role}') + target_group_id = f'{self.venue_id}/{role}' + self.client.add_members_to_group(target_group_id, source_group.members) with open(os.path.join(os.path.dirname(__file__), 'webfield/homepageWebfield.js')) as f: content = f.read() @@ -437,7 +434,29 @@ def setup(self, program_chair_ids=[], publication_chairs_ids=[]): id=self.get_area_chairs_id(), web=content ) - ) + ) + + with open(os.path.join(os.path.dirname(__file__), 'webfield/reviewersWebfield.js')) as f: + content = f.read() + self.client.post_group_edit( + invitation=self.get_meta_invitation_id(), + signatures=[self.venue_id], + group=openreview.api.Group( + id=self.get_reviewers_id(), + web=content + ) + ) + + with open(os.path.join(os.path.dirname(__file__), 'webfield/ethicsReviewersWebfield.js')) as f: + content = f.read() + self.client.post_group_edit( + invitation=self.get_meta_invitation_id(), + signatures=[self.venue_id], + group=openreview.api.Group( + id=self.get_ethics_reviewers_id(), + web=content + ) + ) with open(os.path.join(os.path.dirname(__file__), 'webfield/ethicsChairsWebfield.js')) as f: content = f.read() @@ -600,7 +619,23 @@ def send_decision_notifications(self, decision_options, messages): return self.venue.send_decision_notifications(decision_options, messages) def setup_committee_matching(self, committee_id=None, compute_affinity_scores=False, compute_conflicts=False, compute_conflicts_n_years=None, alternate_matching_group=None, submission_track=None): - return self.venue.setup_committee_matching(committee_id, compute_affinity_scores, compute_conflicts, compute_conflicts_n_years, alternate_matching_group, submission_track) + return_value = self.venue.setup_committee_matching(committee_id, compute_affinity_scores, compute_conflicts, compute_conflicts_n_years, alternate_matching_group, submission_track) + # Attach dateprocess to sync custom max papers + self.client.post_invitation_edit( + invitations=self.venue.get_meta_invitation_id(), + readers=[self.venue_id], + writers=[self.venue_id], + signatures=[self.venue_id], + replacement=False, + invitation=openreview.api.Invitation( + id=self.get_custom_max_papers_id(committee_id), + date_processes=[{ + 'cron': '0 0,12 * * *', + 'script': self.invitation_builder.get_process_content('process/sync_cmp_dateprocess.py') + }], + ) + ) + return return_value def set_assignments(self, assignment_title, committee_id, enable_reviewer_reassignment=False, overwrite=False): return self.venue.set_assignments(assignment_title, committee_id, enable_reviewer_reassignment, overwrite) diff --git a/openreview/arr/process/sync_cmp_dateprocess.py b/openreview/arr/process/sync_cmp_dateprocess.py new file mode 100644 index 0000000000..a4e673eab7 --- /dev/null +++ b/openreview/arr/process/sync_cmp_dateprocess.py @@ -0,0 +1,81 @@ +def process(client, invitation): + import openreview + from openreview.arr.helpers import flag_submission + from openreview.arr.arr import ROOT_DOMAIN + from openreview.arr.invitation import InvitationBuilder + + MAX_LOAD_AND_UNAVAILABILITY_NAME = InvitationBuilder.MAX_LOAD_AND_UNAVAILABILITY_NAME + + domain = client.get_group(invitation.domain) + venue_id = domain.id + meta_invitation_id = domain.get_content_value('meta_invitation_id') + short_name = domain.get_content_value('subtitle') + contact = domain.get_content_value('contact') + authors_name = domain.get_content_value('authors_name') + submission_name = domain.get_content_value('submission_name') + reviewers_name = domain.get_content_value('reviewers_name') + reviewers_anon_name = domain.get_content_value('reviewers_anon_name') + reviewers_submitted_name = domain.get_content_value('reviewers_submitted_name') + sender = domain.get_content_value('message_sender') + comment_threshold = domain.get_content_value('comment_notification_threshold') + + senior_area_chairs_id = domain.content['senior_area_chairs_id']['value'] + area_chairs_id = domain.content['area_chairs_id']['value'] + reviewers_id = domain.content['reviewers_id']['value'] + ethics_chairs_id = domain.content['ethics_chairs_id']['value'] + ethics_reviewers_id = f"{venue_id}/{domain.content['ethics_reviewers_name']['value']}" + + track_edge_readers = { + senior_area_chairs_id: [venue_id], + area_chairs_id: [venue_id, senior_area_chairs_id], + reviewers_id: [venue_id, senior_area_chairs_id, area_chairs_id], + ethics_reviewers_id: [venue_id, senior_area_chairs_id, area_chairs_id, ethics_chairs_id], + } + + cycle_committee_id = invitation.id.split('/-/')[0] + custom_max_papers_id = f"{cycle_committee_id}/-/Custom_Max_Papers" + group_name = cycle_committee_id.split('/')[-1] + root_group_id = f"{ROOT_DOMAIN}/{group_name}" + root_members = client.get_group(root_group_id).members + cycle_members = client.get_group(cycle_committee_id).members + all_member_ids = list(set(root_members + cycle_members)) + all_profiles = openreview.tools.get_profiles(client, all_member_ids) + + name_to_profile_map = {} + for profile in all_profiles: + for name in profile.content['names']: + if 'username' in name: + name_to_profile_map[name['username']] = profile + + root_notes = client.get_all_notes(invitation=f"{root_group_id}/-/{MAX_LOAD_AND_UNAVAILABILITY_NAME}") + id_to_load = {} + for note in root_notes: + if note.signatures[0] not in name_to_profile_map: + continue + id_to_load[name_to_profile_map[note.signatures[0]].id] = note.content['maximum_load_this_cycle']['value'] + + edges_to_post = [] + for cycle_member in cycle_members: + profile = name_to_profile_map.get(cycle_member) + if not profile: + continue + load = id_to_load.get(profile.id) + weight = load if load else 0 + edges_to_post.append( + openreview.api.Edge( + invitation=custom_max_papers_id, + head=cycle_committee_id, + tail=profile.id, + weight=weight, + readers=track_edge_readers[cycle_committee_id] + [profile.id], + writers=[venue_id], + signatures=[venue_id] + ) + ) + + client.delete_edges( + invitation=custom_max_papers_id, + soft_delete=True, + wait_to_finish=True + ) + openreview.tools.post_bulk_edges(client=client, edges=edges_to_post) \ No newline at end of file From c546fc2114381490935f5cd6c264a9b6bb710b73 Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Tue, 16 Dec 2025 15:30:18 -0500 Subject: [PATCH 10/20] Deploy root venue --- openreview/arr/helpers.py | 2 +- tests/test_arr_venue_v2.py | 190 ++++++++++++++++++++++++++++++------- 2 files changed, 156 insertions(+), 36 deletions(-) diff --git a/openreview/arr/helpers.py b/openreview/arr/helpers.py index 6519898077..b7d01d7ae9 100644 --- a/openreview/arr/helpers.py +++ b/openreview/arr/helpers.py @@ -52,7 +52,7 @@ 'Senior_Area_Chairs', 'Ethics_Reviewers', 'Ethics_Chairs', - 'Editors_In_Chief' + 'Program_Chairs' ] class ARRWorkflow(object): diff --git a/tests/test_arr_venue_v2.py b/tests/test_arr_venue_v2.py index affc3e29e4..5ff255a630 100644 --- a/tests/test_arr_venue_v2.py +++ b/tests/test_arr_venue_v2.py @@ -182,7 +182,75 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req # Create top-level groups first # NOTE: (assume this will be done before deployment) - setup_arr_root_groups(openreview_client, 'openreview.net/Support') + root_form_note = pc_client.post_note(openreview.Note( + invitation='openreview.net/Support/-/Request_Form', + signatures=['~Program_ARRChair1'], + readers=[ + 'openreview.net/Support', + '~Program_ARRChair1' + ], + writers=[], + content={ + 'title': 'ACL Rolling Review - Root', + 'Official Venue Name': 'ACL Rolling Review - Root', + 'Abbreviated Venue Name': 'ARR - Root', + 'Official Website URL': 'http://aclrollingreview.org', + 'program_chair_emails': ['editors@aclrollingreview.org', 'pc@aclrollingreview.org'], + 'contact_email': 'editors@aclrollingreview.org', + 'Area Chairs (Metareviewers)': 'Yes, our venue has Area Chairs', + 'senior_area_chairs': 'Yes, our venue has Senior Area Chairs', + 'senior_area_chairs_assignment': 'Submissions', + 'ethics_chairs_and_reviewers': 'Yes, our venue has Ethics Chairs and Reviewers', + 'publication_chairs':'No, our venue does not have Publication Chairs', + 'Venue Start Date': '2023/08/01', + 'Submission Deadline': due_date.strftime('%Y/%m/%d'), + 'Location': 'Virtual', + 'submission_reviewer_assignment': 'Automatic', + 'Author and Reviewer Anonymity': 'Double-blind', + 'reviewer_identity': ['Program Chairs', 'Assigned Senior Area Chair', 'Assigned Area Chair', 'Assigned Reviewers'], + 'area_chair_identity': ['Program Chairs', 'Assigned Senior Area Chair', 'Assigned Area Chair', 'Assigned Reviewers'], + 'senior_area_chair_identity': ['Program Chairs', 'Assigned Senior Area Chair', 'Assigned Area Chair', 'Assigned Reviewers'], + 'Open Reviewing Policy': 'Submissions and reviews should both be private.', + 'submission_readers': 'Assigned program committee (assigned reviewers, assigned area chairs, assigned senior area chairs if applicable)', + 'How did you hear about us?': 'ML conferences', + 'Expected Submissions': '100', + 'use_recruitment_template': 'Yes', + 'api_version': '2', + 'submission_license': ['CC BY-SA 4.0'], + "preferred_emails_groups": [ + "aclweb.org/ACL/ARR/Senior_Area_Chairs", + "aclweb.org/ACL/ARR/Area_Chairs", + "aclweb.org/ACL/ARR/Reviewers" + ], + 'comment_notification_threshold': '3', + 'venue_organizer_agreement': [ + 'OpenReview natively supports a wide variety of reviewing workflow configurations. However, if we want significant reviewing process customizations or experiments, we will detail these requests to the OpenReview staff at least three months in advance.', + 'We will ask authors and reviewers to create an OpenReview Profile at least two weeks in advance of the paper submission deadlines.', + 'When assembling our group of reviewers and meta-reviewers, we will only include email addresses or OpenReview Profile IDs of people we know to have authored publications relevant to our venue. (We will not solicit new reviewers using an open web form, because unfortunately some malicious actors sometimes try to create "fake ids" aiming to be assigned to review their own paper submissions.)', + 'We acknowledge that, if our venue\'s reviewing workflow is non-standard, or if our venue is expecting more than a few hundred submissions for any one deadline, we should designate our own Workflow Chair, who will read the OpenReview documentation and manage our workflow configurations throughout the reviewing process.', + 'We acknowledge that OpenReview staff work Monday-Friday during standard business hours US Eastern time, and we cannot expect support responses outside those times. For this reason, we recommend setting submission and reviewing deadlines Monday through Thursday.', + 'We will treat the OpenReview staff with kindness and consideration.' + ], + 'senior_area_chair_roles': ['Senior_Area_Chairs'], + 'area_chair_roles': ['Area_Chairs'], + 'reviewer_roles': ['Reviewers'], + })) + + helpers.await_queue() + + # Post a deploy note + root_deploy_edit = client.post_note(openreview.Note( + content={'venue_id': 'aclweb.org/ACL/ARR'}, + forum=root_form_note.forum, + invitation='openreview.net/Support/-/Request{}/Deploy'.format(root_form_note.number), + readers=['openreview.net/Support'], + referent=root_form_note.forum, + replyto=root_form_note.forum, + signatures=['openreview.net/Support'], + writers=['openreview.net/Support'] + )) + + helpers.await_queue() # Add all users to top-level role groups # NOTE: (assume this will be done before deployment) @@ -213,9 +281,6 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req openreview_client.add_members_to_group('aclweb.org/ACL/ARR/Ethics_Chairs', [ '~EthicsChair_ARROne1', ]) - openreview_client.add_members_to_group('aclweb.org/ACL/ARR/Editors_In_Chief', [ - 'pc@aclrollingreview.org', - ]) # Create root maximum load invitations # NOTE: (assume this will be done before deployment) @@ -440,25 +505,17 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req domain_group = openreview_client.get_group('aclweb.org/ACL/ARR') assert domain_group is not None - # Verify PC is in the EIC group - eic_group = openreview_client.get_group('aclweb.org/ACL/ARR/Editors_In_Chief') - assert 'pc@aclrollingreview.org' in eic_group.members - # Verify Meta Invitation exists meta_invitation = openreview_client.get_invitation('aclweb.org/ACL/ARR/-/Edit') assert meta_invitation is not None # Verify Role Groups exist - roles = ['Reviewers', 'Area_Chairs', 'Senior_Area_Chairs', 'Ethics_Reviewers', 'Editors_In_Chief'] + roles = ['Reviewers', 'Area_Chairs', 'Senior_Area_Chairs', 'Ethics_Reviewers', 'Program_Chairs'] for role in roles: role_group = openreview_client.get_group(f'aclweb.org/ACL/ARR/{role}') assert role_group is not None assert role_group.id == f'aclweb.org/ACL/ARR/{role}' - # Verify Editors_In_Chief has correct permissions - eic_group = openreview_client.get_group('aclweb.org/ACL/ARR/Editors_In_Chief') - assert 'aclweb.org/ACL/ARR/Editors_In_Chief' in eic_group.writers - august_cycle_group = openreview_client.get_group('aclweb.org/ACL/ARR/2023/August/Reviewers') # Use an existing member from the August cycle group reviewer_profile_id = august_cycle_group.members[0] @@ -468,7 +525,7 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req assert reviewer_profile_id in shared_group.members # Test that calling setup_arr_root_groups again doesn't break (idempotent) - setup_arr_root_groups(openreview_client, 'openreview.net/Support') + # setup_arr_root_groups(openreview_client, 'openreview.net/Support') # Verify groups still exist for role in roles: @@ -1158,7 +1215,7 @@ def test_june_cycle(self, client, openreview_client, helpers, test_client): def test_ac_recruitment(self, client, openreview_client, helpers, request_page, selenium): pc_client = openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) - request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] + request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[1] reviewer_details = '''acextra1@aclrollingreview.com, AC ARRExtraOne\nacextra2@aclrollingreview.com, AC ARRExtraOne''' pc_client.post_note(openreview.Note( @@ -1210,7 +1267,7 @@ def test_ac_recruitment(self, client, openreview_client, helpers, request_page, def test_reviewer_recruitment(self, client, openreview_client, helpers, request_page, selenium): pc_client = openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) - request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] + request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[1] reviewer_details = '''reviewerextra1@aclrollingreview.com, Reviewer ARRExtraOne reviewerextra2@aclrollingreview.com, Reviewer ARRExtraTwo @@ -1271,10 +1328,10 @@ def test_submission_preprocess(self, client, openreview_client, test_client, hel # of previous_URL/reassignment_request_area_chair/reassignment_request_reviewers pc_client=openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) pc_client_v2=openreview.api.OpenReviewClient(username='pc@aclrollingreview.org', password=helpers.strong_password) - june_request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[0] + june_request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[2] june_venue = openreview.helpers.get_conference(client, june_request_form.id, 'openreview.net/Support') test_client = openreview.api.OpenReviewClient(token=test_client.token) - request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] + request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[1] august_venue = openreview.helpers.get_conference(client, request_form.id, 'openreview.net/Support') generic_note_content = { @@ -1619,7 +1676,7 @@ def test_no_assignment_preprocess(self, client, openreview_client, test_client, # If reviewer assignment quota is not set, check that pre-processes don't fail pc_client=openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) pc_client_v2=openreview.api.OpenReviewClient(username='pc@aclrollingreview.org', password=helpers.strong_password) - request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[0] + request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[2] june_venue = openreview.helpers.get_conference(client, request_form.id, 'openreview.net/Support') test_client = openreview.api.OpenReviewClient(token=test_client.token) submissions = june_venue.get_submissions(sort='number:asc') @@ -1849,7 +1906,7 @@ def _generate_valid_content(i, domains, submission): test_client = openreview.api.OpenReviewClient(token=test_client.token) pc_client=openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) - request_form_note=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] + request_form_note=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[1] now = datetime.datetime.now() due_date = now + datetime.timedelta(days=3) @@ -1970,7 +2027,7 @@ def _generate_valid_content(i, domains, submission): def test_submitted_author_form(self, client, openreview_client, helpers, test_client, request_page, selenium): pc_client=openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) pc_client_v2=openreview.api.OpenReviewClient(username='pc@aclrollingreview.org', password=helpers.strong_password) - request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] + request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[1] august_venue = openreview.helpers.get_conference(client, request_form.id, 'openreview.net/Support') submissions = pc_client_v2.get_notes(invitation='aclweb.org/ACL/ARR/2023/August/-/Submission', sort='number:asc') test_client = openreview.api.OpenReviewClient(token=test_client.token) @@ -2176,7 +2233,7 @@ def test_submitted_author_form(self, client, openreview_client, helpers, test_cl def test_post_submission(self, client, openreview_client, helpers, test_client, request_page, selenium): pc_client=openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) - request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] + request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[1] ## close the submissions now = datetime.datetime.now() @@ -2479,7 +2536,7 @@ def test_post_submission(self, client, openreview_client, helpers, test_client, def test_metadata_edit(self, client, openreview_client, helpers, test_client, request_page, selenium): pc_client=openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) pc_client_v2=openreview.api.OpenReviewClient(username='pc@aclrollingreview.org', password=helpers.strong_password) - request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] + request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[1] august_venue = openreview.helpers.get_conference(client, request_form.id, 'openreview.net/Support') submissions = pc_client_v2.get_notes(invitation='aclweb.org/ACL/ARR/2023/August/-/Submission', sort='number:asc') test_client = openreview.api.OpenReviewClient(token=test_client.token) @@ -2612,7 +2669,7 @@ def test_metadata_edit(self, client, openreview_client, helpers, test_client, re def test_setup_matching(self, client, openreview_client, helpers, test_client, request_page, selenium): pc_client=openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) pc_client_v2=openreview.api.OpenReviewClient(username='pc@aclrollingreview.org', password=helpers.strong_password) - request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] + request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[1] august_venue = openreview.helpers.get_conference(client, request_form.id, 'openreview.net/Support') test_client = openreview.api.OpenReviewClient(token=test_client.token) @@ -2900,6 +2957,69 @@ def test_setup_matching(self, client, openreview_client, helpers, test_client, r assert openreview_client.get_invitation('aclweb.org/ACL/ARR/2023/August/Reviewers/-/Conflict') assert openreview_client.get_edges_count(invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Conflict') == 16 # All 8 reviewers will conflict with submissions 1/101 because of domain of SAC + + # Test sync_cmp_dateprocess cron schedule and functionality + from openreview.arr.process.sync_cmp_dateprocess import process as sync_cmp_process + + # 1. Assert date_process exists with correct cron schedule + cmp_invitation = openreview_client.get_invitation('aclweb.org/ACL/ARR/2023/August/Reviewers/-/Custom_Max_Papers') + assert cmp_invitation.date_processes is not None + assert cmp_invitation.date_processes[0].get('cron') == '0 0,12 * * *' + + # 2. Get current edge weight for Reviewer_ARRTwo1 (set to 0 in root note earlier) + original_edges = openreview_client.get_all_edges( + invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Custom_Max_Papers', + tail='~Reviewer_ARRTwo1' + ) + original_weight = original_edges[0].weight if original_edges else 0 + + original_note = openreview_client.get_all_notes( + invitation = "aclweb.org/ACL/ARR/Reviewers/-/Max_Load_And_Unavailability_Request", + signatures=['~Reviewer_ARRTwo1'] + )[0] + + # 3. Modify root max load note to a new value + note_edit = openreview_client.post_note_edit( + invitation="aclweb.org/ACL/ARR/Reviewers/-/Max_Load_And_Unavailability_Request", + signatures=['~Reviewer_ARRTwo1'], + note=openreview.api.Note( + id=original_note.id, + content={ + "maximum_load_this_cycle": {"value": 5}, # Test value + "maximum_load_this_cycle_for_resubmissions": {"value": "No"}, + "meta_data_donation": {"value": "Yes, I consent to donating anonymous metadata of my review for research."} + } + ) + ) + + # 4. Run the process directly + sync_cmp_process(openreview_client, cmp_invitation) + + # 5. Assert edge was updated + updated_edges = openreview_client.get_all_edges( + invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Custom_Max_Papers', + tail='~Reviewer_ARRTwo1' + ) + assert len(updated_edges) == 1 + assert updated_edges[0].weight == 5 + + # 6. Revert the root note back to original value + note_edit = openreview_client.post_note_edit( + invitation="aclweb.org/ACL/ARR/Reviewers/-/Max_Load_And_Unavailability_Request", + signatures=['~Reviewer_ARRTwo1'], + note=openreview.api.Note( + id=original_note.id, + content={ + "maximum_load_this_cycle": {"value": 0}, # Original value + "maximum_load_this_cycle_for_resubmissions": {"value": "No"}, + "meta_data_donation": {"value": "Yes, I consent to donating anonymous metadata of my review for research."} + } + ) + ) + + # Run process again to restore edges + sync_cmp_process(openreview_client, cmp_invitation) + helpers.await_queue() ## Extra 101 conflicts from new reviewer which is an author of all submissions # NOTE: All reviewers included, no excluding @@ -3021,9 +3141,9 @@ def test_resubmission_and_track_matching_data(self, client, openreview_client, h # Create groups for previous cycle pc_client=openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) pc_client_v2=openreview.api.OpenReviewClient(username='pc@aclrollingreview.org', password=helpers.strong_password) - june_request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[0] + june_request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[2] june_venue = openreview.helpers.get_conference(client, june_request_form.id, 'openreview.net/Support') - request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] + request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[1] august_venue = openreview.helpers.get_conference(client, request_form.id, 'openreview.net/Support') june_submissions = pc_client_v2.get_notes(invitation='aclweb.org/ACL/ARR/2023/June/-/Submission', sort='number:asc') submissions = pc_client_v2.get_notes(invitation='aclweb.org/ACL/ARR/2023/August/-/Submission', sort='number:asc') @@ -3509,7 +3629,7 @@ def test_sae_ae_assignments(self, client, openreview_client, helpers, test_clien pc_client=openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) pc_client_v2=openreview.api.OpenReviewClient(username='pc@aclrollingreview.org', password=helpers.strong_password) - request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] + request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[1] august_venue = openreview.helpers.get_conference(client, request_form.id, 'openreview.net/Support') test_client = openreview.api.OpenReviewClient(token=test_client.token) @@ -4055,7 +4175,7 @@ def test_sae_ae_assignments(self, client, openreview_client, helpers, test_clien def test_checklists(self, client, openreview_client, helpers, test_client, request_page, selenium): pc_client=openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) pc_client_v2=openreview.api.OpenReviewClient(username='pc@aclrollingreview.org', password=helpers.strong_password) - request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] + request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[1] venue = openreview.helpers.get_conference(client, request_form.id, 'openreview.net/Support') submissions = pc_client_v2.get_notes(invitation='aclweb.org/ACL/ARR/2023/August/-/Submission', sort='number:asc') violation_fields = ['appropriateness', 'formatting', 'length', 'anonymity', 'responsible_checklist', 'limitations'] # TODO: move to domain or somewhere? @@ -4355,7 +4475,7 @@ def now(): def test_official_review_flagging(self, client, openreview_client, helpers, test_client, request_page, selenium): pc_client=openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) pc_client_v2=openreview.api.OpenReviewClient(username='pc@aclrollingreview.org', password=helpers.strong_password) - request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] + request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[1] venue = openreview.helpers.get_conference(client, request_form.id, 'openreview.net/Support') submissions = pc_client_v2.get_notes(invitation='aclweb.org/ACL/ARR/2023/August/-/Submission', sort='number:asc') ethics_client = openreview.api.OpenReviewClient(username = 'reviewerethics@aclrollingreview.com', password=helpers.strong_password) @@ -4751,7 +4871,7 @@ def now(): def test_author_response(self, client, openreview_client, helpers, test_client, request_page, selenium): pc_client=openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) pc_client_v2=openreview.api.OpenReviewClient(username='pc@aclrollingreview.org', password=helpers.strong_password) - request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] + request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[1] venue = openreview.helpers.get_conference(client, request_form.id, 'openreview.net/Support') submissions = venue.get_submissions(sort='tmdate') @@ -5010,7 +5130,7 @@ def test_author_response(self, client, openreview_client, helpers, test_client, def test_changing_deadlines(self, client, openreview_client, helpers, test_client, request_page, selenium): pc_client=openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) pc_client_v2=openreview.api.OpenReviewClient(username='pc@aclrollingreview.org', password=helpers.strong_password) - request_form_note=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] + request_form_note=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[1] venue = openreview.helpers.get_conference(client, request_form_note.id, 'openreview.net/Support') registration_name = 'Registration' max_load_name = 'Max_Load_And_Unavailability_Request' @@ -5094,7 +5214,7 @@ def test_changing_deadlines(self, client, openreview_client, helpers, test_clien def test_meta_review_flagging_and_ethics_review(self, client, openreview_client, helpers, test_client, request_page, selenium): pc_client=openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) pc_client_v2=openreview.api.OpenReviewClient(username='pc@aclrollingreview.org', password=helpers.strong_password) - request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] + request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[1] venue = openreview.helpers.get_conference(client, request_form.id, 'openreview.net/Support') submissions = pc_client_v2.get_notes(invitation='aclweb.org/ACL/ARR/2023/August/-/Submission', sort='number:asc') violation_fields = ['author_identity_guess'] @@ -5301,7 +5421,7 @@ def test_emergency_reviewing_forms(self, client, openreview_client, helpers): now = datetime.datetime.now() pc_client=openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) pc_client_v2=openreview.api.OpenReviewClient(username='pc@aclrollingreview.org', password=helpers.strong_password) - request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] + request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[1] venue = openreview.helpers.get_conference(client, request_form.id, 'openreview.net/Support') invitation_builder = openreview.arr.InvitationBuilder(venue) @@ -5539,7 +5659,7 @@ def test_review_issue_forms(self, client, openreview_client, helpers, test_clien now = datetime.datetime.now() pc_client=openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) pc_client_v2=openreview.api.OpenReviewClient(username='pc@aclrollingreview.org', password=helpers.strong_password) - request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] + request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[1] venue = openreview.helpers.get_conference(client, request_form.id, 'openreview.net/Support') invitation_builder = openreview.arr.InvitationBuilder(venue) test_client = openreview.api.OpenReviewClient(token=test_client.token) @@ -5662,7 +5782,7 @@ def test_reviewer_management_forms(self, client, openreview_client, helpers, tes sac_client = openreview.api.OpenReviewClient(username='sac2@aclrollingreview.com', password=helpers.strong_password) # Get venue configuration - request_form = pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[1] + request_form = pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form', sort='number:asc')[1] # Configure dates now = datetime.datetime.now() From a394765286a82a968714b195eb2173ddc6388858 Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Wed, 17 Dec 2025 09:27:38 -0500 Subject: [PATCH 11/20] Adjust test to use root venue --- tests/test_arr_venue_v2.py | 49 ++++++++++++++------------------------ 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/tests/test_arr_venue_v2.py b/tests/test_arr_venue_v2.py index 5ff255a630..37b8a661c6 100644 --- a/tests/test_arr_venue_v2.py +++ b/tests/test_arr_venue_v2.py @@ -287,7 +287,10 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req reg_start_date = datetime.datetime.now() reg_due_date = reg_start_date + datetime.timedelta(days=1) reg_exp_date = reg_due_date + datetime.timedelta(days=1) - root_invitation_builder = RootInvitationBuilder(openreview_client) + root_venue = openreview.helpers.get_conference( + client, + root_form_note.id + ) reviewer_load_stage = openreview.stages.RegistrationStage( committee_id='aclweb.org/ACL/ARR/Reviewers', @@ -300,9 +303,8 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req remove_fields = ['profile_confirmed', 'expertise_confirmed'], additional_fields = arr_reviewer_max_load_task ) - root_invitation_builder.set_registration_invitation( - reviewer_load_stage - ) + root_venue.registration_stages = [reviewer_load_stage] + area_chair_load_stage = openreview.stages.RegistrationStage( committee_id='aclweb.org/ACL/ARR/Area_Chairs', @@ -315,9 +317,8 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req remove_fields = ['profile_confirmed', 'expertise_confirmed'], additional_fields = arr_ac_max_load_task ) - root_invitation_builder.set_registration_invitation( - area_chair_load_stage - ) + root_venue.registration_stages.append(area_chair_load_stage) + senior_area_chair_load_stage = openreview.stages.RegistrationStage( committee_id='aclweb.org/ACL/ARR/Senior_Area_Chairs', name='Max_Load_And_Unavailability_Request', @@ -329,9 +330,7 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req remove_fields = ['profile_confirmed', 'expertise_confirmed'], additional_fields = arr_sac_max_load_task ) - root_invitation_builder.set_registration_invitation( - senior_area_chair_load_stage - ) + root_venue.registration_stages.append(senior_area_chair_load_stage) # Create root registration invitations # NOTE: (assume this will be done before deployment) @@ -345,9 +344,7 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req instructions = arr_registration_task_forum['instructions'], additional_fields = arr_registration_task ) - root_invitation_builder.set_registration_invitation( - reviewer_reg_stage - ) + root_venue.registration_stages.append(reviewer_reg_stage) area_chair_reg_stage = openreview.stages.RegistrationStage( committee_id='aclweb.org/ACL/ARR/Area_Chairs', name='Registration', @@ -358,9 +355,7 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req instructions = arr_registration_task_forum['instructions'], additional_fields = arr_registration_task ) - root_invitation_builder.set_registration_invitation( - area_chair_reg_stage - ) + root_venue.registration_stages.append(area_chair_reg_stage) senior_area_chair_reg_stage = openreview.stages.RegistrationStage( committee_id='aclweb.org/ACL/ARR/Senior_Area_Chairs', name='Registration', @@ -371,9 +366,7 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req instructions = arr_registration_task_forum['instructions'], additional_fields = arr_registration_task ) - root_invitation_builder.set_registration_invitation( - senior_area_chair_reg_stage - ) + root_venue.registration_stages.append(senior_area_chair_reg_stage) reviewer_license_stage = openreview.stages.RegistrationStage( committee_id='aclweb.org/ACL/ARR/Reviewers', name='License_Agreement', @@ -385,9 +378,7 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req additional_fields = arr_content_license_task, remove_fields = ['profile_confirmed', 'expertise_confirmed'] ) - root_invitation_builder.set_registration_invitation( - reviewer_license_stage - ) + root_venue.registration_stages.append(reviewer_license_stage) area_chair_license_stage = openreview.stages.RegistrationStage( committee_id='aclweb.org/ACL/ARR/Area_Chairs', name='License_Agreement', @@ -399,9 +390,7 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req additional_fields = arr_content_license_task, remove_fields = ['profile_confirmed', 'expertise_confirmed'] ) - root_invitation_builder.set_registration_invitation( - area_chair_license_stage - ) + root_venue.registration_stages.append(area_chair_license_stage) reviewer_emergency_agreement_stage = openreview.stages.RegistrationStage( committee_id='aclweb.org/ACL/ARR/Reviewers', name='Emergency_Reviewer_Agreement', @@ -413,9 +402,7 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req additional_fields = arr_reviewer_emergency_load_task, remove_fields = ['profile_confirmed', 'expertise_confirmed'] ) - root_invitation_builder.set_registration_invitation( - reviewer_emergency_agreement_stage - ) + root_venue.registration_stages.append(reviewer_emergency_agreement_stage) area_chair_emergency_agreement_stage = openreview.stages.RegistrationStage( committee_id='aclweb.org/ACL/ARR/Area_Chairs', name='Emergency_Metareviewer_Agreement', @@ -427,9 +414,9 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req additional_fields = arr_ac_emergency_load_task, remove_fields = ['profile_confirmed', 'expertise_confirmed'] ) - root_invitation_builder.set_registration_invitation( - area_chair_emergency_agreement_stage - ) + root_venue.registration_stages.append(area_chair_emergency_agreement_stage) + + root_venue.create_registration_stages() request_form_note = pc_client.post_note(openreview.Note( invitation='openreview.net/Support/-/Request_Form', From 965f03f58ddd2addf879b426cae870b721d686c5 Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Wed, 17 Dec 2025 16:47:17 -0500 Subject: [PATCH 12/20] Fix tests --- tests/test_arr_venue_v2.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/tests/test_arr_venue_v2.py b/tests/test_arr_venue_v2.py index 37b8a661c6..db40ee44bc 100644 --- a/tests/test_arr_venue_v2.py +++ b/tests/test_arr_venue_v2.py @@ -34,6 +34,8 @@ from openreview.arr.helpers import setup_arr_root_groups from openreview.arr.root_invitation import RootInvitationBuilder from openreview.arr.arr import ROOT_DOMAIN +from openreview.arr.process.sync_cmp_dateprocess import process as sync_cmp_process + # API2 template from ICML class TestARRVenueV2(): @@ -2945,9 +2947,6 @@ def test_setup_matching(self, client, openreview_client, helpers, test_client, r assert openreview_client.get_edges_count(invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Conflict') == 16 # All 8 reviewers will conflict with submissions 1/101 because of domain of SAC - # Test sync_cmp_dateprocess cron schedule and functionality - from openreview.arr.process.sync_cmp_dateprocess import process as sync_cmp_process - # 1. Assert date_process exists with correct cron schedule cmp_invitation = openreview_client.get_invitation('aclweb.org/ACL/ARR/2023/August/Reviewers/-/Custom_Max_Papers') assert cmp_invitation.date_processes is not None @@ -5449,7 +5448,7 @@ def test_emergency_reviewing_forms(self, client, openreview_client, helpers): ac_client = openreview.api.OpenReviewClient(username = 'ac2@aclrollingreview.com', password=helpers.strong_password) reviewer_note_edit = reviewer_client.post_note_edit( ## Reviewer 1 will have an original load - invitation=f'{venue.get_reviewers_id()}/-/{invitation_builder.MAX_LOAD_AND_UNAVAILABILITY_NAME}', + invitation=f'aclweb.org/ACL/ARR/Reviewers/-/{invitation_builder.MAX_LOAD_AND_UNAVAILABILITY_NAME}', signatures=['~Reviewer_Alternate_ARROne1'], note=openreview.api.Note( content = { @@ -5459,7 +5458,9 @@ def test_emergency_reviewing_forms(self, client, openreview_client, helpers): } ) ) - helpers.await_queue_edit(openreview_client, edit_id=reviewer_note_edit['id']) + custom_max_papers_invitation = openreview_client.get_invitation('aclweb.org/ACL/ARR/2023/August/Reviewers/-/Custom_Max_Papers') + sync_cmp_process(openreview_client, custom_max_papers_invitation) + assert len(openreview_client.get_all_edges(invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Custom_Max_Papers', tail='~Reviewer_ARROne1')) == 1 assert openreview_client.get_all_edges(invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Custom_Max_Papers', tail='~Reviewer_ARROne1')[0].weight == 4 @@ -5913,7 +5914,7 @@ def test_email_options(self, client, openreview_client, helpers, test_client, re rev_client = openreview.api.OpenReviewClient(username = 'reviewer7@aclrollingreview.com', password=helpers.strong_password) rev_two_client = openreview.api.OpenReviewClient(username = 'reviewer2@aclrollingreview.com', password=helpers.strong_password) rev_client.post_note_edit( - invitation='aclweb.org/ACL/ARR/Reviewers/-/Max_Load_And_Unavailability_Request', + invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Max_Load_And_Unavailability_Request', signatures=['~Reviewer_ARRSeven1'], note=openreview.api.Note( content = { @@ -5924,7 +5925,7 @@ def test_email_options(self, client, openreview_client, helpers, test_client, re ) ) rev_client.post_note_edit( - invitation='aclweb.org/ACL/ARR/Reviewers/-/Emergency_Reviewer_Agreement', + invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Emergency_Reviewer_Agreement', signatures=['~Reviewer_ARRSeven1'], note=openreview.api.Note( content = { @@ -5971,7 +5972,7 @@ def test_email_options(self, client, openreview_client, helpers, test_client, re ) ) rev_two_client.post_note_edit( - invitation='aclweb.org/ACL/ARR/Reviewers/-/Emergency_Reviewer_Agreement', + invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Emergency_Reviewer_Agreement', signatures=['~Reviewer_ARRTwo1'], note=openreview.api.Note( content = { @@ -5993,6 +5994,11 @@ def test_email_options(self, client, openreview_client, helpers, test_client, re '~AC_ARRFive1', '~AC_ARRSix1' ]) + openreview_client.add_members_to_group('aclweb.org/ACL/ARR/Area_Chairs', [ + '~AC_ARRFour1', + '~AC_ARRFive1', + '~AC_ARRSix1' + ]) ac_client = openreview.api.OpenReviewClient(username = 'ac4@aclrollingreview.com', password=helpers.strong_password) edge = openreview_client.post_edge(openreview.api.Edge( invitation = 'aclweb.org/ACL/ARR/2023/August/Area_Chairs/-/Assignment', @@ -6039,7 +6045,7 @@ def test_email_options(self, client, openreview_client, helpers, test_client, re # AC with load no assignment and responded emergency ac_client = openreview.api.OpenReviewClient(username = 'ac5@aclrollingreview.com', password=helpers.strong_password) ac_client.post_note_edit( - invitation='aclweb.org/ACL/ARR/2023/August/Area_Chairs/-/Max_Load_And_Unavailability_Request', + invitation='aclweb.org/ACL/ARR/Area_Chairs/-/Max_Load_And_Unavailability_Request', signatures=['~AC_ARRFive1'], note=openreview.api.Note( content = { @@ -6051,7 +6057,7 @@ def test_email_options(self, client, openreview_client, helpers, test_client, re # AC with load no assignment no emergency ac_client = openreview.api.OpenReviewClient(username = 'ac6@aclrollingreview.com', password=helpers.strong_password) ac_client.post_note_edit( - invitation='aclweb.org/ACL/ARR/2023/August/Area_Chairs/-/Max_Load_And_Unavailability_Request', + invitation='aclweb.org/ACL/ARR/Area_Chairs/-/Max_Load_And_Unavailability_Request', signatures=['~AC_ARRSix1'], note=openreview.api.Note( content = { @@ -6072,6 +6078,9 @@ def test_email_options(self, client, openreview_client, helpers, test_client, re ) ) + custom_max_papers_invitation = openreview_client.get_invitation('aclweb.org/ACL/ARR/2023/August/Area_Chairs/-/Custom_Max_Papers') + sync_cmp_process(openreview_client, custom_max_papers_invitation) + def send_email(email_option, role): role_tab_id_format = role.replace('_', '-') role_message_id_format = role.replace('_', '') From 300dfc3cd2f7e6b9f444e25fa6466330d929a676 Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Wed, 17 Dec 2025 17:16:42 -0500 Subject: [PATCH 13/20] Remove old code --- openreview/arr/process/max_load_preprocess.py | 37 ------ openreview/arr/process/max_load_process.py | 117 ------------------ 2 files changed, 154 deletions(-) delete mode 100644 openreview/arr/process/max_load_preprocess.py delete mode 100644 openreview/arr/process/max_load_process.py diff --git a/openreview/arr/process/max_load_preprocess.py b/openreview/arr/process/max_load_preprocess.py deleted file mode 100644 index 0feed3cb3d..0000000000 --- a/openreview/arr/process/max_load_preprocess.py +++ /dev/null @@ -1,37 +0,0 @@ -def process(client, edit, invitation): - current_load = edit.note.content.get('maximum_load_this_cycle', {}).get('value', 0) - available_year = edit.note.content.get('next_available_year', {}).get('value') - available_month = edit.note.content.get('next_available_month', {}).get('value') - - # Check if user indicates they are available - is_year_na = available_year is None# if available_year else False - is_month_na = available_month is None# if available_month else False - - if isinstance(available_year, list): - if len(available_year) > 1: - raise openreview.OpenReviewException("Please provide only one (1) year") - elif len(available_year) == 1: - available_year = available_year[0] - else: # empty list - available_year = None - - if isinstance(available_month, list): - if len(available_month) > 1: - raise openreview.OpenReviewException("Please provide only one (1) month") - elif len(available_month) == 1: - available_month = available_month[0] - else: # empty list - available_month = None - - # Check if user indicates they are unavailable (has provided next available date) - has_available_year = available_year is not None - has_available_month = available_month is not None - - # If user has a load > 0, they should not specify unavailability - if current_load > 0 and (has_available_month or has_available_year): - raise openreview.OpenReviewException("Please only provide your next available year and month if you are unavailable this cycle. Click Cancel to reset these fields and fill out the form again.") - - # Both year and month should be N/A or both should have values - if (has_available_year and not has_available_month) or (not has_available_year and has_available_month): - raise openreview.OpenReviewException("Please provide both your next available year and month") - \ No newline at end of file diff --git a/openreview/arr/process/max_load_process.py b/openreview/arr/process/max_load_process.py deleted file mode 100644 index 852d2c15e5..0000000000 --- a/openreview/arr/process/max_load_process.py +++ /dev/null @@ -1,117 +0,0 @@ -def process(client, edit, invitation): - import time - - domain = client.get_group(invitation.domain) - venue_id = domain.id - submission_venue_id = domain.content['submission_venue_id']['value'] - meta_invitation_id = domain.content['meta_invitation_id']['value'] - venue_name = domain.content['title']['value'] - - CONFERENCE_ID = domain.id - SAC_ID = domain.content['senior_area_chairs_id']['value'] - AC_ID = domain.content['area_chairs_id']['value'] - REV_ID = domain.content['reviewers_id']['value'] - ETHICS_REV_ID = domain.content['ethics_chairs_id']['value'].replace( - domain.content['ethics_chairs_name']['value'], - domain.content['ethics_reviewers_name']['value'] - ) - user = client.get_profile(edit.signatures[0]).id - - edge_readers = [CONFERENCE_ID] - inv_role = invitation.id.split('/')[-3] - role = None - for venue_role in [SAC_ID, AC_ID, REV_ID, ETHICS_REV_ID]: - if f"/{inv_role}" in venue_role: - role = venue_role - if venue_role == AC_ID: - edge_readers += [SAC_ID] - elif venue_role == REV_ID: - edge_readers += [SAC_ID, AC_ID] - - if not role: - raise openreview.OpenReviewException('Invalid role for Custom Max Papers edges') - - edge_readers += [user] - - CUSTOM_MAX_PAPERS_ID = f"{role}/-/Custom_Max_Papers" - AVAILABILITY_ID = f"{role}/-/Reviewing_Resubmissions" - - # Handle SAC case separately - loads computed at matching time, - ## unavailable SACs can have 0 load immediately - if role == SAC_ID: - if 'will not be able to serve' in edit.note.content['availability_this_cycle']['value'].lower(): - client.post_edge( - openreview.api.Edge( - invitation=CUSTOM_MAX_PAPERS_ID, - writers=[CONFERENCE_ID], - signatures=[CONFERENCE_ID], - head=role, - tail=user, - weight=0 - ) - ) - return - - if edit.note.ddate: - client.delete_edges( - invitation=CUSTOM_MAX_PAPERS_ID, - head=role, - tail=user - ) - client.delete_edges( - invitation=AVAILABILITY_ID, - head=role, - tail=user - ) - return - - - client.delete_edges( - invitation=CUSTOM_MAX_PAPERS_ID, - head=role, - tail=user, - wait_to_finish=True, - soft_delete=True - ) - - client.post_edge( - openreview.api.Edge( - invitation=CUSTOM_MAX_PAPERS_ID, - writers=[CONFERENCE_ID], - signatures=[CONFERENCE_ID], - head=role, - tail=user, - weight=int(edit.note.content['maximum_load_this_cycle']['value']) - ) - ) - - if role == ETHICS_REV_ID: - return - - client.delete_edges( - invitation=AVAILABILITY_ID, - head=role, - tail=user, - wait_to_finish=True, - soft_delete=True - ) - - availability_label = None - if 'yes' in edit.note.content['maximum_load_this_cycle_for_resubmissions']['value'].lower() and int(edit.note.content['maximum_load_this_cycle']['value']) == 0: - availability_label = 'Only Reviewing Resubmissions' - elif 'yes' in edit.note.content['maximum_load_this_cycle_for_resubmissions']['value'].lower(): - availability_label = 'Yes' - elif 'no' in edit.note.content['maximum_load_this_cycle_for_resubmissions']['value'].lower(): - availability_label = 'No' - - client.post_edge( - openreview.api.Edge( - invitation=AVAILABILITY_ID, - writers=[CONFERENCE_ID], - signatures=[CONFERENCE_ID], - head=role, - tail=user, - label=availability_label - ) - ) - \ No newline at end of file From 95cab63e8fdee7d107d4554352ebc690ef6e3706 Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Wed, 17 Dec 2025 17:18:22 -0500 Subject: [PATCH 14/20] Modify webfields --- openreview/arr/webfield/areachairsWebfield.js | 7 ++- .../arr/webfield/ethicsReviewersWebfield.js | 27 ++++++++++++ .../arr/webfield/programChairsWebfield.js | 44 ++++++++++++++++--- openreview/arr/webfield/reviewersWebfield.js | 29 ++++++++++++ .../arr/webfield/seniorAreaChairsWebfield.js | 5 ++- 5 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 openreview/arr/webfield/ethicsReviewersWebfield.js create mode 100644 openreview/arr/webfield/reviewersWebfield.js diff --git a/openreview/arr/webfield/areachairsWebfield.js b/openreview/arr/webfield/areachairsWebfield.js index 0bb00cefb5..b595566d2c 100644 --- a/openreview/arr/webfield/areachairsWebfield.js +++ b/openreview/arr/webfield/areachairsWebfield.js @@ -91,6 +91,11 @@ return { anonReviewerName: domain.content.reviewers_anon_name?.value, preferredEmailInvitationId: preferredEmailInvitationId, ithenticateInvitationId: (domain.content.iThenticate_plagiarism_check_committee_readers?.value || []).includes(domain.content.area_chairs_name?.value) ? domain.content.iThenticate_plagiarism_check_invitation_id?.value : null, - additionalDomains: ["aclweb.org/ACL/ARR"] + registrationFormDomainMap: { + '/Area_Chairs/-/Registration': 'aclweb.org/ACL/ARR', + '/Area_Chairs/-/Max_Load_And_Unavailability_Request': 'aclweb.org/ACL/ARR', + '/Area_Chairs/-/License_Agreement': 'aclweb.org/ACL/ARR', + '/Area_Chairs/-/Emergency_Metareviewer_Agreement': 'aclweb.org/ACL/ARR' + }, } } diff --git a/openreview/arr/webfield/ethicsReviewersWebfield.js b/openreview/arr/webfield/ethicsReviewersWebfield.js new file mode 100644 index 0000000000..01db2d8c94 --- /dev/null +++ b/openreview/arr/webfield/ethicsReviewersWebfield.js @@ -0,0 +1,27 @@ +// Webfield component +return { + component: 'ReviewerConsole', + version: 1, + properties: { + header: { + title: 'Ethics Reviewer Console', + instructions: `

This page provides information and status updates for ${domain.content.subtitle?.value}. It will be regularly updated as the conference progresses, so please check back frequently.

` + }, + venueId: domain.id, + reviewerName: domain.content.ethics_reviewers_name?.value, + officialReviewName: domain.content.ethics_review_name?.value, + reviewRatingName: '', + areaChairName: domain.content.area_chairs_name?.value, + submissionName: domain.content.submission_name?.value, + submissionInvitationId: domain.content.submission_id?.value, + customMaxPapersInvitationId: `${domain.id}/${domain.content.ethics_reviewers_name}/-/Custom_Max_Papers`, + recruitmentInvitationId: `${domain.id}/${domain.content.ethics_reviewers_name}/-/Recruitment`, + reviewLoad: '', + hasPaperRanking: false, + registrationFormDomainMap: { + '/Ethics_Reviewers/-/Registration': 'aclweb.org/ACL/ARR', + '/Ethics_Reviewers/-/Max_Load_And_Unavailability_Request': 'aclweb.org/ACL/ARR' + }, + } + } + \ No newline at end of file diff --git a/openreview/arr/webfield/programChairsWebfield.js b/openreview/arr/webfield/programChairsWebfield.js index 1949e2cb99..3e1639c50f 100644 --- a/openreview/arr/webfield/programChairsWebfield.js +++ b/openreview/arr/webfield/programChairsWebfield.js @@ -120,12 +120,44 @@ return { ithenticateInvitationId: domain.content.iThenticate_plagiarism_check_invitation_id?.value, customMaxPapersName: 'Custom_Max_Papers', - additionalRegistrationDomains: ["aclweb.org/ACL/ARR"], - additionalRegistrationPrefixes: [ - "aclweb.org/ACL/ARR/Reviewers", - "aclweb.org/ACL/ARR/Area_Chairs", - "aclweb.org/ACL/ARR/Senior_Area_Chairs" - ], + registrationFormOverrides: { + '/Reviewers/-/Registration': { + domain: 'aclweb.org/ACL/ARR', + invitation: 'aclweb.org/ACL/ARR/Reviewers/-/Registration' + }, + '/Area_Chairs/-/Registration': { + domain: 'aclweb.org/ACL/ARR', + invitation: 'aclweb.org/ACL/ARR/Area_Chairs/-/Registration' + }, + '/Senior_Area_Chairs/-/Registration': { + domain: 'aclweb.org/ACL/ARR', + invitation: 'aclweb.org/ACL/ARR/Senior_Area_Chairs/-/Registration' + }, + '/Reviewers/-/Max_Load_And_Unavailability_Request': { + domain: 'aclweb.org/ACL/ARR', + invitation: 'aclweb.org/ACL/ARR/Reviewers/-/Max_Load_And_Unavailability_Request' + }, + '/Area_Chairs/-/Max_Load_And_Unavailability_Request': { + domain: 'aclweb.org/ACL/ARR', + invitation: 'aclweb.org/ACL/ARR/Area_Chairs/-/Max_Load_And_Unavailability_Request' + }, + '/Senior_Area_Chairs/-/Max_Load_And_Unavailability_Request': { + domain: 'aclweb.org/ACL/ARR', + invitation: 'aclweb.org/ACL/ARR/Senior_Area_Chairs/-/Max_Load_And_Unavailability_Request' + }, + '/Reviewers/-/License_Agreement': { + domain: 'aclweb.org/ACL/ARR', + invitation: 'aclweb.org/ACL/ARR/Reviewers/-/License_Agreement' + }, + '/Area_Chairs/-/License_Agreement': { + domain: 'aclweb.org/ACL/ARR', + invitation: 'aclweb.org/ACL/ARR/Area_Chairs/-/License_Agreement' + }, + '/Senior_Area_Chairs/-/License_Agreement': { + domain: 'aclweb.org/ACL/ARR', + invitation: 'aclweb.org/ACL/ARR/Senior_Area_Chairs/-/License_Agreement' + } + }, customStageInvitations: [ { name:'Action_Editor_Checklist', diff --git a/openreview/arr/webfield/reviewersWebfield.js b/openreview/arr/webfield/reviewersWebfield.js new file mode 100644 index 0000000000..3218cd677f --- /dev/null +++ b/openreview/arr/webfield/reviewersWebfield.js @@ -0,0 +1,29 @@ +// Webfield component +const committee_name = entity.id.split('/').slice(-1)[0].replaceAll('_', ' ') +return { + component: 'ReviewerConsole', + version: 1, + properties: { + header: { + title: `${committee_name} Console`, + instructions: `

This page provides information and status updates for ${domain.content.subtitle?.value}. It will be regularly updated as the conference progresses, so please check back frequently.

` + }, + venueId: domain.id, + reviewerName: domain.content.reviewers_name?.value, + officialReviewName: domain.content.review_name?.value, + reviewRatingName: domain.content.review_rating?.value, + areaChairName: domain.content.area_chairs_name?.value, + submissionName: domain.content.submission_name?.value, + submissionInvitationId: domain.content.submission_id?.value, + customMaxPapersInvitationId: domain.content.reviewers_custom_max_papers_id?.value, + recruitmentInvitationId: domain.content.reviewers_recruitment_id?.value, + reviewLoad: '', + hasPaperRanking: false, + registrationFormDomainMap: { + '/Reviewers/-/Registration': 'aclweb.org/ACL/ARR', + '/Reviewers/-/Max_Load_And_Unavailability_Request': 'aclweb.org/ACL/ARR', + '/Reviewers/-/License_Agreement': 'aclweb.org/ACL/ARR', + '/Reviewers/-/Emergency_Reviewer_Agreement': 'aclweb.org/ACL/ARR' + }, + } +} diff --git a/openreview/arr/webfield/seniorAreaChairsWebfield.js b/openreview/arr/webfield/seniorAreaChairsWebfield.js index ff988043e5..573c836348 100644 --- a/openreview/arr/webfield/seniorAreaChairsWebfield.js +++ b/openreview/arr/webfield/seniorAreaChairsWebfield.js @@ -122,6 +122,9 @@ return { return checklistReplies?.length??0; ` }, - additionalDomains: ["aclweb.org/ACL/ARR"] + registrationFormDomainMap: { + '/Senior_Area_Chairs/-/Registration': 'aclweb.org/ACL/ARR', + '/Senior_Area_Chairs/-/Max_Load_And_Unavailability_Request': 'aclweb.org/ACL/ARR' + }, } } \ No newline at end of file From e59058ace2af5b0dd5b1cc86ac05160dabad5273 Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Fri, 9 Jan 2026 16:36:18 -0500 Subject: [PATCH 15/20] Remove test for invitation --- tests/test_arr_venue_v2.py | 47 -------------------------------------- 1 file changed, 47 deletions(-) diff --git a/tests/test_arr_venue_v2.py b/tests/test_arr_venue_v2.py index b04f3a877b..0a1f62fffc 100644 --- a/tests/test_arr_venue_v2.py +++ b/tests/test_arr_venue_v2.py @@ -2428,7 +2428,6 @@ def test_submitted_author_form(self, client, openreview_client, helpers, test_cl helpers.await_queue() assert openreview_client.get_invitation('aclweb.org/ACL/ARR/2023/August/Authors/-/Submitted_Author_Form') - assert openreview_client.get_invitation('aclweb.org/ACL/ARR/2023/August/-/Register_Authors_To_Reviewers') test_client.post_note_edit( invitation=f"aclweb.org/ACL/ARR/2023/August/Authors/-/Submitted_Author_Form", @@ -2507,52 +2506,6 @@ def test_submitted_author_form(self, client, openreview_client, helpers, test_cl ) ) - # Test that the author was added to reviewers group with registration and license notes - openreview_client.post_invitation_edit( - invitations='aclweb.org/ACL/ARR/2023/August/-/Edit', - readers=['aclweb.org/ACL/ARR/2023/August'], - writers=['aclweb.org/ACL/ARR/2023/August'], - signatures=['aclweb.org/ACL/ARR/2023/August'], - invitation=openreview.api.Invitation( - id = f"aclweb.org/ACL/ARR/2023/August/-/Register_Authors_To_Reviewers", - content = { - 'authors': {'value': ['~SomeFirstName_User1']} - } - ) - ) - helpers.await_queue_edit(openreview_client, 'aclweb.org/ACL/ARR/2023/August/-/Register_Authors_To_Reviewers-0-1', count=2) - - assert '~SomeFirstName_User1' in pc_client_v2.get_group('aclweb.org/ACL/ARR/2023/August/Reviewers').members - registration_notes = pc_client_v2.get_all_notes(invitation=f'aclweb.org/ACL/ARR/2023/August/Reviewers/-/Registration', signatures=['~SomeFirstName_User1']) - assert len(registration_notes) == 1 - - license_notes = pc_client_v2.get_all_notes(invitation=f'aclweb.org/ACL/ARR/2023/August/Reviewers/-/License_Agreement', signatures=['~SomeFirstName_User1']) - assert len(license_notes) == 1 - - reviewer_load_notes = pc_client_v2.get_all_notes(invitation=f'aclweb.org/ACL/ARR/2023/August/Reviewers/-/Max_Load_And_Unavailability_Request', signatures=['~SomeFirstName_User1']) - assert len(reviewer_load_notes) == 1 - - reviewer_load_note = reviewer_load_notes[0] - assert reviewer_load_note.content['maximum_load_this_cycle']['value'] == 4 - assert reviewer_load_note.content['maximum_load_this_cycle_for_resubmissions']['value'] == 'No' - assert reviewer_load_note.content['meta_data_donation']['value'] == "Yes, I consent to donating anonymous metadata of my review for research." - - # Clean up data by removing test user from group and deleting notes - pc_client_v2.remove_members_from_group( - group='aclweb.org/ACL/ARR/2023/August/Reviewers', - members=['~SomeFirstName_User1'] - ) - for note in registration_notes + license_notes + reviewer_load_notes: - openreview_client.post_note_edit( - invitation=note.invitations[0], - signatures=['~SomeFirstName_User1'], - note=openreview.api.Note( - id=note.id, - content=note.content, - ddate=openreview.tools.datetime_millis(datetime.datetime.now()) - ) - ) - # Manually add new field openreview_client.post_invitation_edit( invitations='aclweb.org/ACL/ARR/2023/August/-/Edit', From 89f7457ef871b0f39b77be376fd9b13720fb70ab Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Fri, 9 Jan 2026 16:36:24 -0500 Subject: [PATCH 16/20] Remove process function --- .../management/setup_authors_to_reviewers.py | 158 ------------------ 1 file changed, 158 deletions(-) delete mode 100644 openreview/arr/management/setup_authors_to_reviewers.py diff --git a/openreview/arr/management/setup_authors_to_reviewers.py b/openreview/arr/management/setup_authors_to_reviewers.py deleted file mode 100644 index 0f0f3e89f6..0000000000 --- a/openreview/arr/management/setup_authors_to_reviewers.py +++ /dev/null @@ -1,158 +0,0 @@ -def process(client, invitation): - - now = openreview.tools.datetime_millis(datetime.datetime.now()) - cdate = invitation.cdate - - if cdate > now: - ## invitation is in the future, do not process - print('invitation is not yet active and no child invitations created', cdate) - return - - from openreview.venue import matching - from openreview.arr.invitation import InvitationBuilder - import random - import string - - domain = client.get_group(invitation.domain) - venue_id = domain.id - request_form_id = domain.content['request_form_id']['value'] - meta_invitation_id = domain.content['meta_invitation_id']['value'] - - client_v1 = openreview.Client( - baseurl=openreview.tools.get_base_urls(client)[0], - token=client.token - ) - - request_form = client_v1.get_note(request_form_id) - support_group = request_form.invitation.split('/-/')[0] - venue = openreview.helpers.get_conference(client_v1, request_form_id, support_group) - reviewers_group = client.get_group(venue.get_reviewers_id()) - - # Load authors submitted forms - author_forms = client.get_all_notes( - invitation=f"{venue.get_authors_id()}/-/{InvitationBuilder.SUBMITTED_AUTHORS_NAME}" - ) - # Load existing reviewer registration and license forms - reviewer_forms = client.get_all_notes( - invitation=f"{venue.get_reviewers_id()}/-/{InvitationBuilder.REGISTRATION_NAME}" - ) - reviewer_license_forms = client.get_all_notes( - invitation=f"{venue.get_reviewers_id()}/-/{InvitationBuilder.REVIEWER_LICENSE_NAME}" - ) - reviewer_load_forms = client.get_all_notes( - invitation=f"{venue.get_reviewers_id()}/-/{InvitationBuilder.MAX_LOAD_AND_UNAVAILABILITY_NAME}" - ) - - # Load profiles from author and reviewer signatures - author_signatures = [n.signatures[0] for n in author_forms] - reviewer_signatures = [n.signatures[0] for n in reviewer_forms] - reviewer_license_signatures = [n.signatures[0] for n in reviewer_license_forms] - reviewer_load_signatures = [n.signatures[0] for n in reviewer_load_forms] - all_profiles = openreview.tools.get_profiles(client, author_signatures + reviewer_signatures + reviewer_license_signatures + reviewer_load_signatures) - name_to_id = {} - for profile in all_profiles: - filtered_names = filter( - lambda obj: 'username' in obj and len(obj['username']) > 0, - profile.content.get('names', []) - ) - for name_obj in filtered_names: - name_to_id[name_obj['username']] = profile.id - submitted_reviewer_ids = set(name_to_id[n.signatures[0]] for n in reviewer_forms) - submitted_reviewer_license_ids = set(name_to_id[n.signatures[0]] for n in reviewer_license_forms) - submitted_reviewer_load_ids = set(name_to_id[n.signatures[0]] for n in reviewer_load_forms) - # author_form -> registration form - DEFAULT_REGISTRATION_CONTENT = { - 'profile_confirmed': 'Yes', - 'expertise_confirmed': 'Yes', - 'domains': 'Yes', - 'emails': 'Yes', - 'DBLP': 'Yes', - 'semantic_scholar': 'Yes' - } - REGISTRATION_FORM_MAPPING = { - "confirm_your_profile_has_past_domains": "domains", - "confirm_your_profile_has_all_email_addresses": "emails", - "indicate_languages_you_study": "languages_studied", - "indicate_your_research_areas": "research_area", - "confirm_your_openreview_profile_contains_a_dblp_link": "DBLP", # DBLP should just be ticked - "confirm_your_openreview_profile_contains_a_semantic_scholar_link": "semantic_scholar", # This should also be ticked - } - # author_form -> license_form - LICENSE_FORM_MAPPING = { - "agreement": "agreement", - "attribution": "attribution", - } - - # Filter by authors in the invitation content - ## assume authors in invitations are profile IDs - author_names = invitation.content.get('authors', {}).get('value', []) - author_forms = [a for a in author_forms if name_to_id[a.signatures[0]] in author_names] - - for author_form in author_forms: - author_id = name_to_id[author_form.signatures[0]] - print(f"Processing author {author_id}...") - # Add author to reviewers group - if author_id not in reviewers_group.members: - print(f"Adding author {author_id} to reviewers group") - client.add_members_to_group(reviewers_group, author_id) - - # Copy to registration - if author_id not in submitted_reviewer_ids and \ - 'indicate_your_research_areas' in author_form.content: ## This is required in registration - template_registration_content = { - 'profile_confirmed': { 'value': 'Yes' }, - 'expertise_confirmed': { 'value': 'Yes' } - } - for key, value in REGISTRATION_FORM_MAPPING.items(): - if key in author_form.content: - # Handle DBLP and Semantic Scholar special cases - if 'dblp' in key or 'semantic_scholar' in key: - template_registration_content[value] = { 'value': 'Yes' } - else: - template_registration_content[value] = author_form.content[key] - print(f"Copying registration content to {author_id}") - client.post_note_edit( - invitation=f"{venue.get_reviewers_id()}/-/{InvitationBuilder.REGISTRATION_NAME}", - signatures=[author_id], - note=openreview.api.Note( - content=template_registration_content - ) - ) - - # Copy to license - if author_id not in submitted_reviewer_license_ids: - template_license_content = {} - for key, value in LICENSE_FORM_MAPPING.items(): - if key in author_form.content: - template_license_content[value] = author_form.content[key] - print(f"Copying license content to {author_id}") - client.post_note_edit( - invitation=f"{venue.get_reviewers_id()}/-/{InvitationBuilder.REVIEWER_LICENSE_NAME}", - signatures=[author_id], - note=openreview.api.Note( - content=template_license_content - ) - ) - - # Copy to reviewer load - if author_id not in submitted_reviewer_load_ids: - meta_data_donation = None - if 'meta_data_donation' in author_form.content: - if 'yes' in author_form.content['meta_data_donation']['value'].lower(): - meta_data_donation = {'value': "Yes, I consent to donating anonymous metadata of my review for research."} - else: - meta_data_donation = {'value': "No, I do not consent to donating anonymous metadata of my review for research."} - else: - meta_data_donation = {'value': "No, I do not consent to donating anonymous metadata of my review for research."} - client.post_note_edit( - invitation=f"{venue.get_reviewers_id()}/-/{InvitationBuilder.MAX_LOAD_AND_UNAVAILABILITY_NAME}", - signatures=[author_id], - note=openreview.api.Note( - content={ - 'maximum_load_this_cycle': { 'value': 4 }, ## TODO: Remove this hardcoded value - 'maximum_load_this_cycle_for_resubmissions': { 'value': 'No' }, - 'meta_data_donation': meta_data_donation - } - ) - ) - From 777fcc195f10dc4a247a4d17e87ed345f9f263a8 Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Fri, 9 Jan 2026 16:36:34 -0500 Subject: [PATCH 17/20] Prevent invitation creation --- openreview/arr/helpers.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/openreview/arr/helpers.py b/openreview/arr/helpers.py index b8181ad658..2388c947aa 100644 --- a/openreview/arr/helpers.py +++ b/openreview/arr/helpers.py @@ -612,14 +612,6 @@ def __init__(self, client_v2, venue, configuration_note, request_form_id, suppor start_date=self.configuration_note.content.get('setup_shared_data_date'), process='management/setup_shared_data.py' ), - ARRStage( - type=ARRStage.Type.PROCESS_INVITATION, - required_fields=[], - super_invitation_id=f"{self.venue_id}/-/Register_Authors_To_Reviewers", - stage_arguments={}, - process='management/setup_authors_to_reviewers.py', - ignore_dates=['cdate'] - ), ARRStage( type=ARRStage.Type.PROCESS_INVITATION, required_fields=[], From c1a255b0794b9f24d2be51527fd8a386d25750f9 Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Mon, 12 Jan 2026 14:02:21 -0500 Subject: [PATCH 18/20] Use registrationFormInvitations --- .../arr/webfield/programChairsWebfield.js | 51 +++++-------------- 1 file changed, 13 insertions(+), 38 deletions(-) diff --git a/openreview/arr/webfield/programChairsWebfield.js b/openreview/arr/webfield/programChairsWebfield.js index b23187da5c..8ac89edef0 100644 --- a/openreview/arr/webfield/programChairsWebfield.js +++ b/openreview/arr/webfield/programChairsWebfield.js @@ -120,44 +120,19 @@ return { ithenticateInvitationId: domain.content.iThenticate_plagiarism_check_invitation_id?.value, customMaxPapersName: 'Custom_Max_Papers', - registrationFormOverrides: { - '/Reviewers/-/Registration': { - domain: 'aclweb.org/ACL/ARR', - invitation: 'aclweb.org/ACL/ARR/Reviewers/-/Registration' - }, - '/Area_Chairs/-/Registration': { - domain: 'aclweb.org/ACL/ARR', - invitation: 'aclweb.org/ACL/ARR/Area_Chairs/-/Registration' - }, - '/Senior_Area_Chairs/-/Registration': { - domain: 'aclweb.org/ACL/ARR', - invitation: 'aclweb.org/ACL/ARR/Senior_Area_Chairs/-/Registration' - }, - '/Reviewers/-/Max_Load_And_Unavailability_Request': { - domain: 'aclweb.org/ACL/ARR', - invitation: 'aclweb.org/ACL/ARR/Reviewers/-/Max_Load_And_Unavailability_Request' - }, - '/Area_Chairs/-/Max_Load_And_Unavailability_Request': { - domain: 'aclweb.org/ACL/ARR', - invitation: 'aclweb.org/ACL/ARR/Area_Chairs/-/Max_Load_And_Unavailability_Request' - }, - '/Senior_Area_Chairs/-/Max_Load_And_Unavailability_Request': { - domain: 'aclweb.org/ACL/ARR', - invitation: 'aclweb.org/ACL/ARR/Senior_Area_Chairs/-/Max_Load_And_Unavailability_Request' - }, - '/Reviewers/-/License_Agreement': { - domain: 'aclweb.org/ACL/ARR', - invitation: 'aclweb.org/ACL/ARR/Reviewers/-/License_Agreement' - }, - '/Area_Chairs/-/License_Agreement': { - domain: 'aclweb.org/ACL/ARR', - invitation: 'aclweb.org/ACL/ARR/Area_Chairs/-/License_Agreement' - }, - '/Senior_Area_Chairs/-/License_Agreement': { - domain: 'aclweb.org/ACL/ARR', - invitation: 'aclweb.org/ACL/ARR/Senior_Area_Chairs/-/License_Agreement' - } - }, + registrationFormInvitations: [ + 'aclweb.org/ACL/ARR/Reviewers/-/Registration_Form', + 'aclweb.org/ACL/ARR/Area_Chairs/-/Registration_Form', + 'aclweb.org/ACL/ARR/Senior_Area_Chairs/-/Registration_Form', + 'aclweb.org/ACL/ARR/Reviewers/-/Max_Load_And_Unavailability_Request_Form', + 'aclweb.org/ACL/ARR/Area_Chairs/-/Max_Load_And_Unavailability_Request_Form', + 'aclweb.org/ACL/ARR/Senior_Area_Chairs/-/Max_Load_And_Unavailability_Request_Form', + 'aclweb.org/ACL/ARR/Reviewers/-/License_Agreement_Form', + 'aclweb.org/ACL/ARR/Area_Chairs/-/License_Agreement_Form', + 'aclweb.org/ACL/ARR/Senior_Area_Chairs/-/License_Agreement_Form', + `${domain.id}/Reviewers/-/Emergency_Reviewer_Agreement_Form`, + `${domain.id}/Area_Chairs/-/Emergency_Metareviewer_Agreement_Form`, + ], customStageInvitations: [ { name:'Action_Editor_Checklist', From 9acb15dfcc064e11572a1b370b2105ecb61a08bd Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Mon, 12 Jan 2026 14:02:42 -0500 Subject: [PATCH 19/20] Remove cycle maximum load --- tests/test_arr_venue_v2.py | 75 +------------------------------------- 1 file changed, 2 insertions(+), 73 deletions(-) diff --git a/tests/test_arr_venue_v2.py b/tests/test_arr_venue_v2.py index 11c6c20460..a111b5d681 100644 --- a/tests/test_arr_venue_v2.py +++ b/tests/test_arr_venue_v2.py @@ -684,8 +684,6 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req 'author_consent_start_date': (now).strftime('%Y/%m/%d %H:%M'), 'author_consent_end_date': (due_date).strftime('%Y/%m/%d %H:%M'), 'registration_due_date': (due_date).strftime('%Y/%m/%d %H:%M'), - 'maximum_load_due_date': (due_date).strftime('%Y/%m/%d %H:%M'), - 'maximum_load_exp_date': (due_date).strftime('%Y/%m/%d %H:%M'), 'recognition_form_due_date': (due_date).strftime('%Y/%m/%d %H:%M'), 'license_agreement_due_date': (due_date).strftime('%Y/%m/%d %H:%M'), 'ae_checklist_start_date': (now).strftime('%Y/%m/%d %H:%M'), @@ -719,60 +717,11 @@ def test_august_cycle(self, client, openreview_client, helpers, test_client, req helpers.await_queue() # Check duedates for registration stages - assert openreview_client.get_invitation('aclweb.org/ACL/ARR/2023/August/Reviewers/-/Registration').duedate > 0 - assert openreview_client.get_invitation('aclweb.org/ACL/ARR/2023/August/Area_Chairs/-/Registration').duedate > 0 - assert openreview_client.get_invitation('aclweb.org/ACL/ARR/2023/August/Senior_Area_Chairs/-/Registration').duedate > 0 assert openreview_client.get_invitation('aclweb.org/ACL/ARR/2023/August/Reviewers/-/Recognition_Request').duedate > 0 assert openreview_client.get_invitation('aclweb.org/ACL/ARR/2023/August/Area_Chairs/-/Recognition_Request').duedate > 0 assert openreview_client.get_invitation('aclweb.org/ACL/ARR/2023/August/Reviewers/-/License_Agreement').duedate > 0 assert openreview_client.get_invitation('aclweb.org/ACL/ARR/2023/August/Area_Chairs/-/Metareview_License_Agreement').duedate > 0 - # Pin 2023 and 2024 into next available year - task_array = [ - arr_reviewer_max_load_task, - arr_ac_max_load_task, - arr_sac_max_load_task, - ] - venue_roles = [ - venue.get_reviewers_id(), - venue.get_area_chairs_id(), - venue.get_senior_area_chairs_id() - ] - - assert openreview_client.get_invitation('aclweb.org/ACL/ARR/2023/August/Reviewers/-/Max_Load_And_Unavailability_Request') - assert openreview_client.get_invitation('aclweb.org/ACL/ARR/2023/August/Area_Chairs/-/Max_Load_And_Unavailability_Request') - assert openreview_client.get_invitation('aclweb.org/ACL/ARR/2023/August/Senior_Area_Chairs/-/Max_Load_And_Unavailability_Request') - - for role, task_field in zip(venue_roles, task_array): - m = matching.Matching(venue, venue.client.get_group(role), None, None) - m._create_edge_invitation(venue.get_custom_max_papers_id(m.match_group.id)) - - openreview_client.post_invitation_edit( - invitations=venue.get_meta_invitation_id(), - readers=[venue.id], - writers=[venue.id], - signatures=[venue.id], - invitation=openreview.api.Invitation( - id=f"{role}/-/{max_load_name}", - edit={ - 'note': { - 'content':{ - 'next_available_year': { - 'value': { - 'param': { - "input": "checkbox", - "optional": True, - "type": "integer", - 'enum' : list(set([2022, 2023, 2024] + task_field['next_available_year']['value']['param']['enum'])) - } - } - } - } - } - } - ) - ) - def test_june_cycle(self, client, openreview_client, helpers, test_client): # Build the previous cycle pc_client=openreview.Client(username='pc@aclrollingreview.org', password=helpers.strong_password) @@ -1381,8 +1330,6 @@ def test_submission_preprocess(self, client, openreview_client, test_client, hel 'form_expiration_date': (due_date).strftime('%Y/%m/%d %H:%M'), 'author_consent_start_date': (now).strftime('%Y/%m/%d %H:%M'), 'author_consent_end_date': (due_date).strftime('%Y/%m/%d %H:%M'), - 'maximum_load_due_date': (due_date).strftime('%Y/%m/%d %H:%M'), - 'maximum_load_exp_date': (due_date).strftime('%Y/%m/%d %H:%M'), 'ae_checklist_start_date': (now).strftime('%Y/%m/%d %H:%M'), 'ae_checklist_due_date': (due_date).strftime('%Y/%m/%d %H:%M'), 'ae_checklist_exp_date': (due_date).strftime('%Y/%m/%d %H:%M'), @@ -5109,14 +5056,7 @@ def test_changing_deadlines(self, client, openreview_client, helpers, test_clien now = datetime.datetime.now() due_date = now + datetime.timedelta(days=5) - # Original due dates were at +3, now at +5 - reviewer_max_load_due_date = openreview_client.get_invitation(f'aclweb.org/ACL/ARR/2023/August/Reviewers/-/{max_load_name}').duedate - ac_max_load_due_date = openreview_client.get_invitation(f'aclweb.org/ACL/ARR/2023/August/Area_Chairs/-/{max_load_name}').duedate - sac_max_load_due_date = openreview_client.get_invitation(f'aclweb.org/ACL/ARR/2023/August/Senior_Area_Chairs/-/{max_load_name}').duedate - - reviewer_max_load_exp_date = openreview_client.get_invitation(f'aclweb.org/ACL/ARR/2023/August/Reviewers/-/{max_load_name}').expdate - ac_max_load_exp_date = openreview_client.get_invitation(f'aclweb.org/ACL/ARR/2023/August/Area_Chairs/-/{max_load_name}').expdate - sac_max_load_exp_date = openreview_client.get_invitation(f'aclweb.org/ACL/ARR/2023/August/Senior_Area_Chairs/-/{max_load_name}').expdate + # Original due dates were at +3, now at +5client.get_invitation(f'aclweb.org/ACL/ARR/2023/August/Senior_Area_Chairs/-/{max_load_name}').expdate reviewer_checklist_due_date = openreview_client.get_invitation(f'aclweb.org/ACL/ARR/2023/August/-/Reviewer_Checklist').edit['invitation']['duedate'] reviewer_checklist_exp_date = openreview_client.get_invitation(f'aclweb.org/ACL/ARR/2023/August/-/Reviewer_Checklist').edit['invitation']['expdate'] @@ -5134,8 +5074,6 @@ def test_changing_deadlines(self, client, openreview_client, helpers, test_clien openreview.Note( content={ 'form_expiration_date': (due_date).strftime('%Y/%m/%d %H:%M'), - 'maximum_load_due_date': (due_date).strftime('%Y/%m/%d %H:%M'), - 'maximum_load_exp_date': (due_date).strftime('%Y/%m/%d %H:%M'), 'ae_checklist_start_date': (now).strftime('%Y/%m/%d %H:%M'), 'ae_checklist_due_date': (due_date).strftime('%Y/%m/%d %H:%M'), 'ae_checklist_exp_date': (due_date).strftime('%Y/%m/%d %H:%M'), @@ -5161,15 +5099,6 @@ def test_changing_deadlines(self, client, openreview_client, helpers, test_clien helpers.await_queue() - assert openreview_client.get_invitation(f'aclweb.org/ACL/ARR/2023/August/Reviewers/-/{max_load_name}').duedate > reviewer_max_load_due_date - assert openreview_client.get_invitation(f'aclweb.org/ACL/ARR/2023/August/Reviewers/-/{max_load_name}').expdate > reviewer_max_load_exp_date - - assert openreview_client.get_invitation(f'aclweb.org/ACL/ARR/2023/August/Area_Chairs/-/{max_load_name}').duedate > ac_max_load_due_date - assert openreview_client.get_invitation(f'aclweb.org/ACL/ARR/2023/August/Area_Chairs/-/{max_load_name}').expdate > ac_max_load_exp_date - - assert openreview_client.get_invitation(f'aclweb.org/ACL/ARR/2023/August/Senior_Area_Chairs/-/{max_load_name}').duedate > sac_max_load_due_date - assert openreview_client.get_invitation(f'aclweb.org/ACL/ARR/2023/August/Senior_Area_Chairs/-/{max_load_name}').expdate > sac_max_load_exp_date - assert openreview_client.get_invitation(f'aclweb.org/ACL/ARR/2023/August/-/Reviewer_Checklist').edit['invitation']['duedate'] > reviewer_checklist_due_date assert openreview_client.get_invitation(f'aclweb.org/ACL/ARR/2023/August/-/Reviewer_Checklist').edit['invitation']['expdate'] > reviewer_checklist_exp_date @@ -5904,7 +5833,7 @@ def test_email_options(self, client, openreview_client, helpers, test_client, re rev_client = openreview.api.OpenReviewClient(username = 'reviewer7@aclrollingreview.com', password=helpers.strong_password) rev_two_client = openreview.api.OpenReviewClient(username = 'reviewer2@aclrollingreview.com', password=helpers.strong_password) rev_client.post_note_edit( - invitation='aclweb.org/ACL/ARR/2023/August/Reviewers/-/Max_Load_And_Unavailability_Request', + invitation='aclweb.org/ACL/ARR/Reviewers/-/Max_Load_And_Unavailability_Request', signatures=['~Reviewer_ARRSeven1'], note=openreview.api.Note( content = { From 2568cf6af280fdde825480669a08c7bf89c224f4 Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Wed, 4 Feb 2026 08:40:59 -0500 Subject: [PATCH 20/20] Use registrationFormInvitations array --- openreview/arr/webfield/areachairsWebfield.js | 12 ++++++------ openreview/arr/webfield/ethicsReviewersWebfield.js | 8 ++++---- openreview/arr/webfield/reviewersWebfield.js | 12 ++++++------ openreview/arr/webfield/seniorAreaChairsWebfield.js | 8 ++++---- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/openreview/arr/webfield/areachairsWebfield.js b/openreview/arr/webfield/areachairsWebfield.js index b595566d2c..af98038820 100644 --- a/openreview/arr/webfield/areachairsWebfield.js +++ b/openreview/arr/webfield/areachairsWebfield.js @@ -91,11 +91,11 @@ return { anonReviewerName: domain.content.reviewers_anon_name?.value, preferredEmailInvitationId: preferredEmailInvitationId, ithenticateInvitationId: (domain.content.iThenticate_plagiarism_check_committee_readers?.value || []).includes(domain.content.area_chairs_name?.value) ? domain.content.iThenticate_plagiarism_check_invitation_id?.value : null, - registrationFormDomainMap: { - '/Area_Chairs/-/Registration': 'aclweb.org/ACL/ARR', - '/Area_Chairs/-/Max_Load_And_Unavailability_Request': 'aclweb.org/ACL/ARR', - '/Area_Chairs/-/License_Agreement': 'aclweb.org/ACL/ARR', - '/Area_Chairs/-/Emergency_Metareviewer_Agreement': 'aclweb.org/ACL/ARR' - }, + registrationFormInvitations: [ + 'aclweb.org/ACL/ARR/Area_Chairs/-/Registration', + 'aclweb.org/ACL/ARR/Area_Chairs/-/Max_Load_And_Unavailability_Request', + 'aclweb.org/ACL/ARR/Area_Chairs/-/License_Agreement', + `${domain.id}/Area_Chairs/-/Emergency_Metareviewer_Agreement`, + ], } } diff --git a/openreview/arr/webfield/ethicsReviewersWebfield.js b/openreview/arr/webfield/ethicsReviewersWebfield.js index 01db2d8c94..7bb6bfd9e6 100644 --- a/openreview/arr/webfield/ethicsReviewersWebfield.js +++ b/openreview/arr/webfield/ethicsReviewersWebfield.js @@ -18,10 +18,10 @@ return { recruitmentInvitationId: `${domain.id}/${domain.content.ethics_reviewers_name}/-/Recruitment`, reviewLoad: '', hasPaperRanking: false, - registrationFormDomainMap: { - '/Ethics_Reviewers/-/Registration': 'aclweb.org/ACL/ARR', - '/Ethics_Reviewers/-/Max_Load_And_Unavailability_Request': 'aclweb.org/ACL/ARR' - }, + registrationFormInvitations: [ + 'aclweb.org/ACL/ARR/Ethics_Reviewers/-/Registration', + 'aclweb.org/ACL/ARR/Ethics_Reviewers/-/Max_Load_And_Unavailability_Request', + ], } } \ No newline at end of file diff --git a/openreview/arr/webfield/reviewersWebfield.js b/openreview/arr/webfield/reviewersWebfield.js index 3218cd677f..a051634e27 100644 --- a/openreview/arr/webfield/reviewersWebfield.js +++ b/openreview/arr/webfield/reviewersWebfield.js @@ -19,11 +19,11 @@ return { recruitmentInvitationId: domain.content.reviewers_recruitment_id?.value, reviewLoad: '', hasPaperRanking: false, - registrationFormDomainMap: { - '/Reviewers/-/Registration': 'aclweb.org/ACL/ARR', - '/Reviewers/-/Max_Load_And_Unavailability_Request': 'aclweb.org/ACL/ARR', - '/Reviewers/-/License_Agreement': 'aclweb.org/ACL/ARR', - '/Reviewers/-/Emergency_Reviewer_Agreement': 'aclweb.org/ACL/ARR' - }, + registrationFormInvitations: [ + 'aclweb.org/ACL/ARR/Reviewers/-/Registration', + 'aclweb.org/ACL/ARR/Reviewers/-/Max_Load_And_Unavailability_Request', + 'aclweb.org/ACL/ARR/Reviewers/-/License_Agreement', + `${domain.id}/Reviewers/-/Emergency_Reviewer_Agreement`, + ], } } diff --git a/openreview/arr/webfield/seniorAreaChairsWebfield.js b/openreview/arr/webfield/seniorAreaChairsWebfield.js index d526dd4d06..0302b84194 100644 --- a/openreview/arr/webfield/seniorAreaChairsWebfield.js +++ b/openreview/arr/webfield/seniorAreaChairsWebfield.js @@ -130,9 +130,9 @@ return { return metaReviewReplies?.length??0; ` }, - registrationFormDomainMap: { - '/Senior_Area_Chairs/-/Registration': 'aclweb.org/ACL/ARR', - '/Senior_Area_Chairs/-/Max_Load_And_Unavailability_Request': 'aclweb.org/ACL/ARR' - }, + registrationFormInvitations: [ + 'aclweb.org/ACL/ARR/Senior_Area_Chairs/-/Registration', + 'aclweb.org/ACL/ARR/Senior_Area_Chairs/-/Max_Load_And_Unavailability_Request', + ], } } \ No newline at end of file