diff --git a/openreview/api/__init__.py b/openreview/api/__init__.py index 29e9d1a1c..454415a12 100644 --- a/openreview/api/__init__.py +++ b/openreview/api/__init__.py @@ -4,3 +4,4 @@ from .client import Invitation from .client import Edge from .client import Group +from .client import Tag diff --git a/openreview/api/client.py b/openreview/api/client.py index 0d5fc38e7..48844c423 100644 --- a/openreview/api/client.py +++ b/openreview/api/client.py @@ -1374,7 +1374,22 @@ def get_group_edit(self, id): n = response.json()['edits'][0] return Edit.from_json(n) - def get_tags(self, id = None, invitation = None, forum = None, signature = None, tag = None, limit = None, offset = None, with_count=False): + def post_tag(self, tag): + """ + Posts the tag. + + :param tag: Tag to be posted + :type tag: Tag + + :return Tag: The posted Tag + """ + response = self.session.post(self.tags_url, json = tag.to_json(), headers = self.headers) + response = self.__handle_response(response) + + return Tag.from_json(response.json()) + + + def get_tags(self, id = None, invitation = None, forum = None, signature = None, tag = None, limit = None, offset = None, with_count=False, mintmdate=None): """ Gets a list of Tag objects based on the filters provided. The Tags that will be returned match all the criteria passed in the parameters. @@ -1404,6 +1419,8 @@ def get_tags(self, id = None, invitation = None, forum = None, signature = None, params['limit'] = limit if offset is not None: params['offset'] = offset + if mintmdate is not None: + params['mintmdate'] = mintmdate response = self.session.get(self.tags_url, params=tools.format_params(params), headers = self.headers) response = self.__handle_response(response) @@ -3063,6 +3080,123 @@ def transform_to_anon_ids(self, elements): elements[index] = self.anon_members[self.members.index(element)] else: elements[index] = element - return elements + return elements + + +class Tag(object): + """ + :param tag: Content of the tag + :type tag: str + :param invitation: Invitation id + :type invitation: str + :param readers: List of readers in the Invitation, each reader is a Group id + :type readers: list[str] + :param signatures: List of signatures in the Invitation, each signature is a Group id + :type signatures: list[str] + :param id: Tag id + :type id: str, optional + :param cdate: Creation date + :type cdate: int, optional + :param tcdate: True creation date + :type tcdate: int, optional + :param ddate: Deletion date + :type ddate: int, optional + :param forum: Forum id + :type forum: str, optional + :param replyto: Note id + :type replyto: list[str], optional + :param nonreaders: List of nonreaders in the Invitation, each nonreader is a Group id + :type nonreaders: list[str], optional + """ + def __init__(self, tag, invitation, signatures, readers=None, id=None, cdate=None, tcdate=None, tmdate=None, ddate=None, forum=None, replyto=None, nonreaders=None): + self.id = id + self.cdate = cdate + self.tcdate = tcdate + self.tmdate = tmdate + self.ddate = ddate + self.tag = tag + self.forum = forum + self.invitation = invitation + self.replyto = replyto + self.readers = readers + self.nonreaders = [] if nonreaders is None else nonreaders + self.signatures = signatures + + def to_json(self): + """ + Converts Tag instance to a dictionary. The instance variable names are the keys and their values the values of the dictinary. + + :return: Dictionary containing all the parameters of a Tag instance + :rtype: dict + """ + + body = {} + + if self.id: + body['id'] = self.id + + if self.cdate: + body['cdate'] = self.cdate + + if self.ddate: + body['ddate'] = self.ddate + + if self.tag: + body['tag'] = self.tag + + if self.forum: + body['forum'] = self.forum + + if self.invitation: + body['invitation'] = self.invitation + + if self.replyto: + body['replyto'] = self.replyto + + if self.readers: + body['readers'] = self.readers + + if self.nonreaders: + body['nonreaders'] = self.nonreaders + + if self.signatures: + body['signatures'] = self.signatures + + return body + + @classmethod + def from_json(Tag, t): + """ + Creates a Tag object from a dictionary that contains keys values equivalent to the name of the instance variables of the Tag class + + :param n: Dictionary containing key-value pairs, where the keys values are equivalent to the name of the instance variables in the Tag class + :type n: dict + + :return: Tag whose instance variables contain the values from the dictionary + :rtype: Tag + """ + tag = Tag( + id = t.get('id'), + cdate = t.get('cdate'), + tcdate = t.get('tcdate'), + tmdate = t.get('tmdate'), + ddate = t.get('ddate'), + tag = t.get('tag'), + forum = t.get('forum'), + invitation = t.get('invitation'), + replyto = t.get('replyto'), + readers = t.get('readers'), + nonreaders = t.get('nonreaders'), + signatures = t.get('signatures'), + ) + return tag + + def __repr__(self): + content = ','.join([("%s = %r" % (attr, value)) for attr, value in vars(self).items()]) + return 'Tag(' + content + ')' + + def __str__(self): + pp = pprint.PrettyPrinter() + return pp.pformat(vars(self)) diff --git a/openreview/conference/helpers.py b/openreview/conference/helpers.py index a366d979b..b0676e4a9 100644 --- a/openreview/conference/helpers.py +++ b/openreview/conference/helpers.py @@ -928,6 +928,8 @@ def get_comment_stage(request_forum): email_pcs = request_forum.content.get('email_program_chairs_about_official_comments', '') == 'Yes, email PCs for each official comment made in the venue' + enable_chat = request_forum.content.get('enable_chat_between_committee_members', '') == 'Yes, enable chat between committee members' + return openreview.stages.CommentStage( start_date=commentary_start_date, end_date=commentary_end_date, @@ -937,7 +939,8 @@ def get_comment_stage(request_forum): email_pcs=email_pcs, check_mandatory_readers=True, readers=readers, - invitees=invitees + invitees=invitees, + enable_chat=enable_chat ) def get_registration_stages(request_forum, venue): diff --git a/openreview/stages/venue_stages.py b/openreview/stages/venue_stages.py index 17412b646..1e87e7347 100644 --- a/openreview/stages/venue_stages.py +++ b/openreview/stages/venue_stages.py @@ -960,7 +960,8 @@ def __init__(self, only_accepted=False, check_mandatory_readers=False, readers=[], - invitees=[]): + invitees=[], + enable_chat=False): self.official_comment_name = official_comment_name if official_comment_name else 'Official_Comment' self.public_name = 'Public_Comment' @@ -974,6 +975,7 @@ def __init__(self, self.check_mandatory_readers=check_mandatory_readers self.readers = readers self.invitees = invitees + self.enable_chat = enable_chat def get_readers(self, conference, number, api_version='1'): @@ -1072,6 +1074,59 @@ def get_invitees(self, conference, number): return invitees + def get_chat_invitees(self, conference, number): + invitees = [conference.get_id(), conference.support_user] + + if conference.use_senior_area_chairs and self.Readers.SENIOR_AREA_CHAIRS_ASSIGNED in self.invitees: + invitees.append(conference.get_senior_area_chairs_id(number)) + + if conference.use_area_chairs and self.Readers.AREA_CHAIRS_ASSIGNED in self.invitees: + invitees.append(conference.get_area_chairs_id(number)) + + if self.Readers.REVIEWERS_ASSIGNED in self.invitees: + invitees.append(conference.get_reviewers_id(number)) + + if self.Readers.REVIEWERS_SUBMITTED in self.invitees: + invitees.append(conference.get_reviewers_id(number) + '/Submitted') + + return invitees + + def get_chat_signatures(self, conference, number): + + committee = [conference.get_program_chairs_id()] + + if conference.use_senior_area_chairs and self.Readers.SENIOR_AREA_CHAIRS_ASSIGNED in self.invitees: + committee.append(conference.get_senior_area_chairs_id(number)) + + if conference.use_area_chairs and self.Readers.AREA_CHAIRS_ASSIGNED in self.invitees: + committee.append(conference.get_anon_area_chair_id(number=number, anon_id='.*')) + + if conference.use_secondary_area_chairs and self.Readers.AREA_CHAIRS_ASSIGNED in self.invitees: + committee.append(conference.get_anon_secondary_area_chair_id(number=number, anon_id='.*')) + + if self.Readers.REVIEWERS_ASSIGNED in self.invitees or self.Readers.REVIEWERS_SUBMITTED in self.invitees: + committee.append(conference.get_anon_reviewer_id(number=number, anon_id='.*')) + + return committee + + def get_chat_readers(self, conference, number, api_version='1'): + + readers = [conference.get_program_chairs_id()] + + if conference.use_senior_area_chairs and self.Readers.SENIOR_AREA_CHAIRS_ASSIGNED in self.readers: + readers.append(conference.get_senior_area_chairs_id(number)) + + if conference.use_area_chairs and self.Readers.AREA_CHAIRS_ASSIGNED in self.readers: + readers.append(conference.get_area_chairs_id(number)) + + if self.Readers.REVIEWERS_ASSIGNED in self.readers: + readers.append(conference.get_reviewers_id(number)) + + if self.Readers.REVIEWERS_SUBMITTED in self.readers: + readers.append(conference.get_reviewers_id(number) + '/Submitted') + + return readers + def get_mandatory_readers(self, conference, number): readers = [conference.get_program_chairs_id()] if conference.use_senior_area_chairs: diff --git a/openreview/venue/invitation.py b/openreview/venue/invitation.py index b43bd94bc..bb69e05bd 100644 --- a/openreview/venue/invitation.py +++ b/openreview/venue/invitation.py @@ -72,10 +72,10 @@ def save_invitation(self, invitation, replacement=None): 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 expire_invitation(self, invitation_id): @@ -103,11 +103,11 @@ def get_process_content(self, file_path): with open(os.path.join(os.path.dirname(__file__), file_path)) as f: process = f.read() return process - + def set_meta_invitation(self): venue_id=self.venue_id meta_invitation = openreview.tools.get_invitation(self.client, self.venue.get_meta_invitation_id()) - + if meta_invitation is None or self._should_update_meta_invitation(meta_invitation): self.client.post_invitation_edit(invitations=None, readers=[venue_id], @@ -123,12 +123,12 @@ def set_meta_invitation(self): }, 'group_edit_script': { 'value': self.get_process_content('process/group_edit_process.py') - } + } }, edit=True ) ) - + def set_submission_invitation(self): venue_id = self.venue_id submission_stage = self.venue.submission_stage @@ -153,13 +153,13 @@ def set_submission_invitation(self): duedate=tools.datetime_millis(submission_stage.due_date) if submission_stage.due_date else None, expdate = tools.datetime_millis(submission_stage.exp_date) if submission_stage.exp_date else None, edit = { - 'signatures': { - 'param': { + 'signatures': { + 'param': { 'items': [ { 'prefix': '~.*', 'optional': True }, - { 'value': self.venue.get_program_chairs_id(), 'optional': True } + { 'value': self.venue.get_program_chairs_id(), 'optional': True } ] - } + } }, 'readers': edit_readers, 'writers': [venue_id, '${2/note/content/authorids/value}'], @@ -169,7 +169,7 @@ def set_submission_invitation(self): 'optional': True, 'deletable': True } - }, + }, 'note': { 'id': { 'param': { @@ -227,9 +227,9 @@ def set_submission_deletion_invitation(self, submission_revision_stage): writers=[venue_id], signatures=[venue_id], cdate=deletion_cdate, - date_processes=[{ + date_processes=[{ 'dates': ["#{4/edit/invitation/cdate}", self.update_date_string], - 'script': self.invitation_edit_process + 'script': self.invitation_edit_process }], content={ 'deletion_process_script': { @@ -241,17 +241,17 @@ def set_submission_deletion_invitation(self, submission_revision_stage): 'readers': [venue_id], 'writers': [venue_id], 'content': { - 'noteNumber': { + 'noteNumber': { 'value': { 'param': { - 'regex': '.*', 'type': 'integer' + 'regex': '.*', 'type': 'integer' } } }, 'noteId': { 'value': { 'param': { - 'regex': '.*', 'type': 'string' + 'regex': '.*', 'type': 'string' } } } @@ -278,15 +278,15 @@ def set_submission_deletion_invitation(self, submission_revision_stage): 'ddate': { 'param': { 'range': [ 0, 9999999999999 ], - 'optional': True + 'optional': True } }, - 'signatures': { - 'param': { + 'signatures': { + 'param': { 'items': [ - { 'value': self.venue.get_authors_id(number='${7/content/noteNumber/value}'), 'optional': True }, + { 'value': self.venue.get_authors_id(number='${7/content/noteNumber/value}'), 'optional': True }, { 'value': self.venue.get_program_chairs_id(), 'optional': True } - ] + ] } }, 'readers': [venue_id, self.venue.get_authors_id(number='${4/content/noteNumber/value}')], @@ -324,7 +324,7 @@ def set_post_submission_invitation(self): hidden_field_names = submission_stage.get_hidden_field_names() note_content = { f: { 'readers': [venue_id, self.venue.get_authors_id('${{4/id}/number}')] } for f in hidden_field_names } - existing_invitation = openreview.tools.get_invitation(self.client, post_submission_id) + existing_invitation = openreview.tools.get_invitation(self.client, post_submission_id) if existing_invitation and 'content' in existing_invitation.edit['note']: for field, value in existing_invitation.edit['note']['content'].items(): if field not in hidden_field_names and 'readers' in value: @@ -339,10 +339,10 @@ def set_post_submission_invitation(self): readers = ['everyone'], writers = [venue_id], cdate=post_submission_cdate, - date_processes=[{ + date_processes=[{ 'dates': ["#{4/cdate}", self.update_date_string], - 'script': self.get_process_content('process/post_submission_process.py') - }], + 'script': self.get_process_content('process/post_submission_process.py') + }], edit = { 'signatures': [venue_id], 'readers': [venue_id, self.venue.get_authors_id('${{2/note/id}/number}')], @@ -384,11 +384,11 @@ def set_post_submission_invitation(self): submission_invitation.edit['note']['content'] = note_content submission_invitation = self.save_invitation(submission_invitation, replacement=False) - + def set_pc_submission_revision_invitation(self): venue_id = self.venue_id submission_stage = self.venue.submission_stage - cdate = tools.datetime_millis(submission_stage.exp_date) if submission_stage.exp_date else None + cdate = tools.datetime_millis(submission_stage.exp_date) if submission_stage.exp_date else None submission_id = submission_stage.get_submission_id(self.venue) @@ -411,14 +411,14 @@ def set_pc_submission_revision_invitation(self): 'optional': True, 'deletable': True } - }, + }, 'note': { 'id': { 'param': { 'withInvitation': submission_id, 'optional': True } - }, + }, 'content': content, 'signatures': [self.venue.get_authors_id('${2/number}')] } @@ -427,7 +427,7 @@ def set_pc_submission_revision_invitation(self): ) submission_invitation = self.save_invitation(submission_invitation, replacement=True) - + def set_review_invitation(self): venue_id = self.venue_id @@ -438,7 +438,7 @@ def set_review_invitation(self): review_expdate = tools.datetime_millis(review_stage.exp_date) if review_stage.exp_date else None if not review_expdate: review_expdate = tools.datetime_millis(review_stage.due_date + datetime.timedelta(minutes = SHORT_BUFFER_MIN)) if review_stage.due_date else None - + content = review_stage.get_content(api_version='2', conference=self.venue) previous_query = {} @@ -454,9 +454,9 @@ def set_review_invitation(self): writers=[venue_id], signatures=[venue_id], cdate=review_cdate, - date_processes=[{ + date_processes=[{ 'dates': ["#{4/edit/invitation/cdate}", self.update_date_string], - 'script': self.invitation_edit_process + 'script': self.invitation_edit_process }], content={}, edit={ @@ -464,17 +464,17 @@ def set_review_invitation(self): 'readers': [venue_id], 'writers': [venue_id], 'content': { - 'noteNumber': { + 'noteNumber': { 'value': { 'param': { - 'regex': '.*', 'type': 'integer' + 'regex': '.*', 'type': 'integer' } } }, 'noteId': { 'value': { 'param': { - 'regex': '.*', 'type': 'string' + 'regex': '.*', 'type': 'string' } } } @@ -489,9 +489,9 @@ def set_review_invitation(self): 'maxReplies': 1, 'cdate': review_cdate, 'edit': { - 'signatures': { - 'param': { - 'items': [ { 'prefix': s, 'optional': True } if '.*' in s else { 'value': s, 'optional': True } for s in review_stage.get_signatures(self.venue, '${7/content/noteNumber/value}')] + 'signatures': { + 'param': { + 'items': [ { 'prefix': s, 'optional': True } if '.*' in s else { 'value': s, 'optional': True } for s in review_stage.get_signatures(self.venue, '${7/content/noteNumber/value}')] } }, 'readers': ['${2/note/readers}'], @@ -510,7 +510,7 @@ def set_review_invitation(self): 'param': { 'range': [ 0, 9999999999999 ], 'optional': True, - 'deletable': True + 'deletable': True } }, 'signatures': ['${3/signatures}'], @@ -538,7 +538,7 @@ def set_review_invitation(self): exec(script, funcs) funcs['process'](client, edit, invitation) ''' - + if review_stage.preprocess_path: invitation.content['review_preprocess_script'] = { 'value': self.get_process_content(review_stage.preprocess_path) @@ -551,7 +551,7 @@ def set_review_invitation(self): } exec(script, funcs) funcs['process'](client, edit, invitation) -''' +''' if review_duedate: invitation.edit['invitation']['duedate'] = review_duedate @@ -582,7 +582,7 @@ def set_review_invitation(self): self.save_invitation(invitation, replacement=False) return invitation - + def update_review_invitations(self): source_submissions_query_mapping = self.venue.source_submissions_query_mapping @@ -681,9 +681,9 @@ def set_review_rebuttal_invitation(self): writers=[venue_id], signatures=[venue_id], cdate=review_rebuttal_cdate, - date_processes=[{ + date_processes=[{ 'dates': ["#{4/edit/invitation/cdate}", self.update_date_string], - 'script': self.invitation_edit_process + 'script': self.invitation_edit_process }], content = { 'review_rebuttal_process_script': { @@ -730,8 +730,8 @@ def set_review_rebuttal_invitation(self): funcs['process'](client, edit, invitation) ''', 'edit': { - 'signatures': { - 'param': { + 'signatures': { + 'param': { 'items': [{ 'value': self.venue.get_authors_id(number='${7/content/noteNumber/value}') }] } }, @@ -819,9 +819,9 @@ def set_meta_review_invitation(self): writers=[venue_id], signatures=[venue_id], cdate=meta_review_cdate, - date_processes=[{ + date_processes=[{ 'dates': ["#{4/edit/invitation/cdate}", self.update_date_string], - 'script': self.invitation_edit_process + 'script': self.invitation_edit_process }], content = {}, edit={ @@ -829,17 +829,17 @@ def set_meta_review_invitation(self): 'readers': [venue_id], 'writers': [venue_id], 'content': { - 'noteNumber': { + 'noteNumber': { 'value': { 'param': { - 'regex': '.*', 'type': 'integer' + 'regex': '.*', 'type': 'integer' } } }, 'noteId': { 'value': { 'param': { - 'regex': '.*', 'type': 'string' + 'regex': '.*', 'type': 'string' } } } @@ -855,9 +855,9 @@ def set_meta_review_invitation(self): 'maxReplies': 1, 'cdate': meta_review_cdate, 'edit': { - 'signatures': { - 'param': { - 'items': [ { 'prefix': s, 'optional': True } if '.*' in s else { 'value': s, 'optional': True } for s in meta_review_stage.get_signatures(self.venue, '${7/content/noteNumber/value}')] + 'signatures': { + 'param': { + 'items': [ { 'prefix': s, 'optional': True } if '.*' in s else { 'value': s, 'optional': True } for s in meta_review_stage.get_signatures(self.venue, '${7/content/noteNumber/value}')] } }, 'readers': meta_review_stage.get_readers(self.venue, '${4/content/noteNumber/value}'), @@ -876,7 +876,7 @@ def set_meta_review_invitation(self): 'param': { 'range': [ 0, 9999999999999 ], 'optional': True, - 'deletable': True + 'deletable': True } }, 'signatures': ['${3/signatures}'], @@ -903,7 +903,7 @@ def set_meta_review_invitation(self): exec(script, funcs) funcs['process'](client, edit, invitation) ''' - + if meta_review_stage.preprocess_path: invitation.content['metareview_preprocess_script'] = { 'value': self.get_process_content(meta_review_stage.preprocess_path) @@ -916,7 +916,7 @@ def set_meta_review_invitation(self): } exec(script, funcs) funcs['process'](client, edit, invitation) -''' +''' if meta_review_duedate: invitation.edit['invitation']['duedate'] = meta_review_duedate @@ -940,9 +940,9 @@ def set_meta_review_invitation(self): writers=[venue_id], signatures=[venue_id], cdate=meta_review_cdate, - date_processes=[{ + date_processes=[{ 'dates': ["#{4/edit/invitation/cdate}", self.update_date_string], - 'script': self.invitation_edit_process + 'script': self.invitation_edit_process }], content = { }, @@ -951,17 +951,17 @@ def set_meta_review_invitation(self): 'readers': [venue_id], 'writers': [venue_id], 'content': { - 'noteNumber': { + 'noteNumber': { 'value': { 'param': { - 'regex': '.*', 'type': 'integer' + 'regex': '.*', 'type': 'integer' } } }, 'noteId': { 'value': { 'param': { - 'regex': '.*', 'type': 'string' + 'regex': '.*', 'type': 'string' } } } @@ -975,10 +975,10 @@ def set_meta_review_invitation(self): 'invitees': [venue_id, self.venue.get_senior_area_chairs_id(number='${3/content/noteNumber/value}')], 'cdate': meta_review_cdate, 'edit': { - 'signatures': { - 'param': { + 'signatures': { + 'param': { 'items': [ - { 'value': self.venue.get_senior_area_chairs_id(number='${7/content/noteNumber/value}') } + { 'value': self.venue.get_senior_area_chairs_id(number='${7/content/noteNumber/value}') } ] } }, @@ -1054,7 +1054,7 @@ def set_recruitment_invitation(self, committee_name, options): } } content['reduced_load'] = reduced_load_dict - + process_content = self.get_process_content('process/recruitment_process.py') pre_process_content = self.get_process_content('process/recruitment_pre_process.js') @@ -1209,7 +1209,7 @@ def set_bid_invitations(self): 'writers': [ venue_id, '${2/tail}' ], 'signatures': { 'param': { - 'regex': f'~.*|{venue_id}' + 'regex': f'~.*|{venue_id}' } }, 'head': head, @@ -1287,9 +1287,9 @@ def set_official_comment_invitation(self): writers=[venue_id], signatures=[venue_id], cdate=comment_cdate, - date_processes=[{ + date_processes=[{ 'dates': ["#{4/edit/invitation/cdate}", self.update_date_string], - 'script': self.invitation_edit_process + 'script': self.invitation_edit_process }], content={ 'comment_preprocess_script': { @@ -1345,9 +1345,9 @@ def set_official_comment_invitation(self): funcs['process'](client, edit, invitation) ''', 'edit': { - 'signatures': { - 'param': { - 'items': [ { 'prefix': s, 'optional': True } if '.*' in s else { 'value': s, 'optional': True } for s in comment_stage.get_signatures(self.venue, '${7/content/noteNumber/value}')] + 'signatures': { + 'param': { + 'items': [ { 'prefix': s, 'optional': True } if '.*' in s else { 'value': s, 'optional': True } for s in comment_stage.get_signatures(self.venue, '${7/content/noteNumber/value}')] } }, 'readers': ['${2/note/readers}'], @@ -1361,9 +1361,9 @@ def set_official_comment_invitation(self): } }, 'forum': '${4/content/noteId/value}', - 'replyto': { + 'replyto': { 'param': { - 'withForum': '${6/content/noteId/value}', + 'withForum': '${6/content/noteId/value}', } }, 'ddate': { @@ -1433,9 +1433,9 @@ def set_public_comment_invitation(self): writers=[venue_id], signatures=[venue_id], cdate=comment_cdate, - date_processes=[{ + date_processes=[{ 'dates': ["#{4/edit/invitation/cdate}", self.update_date_string], - 'script': self.invitation_edit_process + 'script': self.invitation_edit_process }], content={ 'comment_process_script': { @@ -1484,9 +1484,9 @@ def set_public_comment_invitation(self): funcs['process'](client, edit, invitation) ''', 'edit': { - 'signatures': { - 'param': { - 'items': [{ 'prefix': '~.*' }] + 'signatures': { + 'param': { + 'items': [{ 'prefix': '~.*' }] } }, 'readers': ['${2/note/readers}'], @@ -1499,9 +1499,9 @@ def set_public_comment_invitation(self): } }, 'forum': '${4/content/noteId/value}', - 'replyto': { + 'replyto': { 'param': { - 'withForum': '${6/content/noteId/value}', + 'withForum': '${6/content/noteId/value}', } }, 'ddate': { @@ -1528,6 +1528,275 @@ def set_public_comment_invitation(self): self.save_invitation(invitation, replacement=False) return invitation + def set_chat_invitation(self): + venue_id = self.venue_id + comment_stage = self.venue.comment_stage + chat_invitation = self.venue.get_invitation_id('Chat') + emoji_chat_invitation = self.venue.get_invitation_id('Chat_Reaction') + comment_cdate = tools.datetime_millis(comment_stage.start_date if comment_stage.start_date else datetime.datetime.utcnow()) + comment_expdate = tools.datetime_millis(comment_stage.end_date) if comment_stage.end_date else None + + if not comment_stage.enable_chat: + invitation = tools.get_invitation(self.client, chat_invitation) + if invitation: + self.client.post_invitation_edit( + invitations=self.venue.get_meta_invitation_id(), + signatures = [venue_id], + invitation = openreview.api.Invitation( + id = chat_invitation, + edit = { + 'invitation': { + 'expdate': tools.datetime_millis(datetime.datetime.utcnow()) + } + } + ) + ) + + self.client.post_invitation_edit( + invitations=self.venue.get_meta_invitation_id(), + signatures = [venue_id], + invitation = openreview.api.Invitation( + id = self.venue.submission_stage.get_submission_id(self.venue), + reply_forum_views = { 'delete': True } + ) + ) + + self.client.post_invitation_edit( + invitations=self.venue.get_meta_invitation_id(), + signatures = [venue_id], + invitation = openreview.api.Invitation( + id = emoji_chat_invitation, + edit = { + 'invitation': { + 'expdate': tools.datetime_millis(datetime.datetime.utcnow()) + } + } + ) + ) + + + return + + invitation = Invitation(id=chat_invitation, + invitees=[venue_id], + readers=[venue_id], + writers=[venue_id], + signatures=[venue_id], + cdate=comment_cdate, + date_processes=[{ + 'dates': ["#{4/edit/invitation/cdate}", self.update_date_string], + 'script': self.invitation_edit_process + }], + content={ + 'chat_process_script': { + 'value': self.get_process_content('process/chat_comment_process.py') + } + }, + edit={ + 'signatures': [venue_id], + 'readers': [venue_id], + 'writers': [venue_id], + 'content': { + 'noteNumber': { + 'value': { + 'param': { + 'regex': '.*', 'type': 'integer' + } + } + }, + 'noteId': { + 'value': { + 'param': { + 'regex': '.*', 'type': 'string' + } + } + } + }, + 'replacement': False, + 'invitation': { + 'id': self.venue.get_invitation_id('Chat', '${2/content/noteNumber/value}'), + 'signatures': [ venue_id ], + 'readers': ['everyone'], + 'writers': [venue_id], + 'invitees': comment_stage.get_chat_invitees(self.venue, number='${3/content/noteNumber/value}'), + 'cdate': comment_cdate, + 'dateprocesses':[{ + 'dates': [], + 'script': self.get_process_content('process/chat_date_comment_process.py') + }], + 'process': '''def process(client, edit, invitation): + meta_invitation = client.get_invitation(invitation.invitations[0]) + script = meta_invitation.content['chat_process_script']['value'] + funcs = { + 'openreview': openreview + } + exec(script, funcs) + funcs['process'](client, edit, invitation) +''', + 'edit': { + 'signatures': { + 'param': { + #'enum': comment_stage.get_chat_signatures(self.venue, '${6/content/noteNumber/value}') + 'items': [ { 'prefix': s, 'optional': True } if '.*' in s else { 'value': s, 'optional': True } for s in comment_stage.get_chat_signatures(self.venue, '${7/content/noteNumber/value}')] + } + }, + 'readers': ['${2/note/readers}'], + 'writers': [venue_id], + 'note': { + 'id': { + 'param': { + 'withInvitation': self.venue.get_invitation_id('Chat', '${6/content/noteNumber/value}'), + 'optional': True + } + }, + 'forum': '${4/content/noteId/value}', + 'replyto': { + 'param': { + 'withForum': '${6/content/noteId/value}', + } + }, + 'ddate': { + 'param': { + 'range': [ 0, 9999999999999 ], + 'optional': True, + 'deletable': True + } + }, + 'signatures': ['${3/signatures}'], + 'readers': comment_stage.get_chat_readers(self.venue, '${5/content/noteNumber/value}'), + 'writers': [venue_id, '${3/signatures}'], + 'content': { + 'message': { + 'value': { + 'param': { + 'type': 'string', + 'maxLength': 1000, + 'markdown': True + } + } + } + } + } + } + } + } + ) + + if comment_expdate: + invitation.edit['invitation']['expdate'] = comment_expdate + + self.save_invitation(invitation, replacement=False) + + self.client.post_invitation_edit( + invitations=self.venue.get_meta_invitation_id(), + signatures = [venue_id], + invitation = openreview.api.Invitation( + id = self.venue.submission_stage.get_submission_id(self.venue), + reply_forum_views = [ + { + 'id': 'discussion', + 'label': 'Discussion', + 'filter': f'-invitations:{venue_id}/{self.venue.submission_stage.name}${{note.number}}/-/Chat', + 'nesting': 3, + 'sort': 'date-desc', + 'layout': 'default', + 'live': True + }, + { + 'id': 'committee-chat', + 'label': 'Committee Members Chat', + 'filter': f'invitations:{venue_id}/{self.venue.submission_stage.name}${{note.number}}/-/Chat,{venue_id}/{self.venue.submission_stage.name}${{note.number}}/-/{self.venue.review_stage.name}', + 'nesting': 1, + 'sort': 'date-asc', + 'layout': 'chat', + 'live': True, + 'expandedInvitations': [f'{venue_id}/{self.venue.submission_stage.name}${{note.number}}/-/Chat'] + } + ] + ) + ) + + invitation = Invitation(id=emoji_chat_invitation, + invitees=[venue_id], + readers=[venue_id], + writers=[venue_id], + signatures=[venue_id], + cdate=comment_cdate, + date_processes=[{ + 'dates': ["#{4/edit/invitation/cdate}", self.update_date_string], + 'script': self.invitation_edit_process + }], + edit={ + 'signatures': [venue_id], + 'readers': [venue_id], + 'writers': [venue_id], + 'content': { + 'noteNumber': { + 'value': { + 'param': { + 'regex': '.*', 'type': 'integer' + } + } + }, + 'noteId': { + 'value': { + 'param': { + 'regex': '.*', 'type': 'string' + } + } + } + }, + 'replacement': True, + 'invitation': { + 'id': self.venue.get_invitation_id('Chat_Reaction', '${2/content/noteNumber/value}'), + 'signatures': [ venue_id ], + 'readers': ['everyone'], + 'writers': [venue_id], + 'invitees': comment_stage.get_chat_invitees(self.venue, number='${3/content/noteNumber/value}'), + 'cdate': comment_cdate, + 'tag': { + 'id': { + 'param': { + 'withInvitation': self.venue.get_invitation_id('Chat_Reaction', '${5/content/noteNumber/value}'), + 'optional': True + } + }, + 'forum': '${3/content/noteId/value}', + 'replyto': { + 'param': { + 'withForum': '${5/content/noteId/value}', + } + }, + 'ddate': { + 'param': { + 'range': [ 0, 999999999999 ], + 'optional': True, + 'deletable': True + } + }, + 'signatures': { + 'param': { + 'items': [ { 'prefix': s, 'optional': True } if '.*' in s else { 'value': s, 'optional': True } for s in comment_stage.get_chat_signatures(self.venue, '${7/content/noteNumber/value}')] + } + }, + 'readers': comment_stage.get_chat_readers(self.venue, '${4/content/noteNumber/value}'), + 'writers': [venue_id, '${2/signatures}'], + 'tag': { + 'param': { + 'enum': ['👍', '👎', '😄', '😂', '🔥'] + } + } + } + } + } + ) + + if comment_expdate: + invitation.edit['invitation']['expdate'] = comment_expdate + + self.save_invitation(invitation, replacement=False) + return invitation + def set_decision_invitation(self): venue_id = self.venue_id @@ -1545,9 +1814,9 @@ def set_decision_invitation(self): writers=[venue_id], signatures=[venue_id], cdate=decision_cdate, - date_processes=[{ + date_processes=[{ 'dates': ["#{4/edit/invitation/cdate}", self.update_date_string], - 'script': self.invitation_edit_process + 'script': self.invitation_edit_process }], content={ 'decision_process_script': { @@ -1630,7 +1899,7 @@ def set_decision_invitation(self): invitation.edit['invitation']['duedate'] = decision_due_date if decision_expdate: - invitation.edit['invitation']['expdate'] = decision_expdate + invitation.edit['invitation']['expdate'] = decision_expdate self.save_invitation(invitation, replacement=True) return invitation @@ -1651,31 +1920,31 @@ def set_withdrawal_invitation(self): writers=[venue_id], signatures=[venue_id], cdate=cdate, - date_processes=[{ + date_processes=[{ 'dates': [self.update_date_string], - 'script': self.invitation_edit_process - }], + 'script': self.invitation_edit_process + }], content={ 'process_script': { 'value': self.get_process_content('process/withdrawal_submission_process.py') } - }, + }, edit={ 'signatures': [venue_id], 'readers': [venue_id], 'writers': [venue_id], 'content': { - 'noteNumber': { + 'noteNumber': { 'value': { 'param': { - 'regex': '.*', 'type': 'integer' + 'regex': '.*', 'type': 'integer' } } }, 'noteId': { 'value': { 'param': { - 'regex': '.*', 'type': 'string' + 'regex': '.*', 'type': 'string' } } } @@ -1698,9 +1967,9 @@ def set_withdrawal_invitation(self): exec(script, funcs) funcs['process'](client, edit, invitation)''', 'edit': { - 'signatures': { - 'param': { - 'items': [{ 'value': self.venue.get_authors_id(number='${7/content/noteNumber/value}') }] + 'signatures': { + 'param': { + 'items': [{ 'value': self.venue.get_authors_id(number='${7/content/noteNumber/value}') }] } }, 'readers': submission_stage.get_withdrawal_readers(self.venue, '${4/content/noteNumber/value}'), @@ -1745,14 +2014,14 @@ def set_withdrawal_invitation(self): } } - ) + ) if cdate: invitation.edit['invitation']['cdate'] = cdate - invitation.date_processes=[{ + invitation.date_processes=[{ 'dates': ["#{4/edit/invitation/cdate}", self.update_date_string], - 'script': self.invitation_edit_process - }] + 'script': self.invitation_edit_process + }] if exp_date: invitation.edit['invitation']['expdate'] = exp_date @@ -1809,13 +2078,13 @@ def set_withdrawal_invitation(self): 'optional': True, 'deletable': True } - }, + }, 'note': { 'id': { 'param': { 'withInvitation': self.venue.submission_stage.get_submission_id(self.venue) } - }, + }, 'content': content, 'readers' : submission_stage.get_withdrawal_readers(self.venue, '${{2/id}/number}') } @@ -1844,14 +2113,14 @@ def set_withdrawal_invitation(self): 'optional': True, 'deletable': True } - }, + }, 'invitation': { 'id': { 'param': { 'regex': self.venue.get_paper_group_prefix() } }, - 'signatures': [venue_id], + 'signatures': [venue_id], 'expdate': { 'param': { 'range': [ 0, 9999999999999 ], @@ -1873,7 +2142,7 @@ def set_withdrawal_invitation(self): 'process_script': { 'value': self.get_process_content('process/withdrawal_reversion_submission_process.py') } - }, + }, edit={ 'signatures': [venue_id], 'readers': [venue_id], @@ -1882,14 +2151,14 @@ def set_withdrawal_invitation(self): 'noteId': { 'value': { 'param': { - 'regex': '.*', 'type': 'string' + 'regex': '.*', 'type': 'string' } } }, 'withdrawalId': { 'value': { 'param': { - 'regex': '.*', 'type': 'string' + 'regex': '.*', 'type': 'string' } } } @@ -1955,7 +2224,7 @@ def set_withdrawal_invitation(self): } } - ) + ) self.save_invitation(invitation, replacement=True) @@ -1978,9 +2247,9 @@ def set_desk_rejection_invitation(self): writers=[venue_id], signatures=[venue_id], cdate=cdate, - date_processes=[{ + date_processes=[{ 'dates': [self.update_date_string], - 'script': self.invitation_edit_process + 'script': self.invitation_edit_process }], content={ 'process_script': { @@ -2046,9 +2315,9 @@ def set_desk_rejection_invitation(self): if cdate: invitation.edit['invitation']['cdate'] = cdate - invitation.date_processes=[{ + invitation.date_processes=[{ 'dates': ["#{4/edit/invitation/cdate}", self.update_date_string], - 'script': self.invitation_edit_process + 'script': self.invitation_edit_process }] self.save_invitation(invitation, replacement=False) @@ -2254,9 +2523,9 @@ def set_submission_revision_invitation(self, submission_revision_stage=None): writers=[venue_id], signatures=[venue_id], cdate=revision_cdate, - date_processes=[{ + date_processes=[{ 'dates': ["#{4/edit/invitation/cdate}", self.update_date_string], - 'script': self.invitation_edit_process + 'script': self.invitation_edit_process }], content={ 'revision_process_script': { @@ -2271,17 +2540,17 @@ def set_submission_revision_invitation(self, submission_revision_stage=None): 'readers': [venue_id], 'writers': [venue_id], 'content': { - 'noteNumber': { + 'noteNumber': { 'value': { 'param': { - 'regex': '.*', 'type': 'integer' + 'regex': '.*', 'type': 'integer' } } }, 'noteId': { 'value': { 'param': { - 'regex': '.*', 'type': 'string' + 'regex': '.*', 'type': 'string' } } } @@ -2307,15 +2576,15 @@ def set_submission_revision_invitation(self, submission_revision_stage=None): 'ddate': { 'param': { 'range': [ 0, 9999999999999 ], - 'optional': True + 'optional': True } }, - 'signatures': { - 'param': { + 'signatures': { + 'param': { 'items': [ - { 'value': self.venue.get_authors_id(number='${7/content/noteNumber/value}'), 'optional': True }, + { 'value': self.venue.get_authors_id(number='${7/content/noteNumber/value}'), 'optional': True }, { 'value': self.venue.get_program_chairs_id(), 'optional': True } - ] + ] } }, 'readers': ['${{2/note/id}/readers}'], @@ -2425,9 +2694,9 @@ def set_custom_stage_invitation(self): writers=[venue_id], signatures=[venue_id], cdate=custom_stage_cdate, - date_processes=[{ + date_processes=[{ 'dates': ["#{4/edit/invitation/cdate}", self.update_date_string], - 'script': self.invitation_edit_process + 'script': self.invitation_edit_process }], content = invitation_content, edit={ @@ -2471,9 +2740,9 @@ def set_custom_stage_invitation(self): funcs['process'](client, edit, invitation) ''', 'edit': { - 'signatures': { - 'param': { - 'items': [ { 'prefix': s, 'optional': True } if '.*' in s else { 'value': s, 'optional': True } for s in custom_stage.get_signatures(self.venue, '${7/content/noteNumber/value}')] + 'signatures': { + 'param': { + 'items': [ { 'prefix': s, 'optional': True } if '.*' in s else { 'value': s, 'optional': True } for s in custom_stage.get_signatures(self.venue, '${7/content/noteNumber/value}')] } }, 'readers': edit_readers, @@ -2548,7 +2817,7 @@ def set_custom_stage_invitation(self): return invitation def set_assignment_invitation(self, committee_id, submission_content=None): - + venue = self.venue venue_id = venue.get_id() assignment_invitation_id = venue.get_assignment_id(committee_id, deployed=True) @@ -2625,9 +2894,9 @@ def set_assignment_invitation(self, committee_id, submission_content=None): } if submission_content: edge_head['param']['withContent'] = submission_content - + if is_reviewer: - edge_nonreaders = [venue.get_authors_id(number='${{2/head}/number}')] + edge_nonreaders = [venue.get_authors_id(number='${{2/head}/number}')] if venue.use_senior_area_chairs: invitation_readers.append(senior_area_chairs_id) edge_invitees.append(senior_area_chairs_id) @@ -2651,7 +2920,7 @@ def set_assignment_invitation(self, committee_id, submission_content=None): if is_area_chair: invitation_readers.append(area_chairs_id) - edge_nonreaders = [venue.get_authors_id(number='${{2/head}/number}')] + edge_nonreaders = [venue.get_authors_id(number='${{2/head}/number}')] if venue.use_senior_area_chairs: invitation_readers.append(senior_area_chairs_id) edge_invitees.append(senior_area_chairs_id) @@ -2720,7 +2989,7 @@ def set_assignment_invitation(self, committee_id, submission_content=None): 'nonreaders': edge_nonreaders, 'writers': edge_writers, 'signatures': { - 'param': { + 'param': { 'regex': '|'.join(edge_signatures), 'default': [venue.get_program_chairs_id()] } @@ -2732,17 +3001,17 @@ def set_assignment_invitation(self, committee_id, submission_content=None): 'options': { 'group': committee_id } - } + } }, 'weight': { 'param': { 'minimum': -1 - } + } } } ) - self.save_invitation(invitation, replacement=True) + self.save_invitation(invitation, replacement=True) def set_expertise_selection_invitations(self): @@ -2792,7 +3061,7 @@ def build_expertise_selection(committee_id): 'writers': [ venue_id, '${2/signatures}' ], 'signatures': { 'param': { - 'regex': '~.*' + 'regex': '~.*' } }, 'head': { @@ -2860,7 +3129,7 @@ def set_registration_invitations(self): 'optional': True, 'deletable': True } - }, + }, 'readers': readers, 'writers': [venue_id], 'signatures': [venue_id], @@ -2881,11 +3150,11 @@ def set_registration_invitations(self): 'type': 'string', 'maxLength': 250000, 'markdown': True, - 'input': 'textarea' + 'input': 'textarea' } } } - } + } } } ) @@ -2919,7 +3188,7 @@ def set_registration_invitations(self): 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', conference=self.venue) + registration_content = registration_stage.get_content(api_version='2', conference=self.venue) registration_invitation_id = venue.get_invitation_id(name=f'{registration_stage.name}', prefix=committee_id) invitation=Invitation(id=registration_invitation_id, @@ -2931,7 +3200,7 @@ def set_registration_invitations(self): duedate = tools.datetime_millis(due_date) if due_date else None, expdate = tools.datetime_millis(expdate) if expdate else None, maxReplies = 1, - minReplies = 1, + minReplies = 1, edit={ 'signatures': { 'param': { 'items': [ { 'prefix': '~.*' } ] }}, 'readers': [venue_id, '${2/signatures}'], @@ -2948,7 +3217,7 @@ def set_registration_invitations(self): 'optional': True, 'deletable': True } - }, + }, 'forum': forum_note_id, 'replyto': forum_note_id, 'signatures': ['${3/signatures}'], @@ -2956,7 +3225,7 @@ def set_registration_invitations(self): 'writers': [venue_id, '${3/signatures}'], 'content': registration_content } - } + } ) self.save_invitation(invitation, replacement=True) @@ -3040,9 +3309,9 @@ def set_submission_reviewer_group_invitation(self): writers=[venue_id], signatures=[venue_id], cdate=cdate, - date_processes=[{ + date_processes=[{ 'dates': ["#{4/cdate}", self.update_date_string], - 'script': self.group_edit_process + 'script': self.group_edit_process }], edit={ 'signatures': [venue_id], @@ -3080,13 +3349,13 @@ def set_submission_reviewer_group_invitation(self): self.save_invitation(invitation, replacement=False) return invitation - + def set_submission_area_chair_group_invitation(self): venue_id = self.venue_id invitation_id = self.venue.get_invitation_id(f'{self.venue.submission_stage.name}_Group', prefix=self.venue.get_area_chairs_id()) cdate=tools.datetime_millis(self.venue.submission_stage.second_due_date_exp_date if self.venue.submission_stage.second_due_date_exp_date else self.venue.submission_stage.exp_date) - + invitation = Invitation(id=invitation_id, invitees=[venue_id], @@ -3094,9 +3363,9 @@ def set_submission_area_chair_group_invitation(self): writers=[venue_id], signatures=[venue_id], cdate=cdate, - date_processes=[{ + date_processes=[{ 'dates': ["#{4/cdate}", self.update_date_string], - 'script': self.group_edit_process + 'script': self.group_edit_process }], edit={ 'signatures': [venue_id], @@ -3140,7 +3409,7 @@ def set_submission_senior_area_chair_group_invitation(self): venue_id = self.venue_id invitation_id = self.venue.get_invitation_id(f'{self.venue.submission_stage.name}_Group', prefix=self.venue.get_senior_area_chairs_id()) cdate=tools.datetime_millis(self.venue.submission_stage.second_due_date_exp_date if self.venue.submission_stage.second_due_date_exp_date else self.venue.submission_stage.exp_date) - + invitation = Invitation(id=invitation_id, invitees=[venue_id], @@ -3148,9 +3417,9 @@ def set_submission_senior_area_chair_group_invitation(self): writers=[venue_id], signatures=[venue_id], cdate=cdate, - date_processes=[{ + date_processes=[{ 'dates': ["#{4/cdate}", self.update_date_string], - 'script': self.group_edit_process + 'script': self.group_edit_process }], edit={ 'signatures': [venue_id], @@ -3246,7 +3515,7 @@ def set_ethics_paper_groups_invitation(self): self.save_invitation(invitation, replacement=False) return invitation - + def set_ethics_review_invitation(self): venue_id = self.venue_id @@ -3257,7 +3526,7 @@ def set_ethics_review_invitation(self): ethics_review_expdate = tools.datetime_millis(ethics_review_stage.exp_date) if ethics_review_stage.exp_date else None if not ethics_review_expdate: ethics_review_expdate = tools.datetime_millis(ethics_review_stage.due_date + datetime.timedelta(minutes = SHORT_BUFFER_MIN)) if ethics_review_stage.due_date else None - + content = ethics_review_stage.get_content(api_version='2', conference=self.venue) invitation = Invitation(id=ethics_review_invitation_id, @@ -3266,9 +3535,9 @@ def set_ethics_review_invitation(self): writers=[venue_id], signatures=[venue_id], cdate=ethics_review_cdate, - date_processes=[{ + date_processes=[{ 'dates': ["#{4/edit/invitation/cdate}", self.update_date_string], - 'script': self.invitation_edit_process + 'script': self.invitation_edit_process }], content={ 'source': { @@ -3280,17 +3549,17 @@ def set_ethics_review_invitation(self): 'readers': [venue_id], 'writers': [venue_id], 'content': { - 'noteNumber': { + 'noteNumber': { 'value': { 'param': { - 'regex': '.*', 'type': 'integer' + 'regex': '.*', 'type': 'integer' } } }, 'noteId': { 'value': { 'param': { - 'regex': '.*', 'type': 'string' + 'regex': '.*', 'type': 'string' } } } @@ -3305,9 +3574,9 @@ def set_ethics_review_invitation(self): 'maxReplies': 1, 'cdate': ethics_review_cdate, 'edit': { - 'signatures': { - 'param': { - 'items': [ { 'prefix': s, 'optional': True } if '.*' in s else { 'value': s, 'optional': True } for s in ethics_review_stage.get_signatures(self.venue, '${7/content/noteNumber/value}')] + 'signatures': { + 'param': { + 'items': [ { 'prefix': s, 'optional': True } if '.*' in s else { 'value': s, 'optional': True } for s in ethics_review_stage.get_signatures(self.venue, '${7/content/noteNumber/value}')] } }, 'readers': ethics_review_stage.get_readers(self.venue, '${4/content/noteNumber/value}', '${2/signatures}'), @@ -3326,7 +3595,7 @@ def set_ethics_review_invitation(self): 'param': { 'range': [ 0, 9999999999999 ], 'optional': True, - 'deletable': True + 'deletable': True } }, 'signatures': ['${3/signatures}'], @@ -3354,7 +3623,7 @@ def set_ethics_review_invitation(self): exec(script, funcs) funcs['process'](client, edit, invitation) ''' - + if ethics_review_stage.preprocess_path: invitation.content['ethics_review_preprocess_script'] = { 'value': self.get_process_content(ethics_review_stage.preprocess_path) @@ -3367,7 +3636,7 @@ def set_ethics_review_invitation(self): } exec(script, funcs) funcs['process'](client, edit, invitation) -''' +''' if ethics_review_duedate: invitation.edit['invitation']['duedate'] = ethics_review_duedate @@ -3520,14 +3789,14 @@ def set_SAC_ethics_flag_invitation(self, sac_ethics_flag_duedate=None): funcs['process'](client, edit, invitation) ''', 'edit': { - 'signatures': { - 'param': { + 'signatures': { + 'param': { 'items': [ - { 'value': venue.get_senior_area_chairs_id(number='${7/content/noteNumber/value}'), 'optional': True }, + { 'value': venue.get_senior_area_chairs_id(number='${7/content/noteNumber/value}'), 'optional': True }, { 'value': venue.get_program_chairs_id(), 'optional': True } - ] + ] } - }, + }, 'readers': ['${2/note/readers}'], 'nonreaders': ['${2/note/nonreaders}'], 'writers': [venue_id], @@ -3638,7 +3907,7 @@ def set_reviewer_recommendation_invitation(self, start_date, due_date, total_rec 'writers': [ venue_id, '${2/signatures}' ], 'signatures': { 'param': { - 'regex': f'~.*|{venue_id}' + 'regex': f'~.*|{venue_id}' } }, 'head': { @@ -3685,9 +3954,9 @@ def set_submission_message_invitation(self): writers=[venue_id], signatures=[venue_id], cdate=cdate, - date_processes=[{ + date_processes=[{ 'dates': ["#{4/edit/invitation/cdate}", self.update_date_string], - 'script': self.invitation_edit_process + 'script': self.invitation_edit_process }], edit={ 'signatures': [venue_id], @@ -3734,7 +4003,7 @@ def set_submission_message_invitation(self): ) self.save_invitation(invitation, replacement=True) - + ## invitation to message all reviewers invitation = Invitation(id=self.venue.get_message_id(committee_id=self.venue.get_reviewers_id()), readers=[venue_id], @@ -3775,6 +4044,6 @@ def set_submission_message_invitation(self): } ) - self.save_invitation(invitation, replacement=True) - + self.save_invitation(invitation, replacement=True) + return invitation diff --git a/openreview/venue/process/chat_comment_process.py b/openreview/venue/process/chat_comment_process.py new file mode 100644 index 000000000..87f2ee91b --- /dev/null +++ b/openreview/venue/process/chat_comment_process.py @@ -0,0 +1,112 @@ +def process(client, edit, invitation): + + domain = client.get_group(edit.domain) + venue_id = domain.id + short_name = domain.get_content_value('subtitle') + contact = domain.get_content_value('contact') + meta_invitation_id = domain.get_content_value('meta_invitation_id') + sender = domain.get_content_value('message_sender') + + submission = client.get_note(edit.note.forum) + comment = client.get_note(edit.note.id) + + invitation = client.get_invitation(invitation.id) + if invitation.date_processes[0].get('cron') is None: + ## Activate date process to run every 4 hours + print('update cron job tu run every 4 hours') + client.post_invitation_edit ( + invitations=meta_invitation_id, + signatures=[venue_id], + invitation=openreview.api.Invitation( + id=invitation.id, + date_processes = [{ + 'cron': '0 */4 * * *', + 'script': invitation.date_processes[0].get('script') + }] + ) + ) + + if comment.number == 1: + print('Send initial comment email') + + client.post_message( + invitation=meta_invitation_id, + subject = f'[{short_name}] New conversation in committee members chat for submission {submission.number}: {submission.content["title"]["value"]}', + recipients = comment.readers, + message = f'''Hi {{{{fullname}}}}, + +A new conversation has been started in the {short_name} forum for submission {submission.number}: {submission.content['title']['value']} + +You can view the conversation here: https://openreview.net/forum?id={submission.id}¬eId={comment.id}#committee-chat +''', + replyTo = contact, + ignoreRecipients = comment.signatures, + signature=venue_id, + sender=sender + ) + + ## Update the last notified id + print('Set last notified id', comment.id) + client.post_invitation_edit ( + invitations=meta_invitation_id, + signatures=[venue_id], + invitation=openreview.api.Invitation( + id=invitation.id, + content={ + 'last_notified_id': { 'value': comment.id } + } + ) + ) + + return + + last_notified_id = invitation.content.get('last_notified_id', {}).get('value') if invitation.content else None + + if not last_notified_id: + ## Update the last notified id + print('Set the last notified id, it was not set before', comment.id) + client.post_invitation_edit ( + invitations=meta_invitation_id, + signatures=[venue_id], + invitation=openreview.api.Invitation( + id=invitation.id, + content={ + 'last_notified_id': { 'value': comment.id } + } + ) + ) + last_notified_id = comment.id + + print('Send follow up comment email') + last_notified_comment = client.get_note(last_notified_id) + new_comments = client.get_notes(invitation=invitation.id, forum=submission.id, mintcdate=last_notified_comment.tcdate, sort='tcdate:asc') + + print(f'New comments: {len(new_comments)}') + if len(new_comments) >= 5: + client.post_message( + invitation=meta_invitation_id, + subject = f'[{short_name}] New messages in committee members chat for submission {submission.number}: {submission.content["title"]["value"]}', + recipients = comment.readers, + message = f'''Hi {{{{fullname}}}}, + +New comments have been posted for the conversation in the {short_name} forum for submission {submission.number}: {submission.content['title']['value']} + +You can view the conversation here: https://openreview.net/forum?id={submission.id}¬eId={new_comments[0].id}#committee-chat +''', + replyTo = contact, + ignoreRecipients = comment.signatures, + signature=venue_id, + sender=sender + ) + + print('Update the last notified id', comment.id) + client.post_invitation_edit ( + invitations=meta_invitation_id, + signatures=[venue_id], + invitation=openreview.api.Invitation( + id=invitation.id, + content={ + 'last_notified_id': { 'value': comment.id } + } + ) + ) \ No newline at end of file diff --git a/openreview/venue/process/chat_date_comment_process.py b/openreview/venue/process/chat_date_comment_process.py new file mode 100644 index 000000000..2d24e7b96 --- /dev/null +++ b/openreview/venue/process/chat_date_comment_process.py @@ -0,0 +1,54 @@ +def process(client, invitation): + + domain = client.get_group(invitation.domain) + venue_id = domain.id + short_name = domain.get_content_value('subtitle') + contact = domain.get_content_value('contact') + meta_invitation_id = domain.get_content_value('meta_invitation_id') + sender = domain.get_content_value('message_sender') + + + last_notified_id = invitation.content.get('last_notified_id', {}).get('value') if invitation.content else None + + last_notified_comment = client.get_note(last_notified_id) if last_notified_id else None + + if not last_notified_comment: + print('No last notified comment found') + return + + submission = client.get_note(last_notified_comment.forum) + new_comments = client.get_notes(invitation=invitation.id, forum=submission.id, mintcdate=last_notified_comment.tcdate, sort='tcdate:asc') + + print(f'New comments: {len(new_comments)}') + if len(new_comments) == 0: + return + + client.post_message( + invitation = meta_invitation_id, + subject = f'[{short_name}] New message{"s" if len(new_comments) > 1 else ""} in committee members chat for submission {submission.number}: {submission.content["title"]["value"]}', + recipients = new_comments[-1].readers, + message = f'''Hi {{{{fullname}}}}, + +New comment{"s have" if len(new_comments) > 1 else " has"} been posted for the conversation in the {short_name} forum for submission {submission.number}: {submission.content['title']['value']} + +You can view the conversation here: https://openreview.net/forum?id={submission.id}¬eId={new_comments[0].id}#committee-chat +''', + replyTo = contact, + ignoreRecipients = new_comments[-1].signatures, + signature=venue_id, + sender = sender + ) + + ## Update the last notified id + client.post_invitation_edit ( + invitations=meta_invitation_id, + signatures=[venue_id], + invitation=openreview.api.Invitation( + id=invitation.id, + content={ + 'last_notified_id': { 'value': new_comments[-1].id } + } + ) + ) + + diff --git a/openreview/venue/venue.py b/openreview/venue/venue.py index cd60041e2..5b462e48c 100644 --- a/openreview/venue/venue.py +++ b/openreview/venue/venue.py @@ -527,6 +527,8 @@ def create_comment_stage(self): if self.comment_stage.allow_public_comments: self.invitation_builder.set_public_comment_invitation() + self.invitation_builder.set_chat_invitation() + def create_decision_stage(self): invitation = self.invitation_builder.set_decision_invitation() diff --git a/openreview/venue_request/venue_request.py b/openreview/venue_request/venue_request.py index 057debb83..d59900501 100644 --- a/openreview/venue_request/venue_request.py +++ b/openreview/venue_request/venue_request.py @@ -490,6 +490,16 @@ def setup_comment_stage(self): 'required': True, 'default': 'No, do not email PCs for each official comment made in the venue', 'order': 31 + }, + 'enable_chat_between_committee_members': { + 'description': 'An experimental feature that allows committee members to chat with each other. Only the selected participants that are members of the reviewing committee will be using this feature. Default is "Yes, enable chat between committee members". More information: https://docs.openreview.net/getting-started/live-chat-on-the-forum-page', + 'value-radio': [ + 'Yes, enable chat between committee members', + 'No, do not enable chat between committee members' + ], + 'required': False, + 'default': 'Yes, enable chat between committee members', + 'order': 32 } } diff --git a/setup.py b/setup.py index 76f5d4961..dd1b01d8a 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='openreview-py', - version='1.39.3', + version='1.39.4', description='OpenReview API Python client library', url='https://github.com/openreview/openreview-py', diff --git a/tests/conftest.py b/tests/conftest.py index a93b95401..f4c283795 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -111,6 +111,9 @@ def respond_invitation(selenium, request_page, url, accept, quota=None, comment= buttons = container.find_elements(By.TAG_NAME, "button") + for button in buttons: + assert button.is_enabled() + if quota and accept: if len(buttons) == 3: ## Accept with quota buttons[1].click() diff --git a/tests/test_icml_conference.py b/tests/test_icml_conference.py index 5fce2e7c0..e05150c51 100644 --- a/tests/test_icml_conference.py +++ b/tests/test_icml_conference.py @@ -3636,7 +3636,8 @@ def test_comment_stage(self, openreview_client, helpers): 'commentary_end_date': end_date.strftime('%Y/%m/%d'), 'participants': ['Program Chairs', 'Assigned Senior Area Chairs', 'Assigned Area Chairs', 'Assigned Reviewers'], 'additional_readers': ['Program Chairs', 'Assigned Senior Area Chairs', 'Assigned Area Chairs', 'Assigned Reviewers', 'Assigned Submitted Reviewers'], - 'email_program_chairs_about_official_comments': 'Yes, email PCs for each official comment made in the venue' + 'email_program_chairs_about_official_comments': 'Yes, email PCs for each official comment made in the venue', + 'enable_chat_between_committee_members': 'Yes, enable chat between committee members' }, forum=request_form.forum, invitation=f'openreview.net/Support/-/Request{request_form.number}/Comment_Stage', @@ -3648,7 +3649,16 @@ def test_comment_stage(self, openreview_client, helpers): )) helpers.await_queue() + helpers.await_queue_edit(openreview_client, 'ICML.cc/2023/Conference/-/Official_Comment-0-1', count=1) + helpers.await_queue_edit(openreview_client, 'ICML.cc/2023/Conference/-/Chat-0-1', count=1) + helpers.await_queue_edit(openreview_client, 'ICML.cc/2023/Conference/-/Chat_Reaction-0-1', count=1) + + chat_invitations = openreview_client.get_invitations(invitation='ICML.cc/2023/Conference/-/Chat') + assert len(chat_invitations) == 100 + chat_reaction_invitations = openreview_client.get_invitations(invitation='ICML.cc/2023/Conference/-/Chat_Reaction') + assert len(chat_reaction_invitations) == 100 + invitation = openreview_client.get_invitation('ICML.cc/2023/Conference/Submission1/-/Official_Comment') assert invitation assert 'ICML.cc/2023/Conference/Submission1/Ethics_Reviewers' in invitation.invitees @@ -5305,113 +5315,19 @@ def test_post_decision_stage(self, client, openreview_client, helpers, selenium, def test_forum_chat(self, openreview_client, helpers): - openreview_client.post_invitation_edit( - invitations='ICML.cc/2023/Conference/-/Edit', - readers = ['ICML.cc/2023/Conference'], - writers = ['ICML.cc/2023/Conference'], - signatures = ['ICML.cc/2023/Conference'], - invitation = openreview.api.Invitation( - id = 'ICML.cc/2023/Conference/-/Submission', - reply_forum_views = [ - { - 'id': 'all', - 'label': 'All' - }, - { - 'id': 'discussion', - 'label': 'Discussion', - 'filter': '-invitations:ICML.cc/2023/Conference/Submission${note.number}/-/Chat', - 'nesting': 3, - 'sort': 'date-desc', - 'layout': 'default', - 'live': True - }, - { - 'id': 'reviewers-chat', - 'label': 'Reviewers Chat', - 'filter': 'invitations:ICML.cc/2023/Conference/Submission${note.number}/-/Chat,ICML.cc/2023/Conference/Submission${note.number}/-/Official_Review', - 'nesting': 1, - 'sort': 'date-asc', - 'layout': 'chat', - 'live': True, - 'expandedInvitations': ['ICML.cc/2023/Conference/Submission${note.number}/-/Chat'] - } - ] - ) - ) - submission_invitation = openreview_client.get_invitation('ICML.cc/2023/Conference/-/Submission') assert len(submission_invitation.reply_forum_views) submission = openreview_client.get_notes(invitation='ICML.cc/2023/Conference/-/Submission', number=1)[0] - openreview_client.post_invitation_edit( - invitations='ICML.cc/2023/Conference/-/Edit', - readers = ['ICML.cc/2023/Conference'], - writers = ['ICML.cc/2023/Conference'], - signatures = ['ICML.cc/2023/Conference'], - invitation = openreview.api.Invitation( - id = 'ICML.cc/2023/Conference/Submission1/-/Chat', - readers = ['everyone'], - writers = ['ICML.cc/2023/Conference'], - signatures = ['ICML.cc/2023/Conference'], - invitees = ['ICML.cc/2023/Conference/Program_Chairs', 'ICML.cc/2023/Conference/Submission1/Area_Chairs', 'ICML.cc/2023/Conference/Submission1/Reviewers'], - edit = { - 'readers': ['ICML.cc/2023/Conference', '${2/signatures}'], - 'writers': ['ICML.cc/2023/Conference'], - 'signatures': { - 'param': { - 'enum': [ - 'ICML.cc/2023/Conference/Program_Chairs', - 'ICML.cc/2023/Conference/Submission1/Area_Chair_.*', - 'ICML.cc/2023/Conference/Submission1/Reviewer_.*', - ] - } - }, - 'note': { - 'id': { - 'param': { - 'withInvitation': 'ICML.cc/2023/Conference/Submission1/-/Chat', - 'optional': True - } - }, - 'readers': ['ICML.cc/2023/Conference/Program_Chairs', 'ICML.cc/2023/Conference/Submission1/Area_Chairs', 'ICML.cc/2023/Conference/Submission1/Reviewers'], - 'writers': ['ICML.cc/2023/Conference'], - 'signatures': ['${3/signatures}'], - 'ddate': { - 'param': { - 'range': [ 0, 9999999999999 ], - 'optional': True, - 'deletable': True - } - }, - 'forum': submission.id, - 'replyto': { - 'param': { - 'withForum': submission.id - } - }, - 'content': { - 'message': { - 'value': { - 'param': { - 'type': 'string', - 'maxLength': 50000, - 'markdown': True - } - } - } - } - } - } - ) - ) - reviewer_client = openreview.api.OpenReviewClient(username='reviewer1@icml.cc', password=helpers.strong_password) anon_groups = reviewer_client.get_groups(prefix='ICML.cc/2023/Conference/Submission1/Reviewer_', signatory='~Reviewer_ICMLOne1') anon_group_id = anon_groups[0].id + invitation = openreview_client.get_invitation('ICML.cc/2023/Conference/Submission1/-/Chat') + assert invitation.date_processes[0].get('dates') == [] + note_edit = reviewer_client.post_note_edit( invitation='ICML.cc/2023/Conference/Submission1/-/Chat', signatures=[anon_group_id], @@ -5423,6 +5339,22 @@ def test_forum_chat(self, openreview_client, helpers): ) ) + helpers.await_queue_edit(openreview_client, edit_id=note_edit['id']) + + invitation = openreview_client.get_invitation('ICML.cc/2023/Conference/Submission1/-/Chat') + assert invitation.date_processes[0].get('dates') is None + assert invitation.date_processes[0].get('cron') == '0 */4 * * *' + + assert len(openreview_client.get_messages(to='reviewer1@icml.cc', subject='[ICML 2023] New conversation in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='reviewer2@icml.cc', subject='[ICML 2023] New conversation in committee members chat for submission 1: Paper title 1 Version 2')) == 1 + assert len(openreview_client.get_messages(to='melisa@icml.cc', subject='[ICML 2023] New conversation in committee members chat for submission 1: Paper title 1 Version 2')) == 1 + assert len(openreview_client.get_messages(to='reviewer3@icml.cc', subject='[ICML 2023] New conversation in committee members chat for submission 1: Paper title 1 Version 2')) == 1 + assert len(openreview_client.get_messages(to='reviewer4@yahoo.com', subject='[ICML 2023] New conversation in committee members chat for submission 1: Paper title 1 Version 2')) == 1 + assert len(openreview_client.get_messages(to='rachel_bis@icml.cc', subject='[ICML 2023] New conversation in committee members chat for submission 1: Paper title 1 Version 2')) == 1 + assert len(openreview_client.get_messages(to='ac2@icml.cc', subject='[ICML 2023] New conversation in committee members chat for submission 1: Paper title 1 Version 2')) == 1 + assert len(openreview_client.get_messages(to='sac2@icml.cc', subject='[ICML 2023] New conversation in committee members chat for submission 1: Paper title 1 Version 2')) == 1 + assert len(openreview_client.get_messages(to='pc@icml.cc', subject='[ICML 2023] New conversation in committee members chat for submission 1: Paper title 1 Version 2')) == 1 + pc_client=openreview.api.OpenReviewClient(username='pc@icml.cc', password=helpers.strong_password) note_edit = pc_client.post_note_edit( @@ -5435,3 +5367,160 @@ def test_forum_chat(self, openreview_client, helpers): } ) ) + + helpers.await_queue_edit(openreview_client, edit_id=note_edit['id']) + + assert len(openreview_client.get_messages(to='reviewer1@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='reviewer2@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='melisa@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='reviewer3@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='reviewer4@yahoo.com', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='rachel_bis@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='ac2@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='sac2@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='pc@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + + sac_client=openreview.api.OpenReviewClient(username='sac2@icml.cc', password=helpers.strong_password) + + note_edit = sac_client.post_note_edit( + invitation='ICML.cc/2023/Conference/Submission1/-/Chat', + signatures=['ICML.cc/2023/Conference/Submission1/Senior_Area_Chairs'], + note=openreview.api.Note( + replyto=note_edit['note']['id'], + content={ + 'message': { 'value': 'Chat comment number 3' } + } + ) + ) + + helpers.await_queue_edit(openreview_client, edit_id=note_edit['id']) + + assert len(openreview_client.get_messages(to='reviewer1@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='reviewer2@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='melisa@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='reviewer3@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='reviewer4@yahoo.com', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='rachel_bis@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='ac2@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='sac2@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='pc@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + + note_edit = sac_client.post_note_edit( + invitation='ICML.cc/2023/Conference/Submission1/-/Chat', + signatures=['ICML.cc/2023/Conference/Submission1/Senior_Area_Chairs'], + note=openreview.api.Note( + replyto=note_edit['note']['id'], + content={ + 'message': { 'value': 'Chat comment number 4' } + } + ) + ) + + helpers.await_queue_edit(openreview_client, edit_id=note_edit['id']) + + assert len(openreview_client.get_messages(to='reviewer1@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='reviewer2@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='melisa@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='reviewer3@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='reviewer4@yahoo.com', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='rachel_bis@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='ac2@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='sac2@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='pc@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + + note_edit = sac_client.post_note_edit( + invitation='ICML.cc/2023/Conference/Submission1/-/Chat', + signatures=['ICML.cc/2023/Conference/Submission1/Senior_Area_Chairs'], + note=openreview.api.Note( + replyto=note_edit['note']['id'], + content={ + 'message': { 'value': 'Chat comment number 5' } + } + ) + ) + + helpers.await_queue_edit(openreview_client, edit_id=note_edit['id']) + + assert len(openreview_client.get_messages(to='reviewer1@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='reviewer2@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='melisa@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='reviewer3@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='reviewer4@yahoo.com', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='rachel_bis@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='ac2@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='sac2@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='pc@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + + note_edit = sac_client.post_note_edit( + invitation='ICML.cc/2023/Conference/Submission1/-/Chat', + signatures=['ICML.cc/2023/Conference/Submission1/Senior_Area_Chairs'], + note=openreview.api.Note( + replyto=note_edit['note']['id'], + content={ + 'message': { 'value': 'Chat comment number 6' } + } + ) + ) + + helpers.await_queue_edit(openreview_client, edit_id=note_edit['id']) + + assert len(openreview_client.get_messages(to='reviewer1@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 1 + assert len(openreview_client.get_messages(to='reviewer2@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 1 + assert len(openreview_client.get_messages(to='melisa@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 1 + assert len(openreview_client.get_messages(to='reviewer3@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 1 + assert len(openreview_client.get_messages(to='reviewer4@yahoo.com', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 1 + assert len(openreview_client.get_messages(to='rachel_bis@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 1 + assert len(openreview_client.get_messages(to='ac2@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 1 + assert len(openreview_client.get_messages(to='sac2@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 0 + assert len(openreview_client.get_messages(to='pc@icml.cc', subject='[ICML 2023] New messages in committee members chat for submission 1: Paper title 1 Version 2')) == 1 + + ## Add tag emoji + tag = sac_client.post_tag(openreview.api.Tag( + invitation='ICML.cc/2023/Conference/Submission1/-/Chat_Reaction', + signatures=['ICML.cc/2023/Conference/Submission1/Senior_Area_Chairs'], + tag='😄', + replyto=note_edit['note']['id'] + )) + + tags = openreview_client.get_tags(invitation='ICML.cc/2023/Conference/Submission1/-/Chat_Reaction', mintmdate=tag.tmdate - 5000) + assert len(tags) == 1 + + ## Disable chat + pc_client=openreview.Client(username='pc@icml.cc', password=helpers.strong_password) + request_form=pc_client.get_notes(invitation='openreview.net/Support/-/Request_Form')[0] + + # Post an official comment stage note + now = datetime.datetime.utcnow() + start_date = now - datetime.timedelta(days=2) + end_date = now + datetime.timedelta(days=3) + comment_stage_note = pc_client.post_note(openreview.Note( + content={ + 'commentary_start_date': start_date.strftime('%Y/%m/%d'), + 'commentary_end_date': end_date.strftime('%Y/%m/%d'), + 'participants': ['Program Chairs', 'Assigned Senior Area Chairs', 'Assigned Area Chairs', 'Assigned Reviewers'], + 'additional_readers': ['Program Chairs', 'Assigned Senior Area Chairs', 'Assigned Area Chairs', 'Assigned Reviewers', 'Assigned Submitted Reviewers'], + 'email_program_chairs_about_official_comments': 'Yes, email PCs for each official comment made in the venue', + 'enable_chat_between_committee_members': 'No, do not enable chat between committee members' + }, + forum=request_form.forum, + invitation=f'openreview.net/Support/-/Request{request_form.number}/Comment_Stage', + readers=['ICML.cc/2023/Conference/Program_Chairs', 'openreview.net/Support'], + replyto=request_form.forum, + referent=request_form.forum, + signatures=['~Program_ICMLChair1'], + writers=[] + )) + + helpers.await_queue() + helpers.await_queue_edit(openreview_client, 'ICML.cc/2023/Conference/-/Official_Comment-0-1', count=1) + helpers.await_queue_edit(openreview_client, 'ICML.cc/2023/Conference/-/Chat-0-1', count=2) + helpers.await_queue_edit(openreview_client, 'ICML.cc/2023/Conference/-/Chat_Reaction-0-1', count=2) + + chat_invitations = openreview_client.get_invitations(invitation='ICML.cc/2023/Conference/-/Chat') + assert len(chat_invitations) == 0 + + chat_reaction_invitations = openreview_client.get_invitations(invitation='ICML.cc/2023/Conference/-/Chat_Reaction') + assert len(chat_reaction_invitations) == 0 + + submission_invitation = openreview_client.get_invitation('ICML.cc/2023/Conference/-/Submission') + assert submission_invitation.reply_forum_views is None \ No newline at end of file