def gather_hot_topics( user_profile: UserProfile, hot_topics: List[TopicKey], topic_activity: TopicActivity, ) -> List[Dict[str, Any]]: # Returns a list of dictionaries containing the templating # information for each hot topic. topic_senders = topic_activity.topic_senders topic_length = topic_activity.topic_length topic_messages = topic_activity.topic_messages hot_topic_render_payloads = [] for h in hot_topics: users = list(topic_senders[h]) count = topic_length[h] messages = topic_messages[h] # We'll display up to 2 messages from the topic. first_few_messages = messages[:2] teaser_data = { "participants": users, "count": count - len(first_few_messages), "first_few_messages": build_message_list(user_profile, first_few_messages) } hot_topic_render_payloads.append(teaser_data) return hot_topic_render_payloads
def teaser_data(self, user_profile: UserProfile) -> Dict[str, Any]: teaser_count = self.num_human_messages - len(self.sample_messages) first_few_messages = build_message_list( user_profile, self.sample_messages, ) return { "participants": self.human_senders, "count": teaser_count, "first_few_messages": first_few_messages, }
def teaser_data(self, user: UserProfile, stream_map: Dict[int, Stream]) -> Dict[str, Any]: teaser_count = self.num_human_messages - len(self.sample_messages) first_few_messages = build_message_list( user=user, messages=self.sample_messages, stream_map=stream_map, ) return { "participants": sorted(self.human_senders), "count": teaser_count, "first_few_messages": first_few_messages, }
def gather_hot_conversations(user_profile: UserProfile, messages: List[Message]) -> List[Dict[str, Any]]: # Gather stream conversations of 2 types: # 1. long conversations # 2. conversations where many different people participated # # Returns a list of dictionaries containing the templating # information for each hot conversation. conversation_length = defaultdict(int) # type: Dict[Tuple[int, str], int] conversation_messages = defaultdict( list) # type: Dict[Tuple[int, str], List[Message]] conversation_diversity = defaultdict( set) # type: Dict[Tuple[int, str], Set[str]] for message in messages: key = (message.recipient.type_id, message.topic_name()) conversation_messages[key].append(message) if not message.sent_by_human(): # Don't include automated messages in the count. continue conversation_diversity[key].add(message.sender.full_name) conversation_length[key] += 1 diversity_list = list(conversation_diversity.items()) diversity_list.sort(key=lambda entry: len(entry[1]), reverse=True) length_list = list(conversation_length.items()) length_list.sort(key=lambda entry: entry[1], reverse=True) # Get up to the 4 best conversations from the diversity list # and length list, filtering out overlapping conversations. hot_conversations = [elt[0] for elt in diversity_list[:2]] for candidate, _ in length_list: if candidate not in hot_conversations: hot_conversations.append(candidate) if len(hot_conversations) >= 4: break # There was so much overlap between the diversity and length lists that we # still have < 4 conversations. Try to use remaining diversity items to pad # out the hot conversations. num_convos = len(hot_conversations) if num_convos < 4: hot_conversations.extend( [elt[0] for elt in diversity_list[num_convos:4]]) hot_conversation_render_payloads = [] for h in hot_conversations: users = list(conversation_diversity[h]) count = conversation_length[h] messages = conversation_messages[h] # We'll display up to 2 messages from the conversation. first_few_messages = messages[:2] teaser_data = { "participants": users, "count": count - len(first_few_messages), "first_few_messages": build_message_list(user_profile, first_few_messages) } hot_conversation_render_payloads.append(teaser_data) return hot_conversation_render_payloads
def handle_digest_email( user_profile_id: int, cutoff: float, render_to_web: bool = False) -> Union[None, Dict[str, Any]]: user_profile = get_user_profile_by_id(user_profile_id) # We are disabling digest emails for soft deactivated users for the time. # TODO: Find an elegant way to generate digest emails for these users. if user_profile.long_term_idle: return None # Convert from epoch seconds to a datetime object. cutoff_date = datetime.datetime.fromtimestamp(int(cutoff), tz=pytz.utc) all_messages = UserMessage.objects.filter( user_profile=user_profile, message__pub_date__gt=cutoff_date).select_related('message').order_by( "message__pub_date") context = common_context(user_profile) # Start building email template data. context.update({ 'unsubscribe_link': one_click_unsubscribe_link(user_profile, "digest") }) # Gather recent missed PMs, re-using the missed PM email logic. # You can't have an unread message that you sent, but when testing # this causes confusion so filter your messages out. pms = all_messages.filter(~Q(message__recipient__type=Recipient.STREAM) & ~Q(message__sender=user_profile)) # Show up to 4 missed PMs. pms_limit = 4 context['unread_pms'] = build_message_list( user_profile, [pm.message for pm in pms[:pms_limit]]) context['remaining_unread_pms_count'] = max(0, len(pms) - pms_limit) home_view_recipients = Subscription.objects.filter( user_profile=user_profile, active=True, in_home_view=True).values_list('recipient_id', flat=True) stream_messages = all_messages.filter( message__recipient__type=Recipient.STREAM, message__recipient__in=home_view_recipients) messages = [um.message for um in stream_messages] # Gather hot conversations. context["hot_conversations"] = gather_hot_conversations( user_profile, messages) # Gather new streams. new_streams_count, new_streams = gather_new_streams( user_profile, cutoff_date) context["new_streams"] = new_streams context["new_streams_count"] = new_streams_count # Gather users who signed up recently. new_users_count, new_users = gather_new_users(user_profile, cutoff_date) context["new_users"] = new_users if render_to_web: return context # We don't want to send emails containing almost no information. if enough_traffic(context["unread_pms"], context["hot_conversations"], new_streams_count, new_users_count): logger.info("Sending digest email for %s" % (user_profile.email, )) # Send now, as a ScheduledEmail send_future_email('zerver/emails/digest', user_profile.realm, to_user_ids=[user_profile.id], from_name="Zulip Digest", from_address=FromAddress.NOREPLY, context=context) return None