diff --git a/tools/screenshots/generate-user-messages-screenshot b/tools/screenshots/generate-user-messages-screenshot index e6524ac06e..b790066958 100755 --- a/tools/screenshots/generate-user-messages-screenshot +++ b/tools/screenshots/generate-user-messages-screenshot @@ -8,6 +8,7 @@ import sys from datetime import datetime, timezone from typing import Dict, List, Optional +from django.conf import settings from pydantic import BaseModel, ConfigDict SCREENSHOTS_DIR = os.path.abspath(os.path.dirname(__file__)) @@ -33,9 +34,12 @@ import json from tools.lib.test_script import prepare_puppeteer_run from zerver.actions.create_user import do_create_user +from zerver.actions.message_edit import check_update_message +from zerver.actions.message_flags import do_update_message_flags from zerver.actions.message_send import do_send_messages, internal_prep_stream_message from zerver.actions.reactions import do_add_reaction from zerver.actions.streams import bulk_add_subscriptions, do_change_subscription_property +from zerver.actions.user_groups import check_add_user_group from zerver.actions.user_settings import do_change_avatar_fields from zerver.lib.emoji import get_emoji_data from zerver.lib.message import SendMessageRequest, access_message @@ -45,31 +49,51 @@ from zerver.lib.timestamp import datetime_to_timestamp from zerver.lib.upload import upload_avatar_image from zerver.lib.url_encoding import topic_narrow_url from zerver.models import Message, UserProfile +from zerver.models.groups import NamedUserGroup from zerver.models.realms import get_realm -from zerver.models.users import get_user_by_delivery_email +from zerver.models.users import get_system_bot, get_user_by_delivery_email realm = get_realm("zulip") +realm.message_content_edit_limit_seconds = None +realm.save() + DEFAULT_USER = get_user_by_delivery_email("iago@zulip.com", realm) +NOTIFICATION_BOT = get_system_bot(settings.NOTIFICATION_BOT, realm.id) message_thread_ids: List[int] = [] +USER_AVATARS_MAP = { + "Ariella Drake": "tools/screenshots/user_avatars/AriellaDrake.png", + "Elena Garcia": "tools/screenshots/user_avatars/ElenaGarcia.jpg", + "Kevin Lin": "tools/screenshots/user_avatars/KevinLin.png", + "Zoe Davis": "tools/screenshots/user_avatars/ZoeDavis.png", + "Bo Williams": "tools/screenshots/user_avatars/BoWilliams.png", + "James Williams": "tools/screenshots/user_avatars/JamesWilliams.png", + "Manvir Singh": "tools/screenshots/user_avatars/ManvirSingh.png", + "Dal Kim": "tools/screenshots/user_avatars/DalKim.jpg", + "John Lin": "tools/screenshots/user_avatars/JohnLin.png", + "Maxy Stert": "tools/screenshots/user_avatars/MaxyStert.jpg", +} + class MessageThread(BaseModel): model_config = ConfigDict(frozen=True) sender: str content: str + starred: bool + edited: bool reactions: Dict[str, List[str]] date: Dict[str, int] -def create_user(full_name: str, avatar_filename: str) -> None: +def create_user(full_name: str, avatar_filename: Optional[str]) -> None: email = f'{full_name.replace(" ", "")}@zulip.com' try: user = UserProfile.objects.get(realm=realm, full_name=full_name) except UserProfile.DoesNotExist: user = do_create_user(email, "password", realm, full_name, acting_user=DEFAULT_USER) - if avatar_filename != "": + if avatar_filename is not None: set_avatar(user, avatar_filename) @@ -80,9 +104,9 @@ def set_avatar(user: UserProfile, filename: str) -> None: def create_and_subscribe_stream( - stream_name: str, users: List[str], color: Optional[str] = None + stream_name: str, users: List[str], color: Optional[str] = None, invite_only: bool = False ) -> None: - stream = ensure_stream(realm, stream_name, acting_user=DEFAULT_USER) + stream = ensure_stream(realm, stream_name, invite_only=invite_only, acting_user=DEFAULT_USER) bulk_add_subscriptions( realm, [stream], @@ -112,6 +136,8 @@ def send_stream_messages( subscriber = UserProfile.objects.get(realm=realm, id=subscriber_id) subscribers[subscriber.full_name] = subscriber + subscribers["Notification Bot"] = NOTIFICATION_BOT + messages: List[Optional[SendMessageRequest]] = [] for message in staged_messages: @@ -149,6 +175,14 @@ def send_stream_messages( users = [subscribers[user_name] for user_name in user_names] add_message_reactions(message_id, reaction, users) + if message.get("starred"): + do_update_message_flags(DEFAULT_USER, "add", "starred", [message_id]) + + if message.get("edited"): + sender = UserProfile.objects.get(realm=realm, full_name=message["sender"]) + updated_content = message["content"] + " " + check_update_message(sender, message_id, content=updated_content) + return message_ids @@ -161,6 +195,14 @@ def add_message_reactions(message_id: int, emoji: str, users: List[UserProfile]) ) +def create_user_group(group_name: str, members: List[str]) -> None: + member_profiles = [ + UserProfile.objects.get(realm=realm, full_name=member_name) for member_name in members + ] + member_profiles.append(DEFAULT_USER) + check_add_user_group(realm, group_name, member_profiles, acting_user=DEFAULT_USER) + + def capture_streams_narrow_screenshot( image_path: str, stream_name: str, topic: str, unread_msg_id: int ) -> None: @@ -193,13 +235,28 @@ try: with open(options.thread[0]) as f: threads = json.load(f) for thread in threads: - for user, avatar in thread["users"].items(): - create_user(user, avatar) + for user in thread["users"]: + user_avatar = USER_AVATARS_MAP.get(user) + create_user(user, user_avatar) if thread["recipient_type"] == "channel": users = list(thread["users"].keys()) users.append("Iago") - create_and_subscribe_stream(thread["channel"], users, thread["color"]) + + if thread.get("user_groups") is not None: + for user_group in thread.get("user_groups"): + user_grp = NamedUserGroup.objects.filter( + name=user_group["group_name"], realm=realm + ).first() + if user_grp is not None: + user_grp.delete() + + create_user_group(user_group["group_name"], user_group["members"]) + + invite_only = ( + False if thread.get("invite_only") is None else thread.get("invite_only") + ) + create_and_subscribe_stream(thread["channel"], users, thread["color"], invite_only) message_ids = send_stream_messages( thread["channel"], thread["topic"], thread["messages"] ) diff --git a/tools/screenshots/thread-screenshot.js b/tools/screenshots/thread-screenshot.js index 84a56f3329..97cedbf40e 100644 --- a/tools/screenshots/thread-screenshot.js +++ b/tools/screenshots/thread-screenshot.js @@ -32,7 +32,7 @@ if (options.imagePath === undefined) { async function run() { const browser = await puppeteer.launch({ args: [ - "--window-size=1400,1024", + "--window-size=500,1024", "--no-sandbox", "--disable-setuid-sandbox", // Helps render fonts correctly on Ubuntu: https://github.com/puppeteer/puppeteer/issues/661 @@ -44,7 +44,7 @@ async function run() { try { const page = await browser.newPage(); // deviceScaleFactor:2 gives better quality screenshots (higher pixel density) - await page.setViewport({width: 1280, height: 1024, deviceScaleFactor: 2}); + await page.setViewport({width: 530, height: 1024, deviceScaleFactor: 2}); await page.goto(`${options.realmUrl}/devlogin`); // wait for Iago devlogin button and click on it. await page.waitForSelector('[value="iago@zulip.com"]'); @@ -64,17 +64,33 @@ async function run() { const messageListSelector = "#message-lists-container"; await page.waitForSelector(messageListSelector); + // remove unread marker and don't select message + const marker = `.message-list[data-message-list-id="${CSS.escape( + message_list_id, + )}"] .unread_marker`; + await page.evaluate((sel) => { + $(sel).remove(); + }, marker); + const messageSelector = `#message-row-${message_list_id}-${CSS.escape(options.messageId)}`; await page.waitForSelector(messageSelector); const messageListBox = await page.$(messageListSelector); await page.evaluate((msg) => $(msg).removeClass("selected_message"), messageSelector); + // This is done so as to get white background while capturing screenshots. + const background_selectors = [".app-main", ".message-feed", ".message_header"]; + await page.evaluate((selectors) => { + for (const selector of selectors) { + $(selector).css("background-color", "white"); + } + }, background_selectors); + // Compute screenshot area, with some padding around the message group const clip = {...(await messageListBox.boundingBox())}; - clip.x -= 5; - clip.width += 10; - clip.y += 5; + clip.width -= 64; + clip.y += 10; + clip.height -= 8; const imagePath = options.imagePath; const imageDir = path.dirname(imagePath); mkdirp.sync(imageDir);