def get_assignments(user=None, state=None): queryset = SocialAssignments.objects if user is not None: queryset = queryset.filter(user=user) if state is not None: queryset = queryset.filter(state=state) queryset = queryset.order_by("-id").values() assignments = [a for a in queryset] assignmentsById = {} tweetIds = [a["social_id"] for a in assignments] for a in assignments: tweetIds.append(a["social_id"]) for tweetId in a["thread_tweets"]: tweetIds.append(tweetId) tweets = {} for tweet in get_tweets_by_ids(tweetIds): tweets[tweet["tweet_id"]] = TweetsSerializer(tweet).data for assignment in assignments: assignmentsById[assignment["id"]] = SocialAssignmentSerializer(assignment).data return {"assignments": assignmentsById, "tweets": tweets}
def assign_reviewer(self, request, format=None): qp = request.query_params tweetId = qp["tweetId"] if "tweetId" in qp else None reviewerId = qp["reviewerId"] if "reviewerId" in qp else None try: status = get_status_from_db(tweetId) if status is None: raise ScremsongException( "Could not find tweet {} in local db".format(tweetId)) parents, parent = resolve_tweet_parents(status) parent, tweets, relationships = resolve_tweet_thread_for_parent( parent) replyTweetIds = [ tweetId for tweetId in list(tweets.keys()) if tweetId != parent["data"]["id_str"] ] assignment, created = SocialAssignments.objects.update_or_create( platform=SocialPlatformChoice.TWITTER, social_id=parent["data"]["id_str"], defaults={ "user_id": reviewerId, "assigned_by": request.user, "thread_relationships": relationships, "thread_tweets": replyTweetIds }) websockets.send_channel_message( "reviewers.assign", { "assignment": SocialAssignmentSerializer(assignment).data, "tweets": set_tweet_object_state_en_masse(tweets, TweetState.ASSIGNED), }) return Response({"OK": True}) except ScremsongException as e: return Response( { "error": "Could not assign tweet {}. Failed to resolve and update tweet thread. Message: {}" .format(tweetId, str(e)) }, status=status.HTTP_400_BAD_REQUEST)
def mark_read(self, request, format=None): qp = request.query_params assignmentId = int( qp["assignmentId"]) if "assignmentId" in qp else None assignment = SocialAssignments.objects.get(id=assignmentId) assignment.last_read_on = getCreationDateOfNewestTweetInAssignment( assignment) assignment.save() websockets.send_channel_message( "reviewers.assignment_metdata_changed", { "assignment": SocialAssignmentSerializer(assignment).data, }) return Response({"OK": True})
def restore(self, request, format=None): qp = request.query_params assignmentId = int( qp["assignmentId"]) if "assignmentId" in qp else None assignment = SocialAssignments.objects.get(id=assignmentId) assignment.close_reason = None assignment.state = SocialAssignmentState.PENDING assignment.save() websockets.send_channel_message( "reviewers.assignment_metdata_changed", { "assignment": SocialAssignmentSerializer(assignment).data, }) return Response({"OK": True})
def bulk_reassign_reviewer(self, request, format=None): qp = request.query_params currentReviewerId = int( qp["currentReviewerId"]) if "currentReviewerId" in qp else None newReviewerId = int( qp["newReviewerId"]) if "newReviewerId" in qp else None if currentReviewerId is not None and newReviewerId is not None: try: assignmentsUpdated = [] tweetsUpdated = {} assignments = SocialAssignments.objects.filter( user_id=currentReviewerId).filter( state=SocialAssignmentState.PENDING) with transaction.atomic(): for assignment in assignments: assignment.user_id = newReviewerId assignment.assigned_by = request.user assignment.save() assignmentsUpdated.append( SocialAssignmentSerializer(assignment).data) parent = get_status_from_db(assignment.social_id) parent, tweets, relationships = resolve_tweet_thread_for_parent( parent) tweetsUpdated = { **tweetsUpdated, **set_tweet_object_state_en_masse( tweets, TweetState.ASSIGNED) } websockets.send_channel_message("reviewers.bulk_assign", { "assignments": assignmentsUpdated, "tweets": tweetsUpdated, }) return Response({"OK": True}) except ScremsongException as e: return Response( { "error": "Could not bulk reassign assignments for {} to {}. Failed to resolve and update tweet thread. Message: {}" .format(currentReviewerId, newReviewerId, str(e)) }, status=status.HTTP_400_BAD_REQUEST)
def close(self, request, format=None): qp = request.query_params assignmentId = int( qp["assignmentId"]) if "assignmentId" in qp else None reason = str(qp["reason"]) if "reason" in qp else None if SocialAssignmentCloseReason.has_value(reason) is True: assignment = SocialAssignments.objects.get(id=assignmentId) assignment.close_reason = reason assignment.state = SocialAssignmentState.CLOSED assignment.last_read_on = getCreationDateOfNewestTweetInAssignment( assignment) assignment.save() websockets.send_channel_message( "reviewers.assignment_metdata_changed", { "assignment": SocialAssignmentSerializer(assignment).data, }) return Response({"OK": True})
def reassign_reviewer(self, request, format=None): qp = request.query_params assignmentId = int( qp["assignmentId"]) if "assignmentId" in qp else None newReviewerId = int( qp["newReviewerId"]) if "newReviewerId" in qp else None if assignmentId is not None and newReviewerId is not None: try: assignment = get_or_none(SocialAssignments, id=assignmentId) assignment.user_id = newReviewerId assignment.assigned_by = request.user assignment.save() parent = get_status_from_db(assignment.social_id) parent, tweets, relationships = resolve_tweet_thread_for_parent( parent) websockets.send_channel_message( "reviewers.assign", { "assignment": SocialAssignmentSerializer(assignment).data, "tweets": set_tweet_object_state_en_masse( tweets, TweetState.ASSIGNED), }) return Response({"OK": True}) except ScremsongException as e: return Response( { "error": "Could not reassign assignment {}. Failed to resolve and update tweet thread. Message: {}" .format(assignmentId, str(e)) }, status=status.HTTP_400_BAD_REQUEST)
def process_new_tweet_reply(status, tweetSource, sendWebSocketEvent): # Deal with tweets arriving / being processed out of order. # If it's already part of an assignment then it's been processed and clients have been notified. if is_tweet_part_of_an_assignment(status["id_str"]) is True: logger.warning( "Got tweet {} that was already part of an assignment (process_new_tweet_reply)" .format(status["id_str"])) # Only send a web socket event if we're not handling this elsewhere (e.g. backfilling) if sendWebSocketEvent is True: notify_of_saved_tweet(get_tweet_from_db(status["id_str"])) return True # OK! This means that this is a newly arrived tweet, so we need to work out if it's part of an already existing assignment. # Start by saving the tweet as dirty (i.e. we need it in the database for thread resolution, but haven't finished processing it yet) tweet, created = save_tweet(status, source=tweetSource, status=TweetStatus.DIRTY) try: parents, parent = resolve_tweet_parents(status) # If the parent is tweet is part of an assignment then we need to go and # run a refresh to get us all new replies in the thread. # This gets us replies that we don't get via the stream, as well as our own replies. if is_tweet_part_of_an_assignment(parent["id_str"]) is True: parent, tweets, relationships = resolve_tweet_thread_for_parent( parent) replyTweetIds = [ tweetId for tweetId in list(tweets.keys()) if tweetId != parent["data"]["id_str"] ] assignment, created = SocialAssignments.objects.update_or_create( platform=SocialPlatformChoice.TWITTER, social_id=parent["data"]["id_str"], defaults={ "thread_relationships": relationships, "thread_tweets": replyTweetIds, "last_updated_on": timezone.now() }) # Adding a new tweet reopens the assignment if the user is accepting assignments or unassigns it if they're offline and the assignment is closed # if is_user_accepting_assignments(assignment.user_id) is False and is_from_demsausage(tweet) is False: # logger.info("Processing tweet {}: User is offline, so delete the assignment".format(status["id_str"])) # assignmentId = assignment.id # assignment.delete() # websockets.send_channel_message("reviewers.unassign", { # "assignmentId": assignmentId, # }) # else: # Adding a new tweet reopens the assignment if the user is accepting assignments if assignment.state == SocialAssignmentState.CLOSED: if is_from_demsausage(tweet) is False: logger.info( "Processing tweet {}: Set it to pending".format( status["id_str"])) assignment.state = SocialAssignmentState.PENDING assignment.save() websockets.send_user_channel_message( "notifications.send", { "message": "One of your assignments has had a new reply arrive - it's been added back into your queue again", "options": { "variant": NotificationVariants.INFO } }, assignment.user.username) else: websockets.send_user_channel_message( "notifications.send", { "message": "One of your assignments has had a new reply arrive", "options": { "variant": NotificationVariants.INFO } }, assignment.user.username) websockets.send_channel_message( "reviewers.assignment_updated", { "assignment": SocialAssignmentSerializer(assignment).data, "tweets": tweets, }) tweet.state = TweetState.ASSIGNED # Once we're done processing the tweet, or if its parent is not part of an assignment, # then we just carry on and save the tweet has processed and send a notification. tweet.status = TweetStatus.OK tweet.save() if sendWebSocketEvent is True: notify_of_saved_tweet(tweet) return True except Exception as e: # Mark tweets as dirty if we failed to resolve thread relationships (or if something else terrible happened) tweet = Tweets.objects.update_or_create( tweet_id=tweet.tweet_id, defaults={"status": TweetStatus.DIRTY}) raise e logger.error("Failed to process new tweet {}".format(status["id_str"])) return False