Beispiel #1
0
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
Beispiel #2
0
 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,
     }
Beispiel #3
0
 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,
     }
Beispiel #4
0
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
Beispiel #5
0
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