From cc934429fe7d11ddb80ea1a03f917da5a98dc2cd Mon Sep 17 00:00:00 2001 From: Prakhar Pratyush Date: Thu, 16 Nov 2023 14:15:19 +0530 Subject: [PATCH] settings: Add option for followed topics to unread count badge setting. This commit adds a new option 'DMs, mentions, and followed topics' to 'desktop_icon_count_display' setting. The total unread count of DMs, mentions, and followed topics appears in desktop sidebar and browser tab when this option is configured. Some existing options are relabeled and renumbered. We finally have: * All unread messages * DMs, mentions, and followed topics * DMs and mentions * None Fixes #27503. --- api_docs/changelog.md | 9 +++ version.py | 2 +- web/src/settings_config.ts | 12 ++-- web/src/unread.js | 54 ++++++++++++++-- web/tests/unread.test.js | 3 + ...mber_options_desktop_icon_count_display.py | 64 +++++++++++++++++++ zerver/models.py | 8 ++- zerver/openapi/zulip.yaml | 48 ++++++++++---- 8 files changed, 173 insertions(+), 27 deletions(-) create mode 100644 zerver/migrations/0490_renumber_options_desktop_icon_count_display.py diff --git a/api_docs/changelog.md b/api_docs/changelog.md index 72b5374f66..520090c494 100644 --- a/api_docs/changelog.md +++ b/api_docs/changelog.md @@ -20,6 +20,15 @@ format used by the Zulip server that they are interacting with. ## Changes in Zulip 8.0 +**Feature level 227** + +* [`PATCH /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults), + [`POST /register`](/api/register-queue), [`PATCH /settings`](/api/update-settings): + Added `DMs, mentions, and followed topics` option for `desktop_icon_count_display` + setting, and renumbered the options. + The total unread count of DMs, mentions, and followed topics appears in + desktop sidebar and browser tab when this option is configured. + **Feature level 226** * [`POST /register`](/api/register-queue), [`GET /events`](/api/get-events), diff --git a/version.py b/version.py index ec7a8faeb2..2b0148d9e4 100644 --- a/version.py +++ b/version.py @@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.9.3" # Changes should be accompanied by documentation explaining what the # new level means in api_docs/changelog.md, as well as "**Changes**" # entries in the endpoint's documentation in `zulip.yaml`. -API_FEATURE_LEVEL = 226 +API_FEATURE_LEVEL = 227 # Bump the minor PROVISION_VERSION to indicate that folks should provision # only when going from an old version of the code to a newer version. Bump diff --git a/web/src/settings_config.ts b/web/src/settings_config.ts index 211534281a..034e34ff12 100644 --- a/web/src/settings_config.ts +++ b/web/src/settings_config.ts @@ -872,14 +872,18 @@ export const realm_name_in_email_notifications_policy_values = { export const desktop_icon_count_display_values = { messages: { code: 1, - description: $t({defaultMessage: "All unreads"}), + description: $t({defaultMessage: "All unread messages"}), }, - notifiable: { + dm_mention_followed_topic: { code: 2, - description: $t({defaultMessage: "Direct messages and mentions"}), + description: $t({defaultMessage: "DMs, mentions, and followed topics"}), + }, + dm_mention: { + code: 3, + description: $t({defaultMessage: "DMs and mentions"}), }, none: { - code: 3, + code: 4, description: $t({defaultMessage: "None"}), }, }; diff --git a/web/src/unread.js b/web/src/unread.js index 336fdcf92d..2e44f699ec 100644 --- a/web/src/unread.js +++ b/web/src/unread.js @@ -264,6 +264,7 @@ class UnreadTopicCounter { get_counts(include_per_topic_count = false, include_per_topic_latest_msg_id = false) { const res = {}; res.stream_unread_messages = 0; + res.followed_topic_unread_messages = 0; res.stream_count = new Map(); // hash by stream_id -> count for (const [stream_id, per_stream_bucketer] of this.bucketer) { // We track unread counts for streams that may be currently @@ -308,6 +309,8 @@ class UnreadTopicCounter { // unreads. res.stream_count.set(stream_id, this.get_stream_count(stream_id)); res.stream_unread_messages += res.stream_count.get(stream_id).unmuted_count; + res.followed_topic_unread_messages += + res.stream_count.get(stream_id).followed_count; } } @@ -365,9 +368,14 @@ class UnreadTopicCounter { const sub = sub_store.get(stream_id); let unmuted_count = 0; let muted_count = 0; + let followed_count = 0; for (const [topic, msgs] of per_stream_bucketer) { const topic_count = msgs.size; + if (user_topics.is_topic_followed(stream_id, topic)) { + followed_count += topic_count; + } + if (user_topics.is_topic_unmuted_or_followed(stream_id, topic)) { unmuted_count += topic_count; } else if (user_topics.is_topic_muted(stream_id, topic)) { @@ -381,6 +389,7 @@ class UnreadTopicCounter { const stream_count = { unmuted_count, muted_count, + followed_count, stream_is_muted: sub.is_muted, }; return stream_count; @@ -484,6 +493,24 @@ class UnreadTopicCounter { return streams_with_unmuted_mentions; } + get_followed_topic_unread_mentions() { + let followed_topic_unread_mentions = 0; + for (const message_id of unread_mentions_counter) { + const stream_id = this.bucketer.reverse_lookup.get(message_id); + if (stream_id === undefined) { + // This is a direct message containing a mention. + continue; + } + + const stream_bucketer = this.bucketer.get_bucket(stream_id); + const topic = stream_bucketer.reverse_lookup.get(message_id); + if (user_topics.is_topic_followed(stream_id, topic)) { + followed_topic_unread_mentions += 1; + } + } + return followed_topic_unread_mentions; + } + topic_has_any_unread(stream_id, topic) { const per_stream_bucketer = this.bucketer.get_bucket(stream_id); @@ -808,6 +835,9 @@ export function get_counts() { const streams_with_unmuted_mentions = unread_topic_counter.get_streams_with_unmuted_mentions(); res.home_unread_messages = topic_res.stream_unread_messages; res.stream_unread_messages = topic_res.stream_unread_messages; + res.followed_topic_unread_messages_count = topic_res.followed_topic_unread_messages; + res.followed_topic_unread_messages_with_mention_count = + unread_topic_counter.get_followed_topic_unread_mentions(); res.stream_count = topic_res.stream_count; res.streams_with_mentions = [...streams_with_mentions]; res.streams_with_unmuted_mentions = [...streams_with_unmuted_mentions]; @@ -824,19 +854,33 @@ export function get_counts() { export function calculate_notifiable_count(res) { let new_message_count = 0; - const only_show_notifiable = + const only_show_dm_mention = user_settings.desktop_icon_count_display === - settings_config.desktop_icon_count_display_values.notifiable.code; + settings_config.desktop_icon_count_display_values.dm_mention.code; + const only_show_dm_mention_followed_topic = + user_settings.desktop_icon_count_display === + settings_config.desktop_icon_count_display_values.dm_mention_followed_topic.code; const no_notifications = user_settings.desktop_icon_count_display === settings_config.desktop_icon_count_display_values.none.code; - if (only_show_notifiable) { - // DESKTOP_ICON_COUNT_DISPLAY_NOTIFIABLE - new_message_count = + + if (only_show_dm_mention || only_show_dm_mention_followed_topic) { + // DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION + const dm_mention_count = res.mentioned_message_count + res.direct_message_count - // Avoid double-counting direct messages containing mentions res.direct_message_with_mention_count; + if (only_show_dm_mention_followed_topic) { + // DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION_FOLLOWED_TOPIC + // Avoid double-counting followed topic messages containing mentions + new_message_count = + dm_mention_count + + res.followed_topic_unread_messages_count - + res.followed_topic_unread_messages_with_mention_count; + } else { + new_message_count = dm_mention_count; + } } else if (no_notifications) { // DESKTOP_ICON_COUNT_DISPLAY_NONE new_message_count = 0; diff --git a/web/tests/unread.test.js b/web/tests/unread.test.js index 2a73ae0c15..82db19a280 100644 --- a/web/tests/unread.test.js +++ b/web/tests/unread.test.js @@ -59,6 +59,9 @@ function test_notifiable_count(home_unread_messages, expected_notifiable_count) assert.deepEqual(notifiable_counts, expected_notifiable_count); user_settings.desktop_icon_count_display = 3; notifiable_counts = unread.get_notifiable_count(); + assert.deepEqual(notifiable_counts, expected_notifiable_count); + user_settings.desktop_icon_count_display = 4; + notifiable_counts = unread.get_notifiable_count(); assert.deepEqual(notifiable_counts, 0); } diff --git a/zerver/migrations/0490_renumber_options_desktop_icon_count_display.py b/zerver/migrations/0490_renumber_options_desktop_icon_count_display.py new file mode 100644 index 0000000000..868bde1f17 --- /dev/null +++ b/zerver/migrations/0490_renumber_options_desktop_icon_count_display.py @@ -0,0 +1,64 @@ +# Generated by Django 4.2.7 on 2023-11-17 04:55 + +from django.db import migrations +from django.db.backends.base.schema import BaseDatabaseSchemaEditor +from django.db.migrations.state import StateApps + +OLD_DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION = 2 +NEW_DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION = 3 +OLD_DESKTOP_ICON_COUNT_DISPLAY_NONE = 3 +NEW_DESKTOP_ICON_COUNT_DISPLAY_NONE = 4 + + +def renumber_options(apps: StateApps, schema_editor: BaseDatabaseSchemaEditor) -> None: + # We added a new option 'DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION_FOLLOWED_TOPIC' + # for 'desktop_icon_count_display' setting. It has the value 2. + # The following options are renumbered: + # * 'DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION' from 2 to 3 + # * 'DESKTOP_ICON_COUNT_DISPLAY_NONE' from 3 to 4 + # The migration is to update these values. + RealmUserDefault = apps.get_model("zerver", "RealmUserDefault") + UserProfile = apps.get_model("zerver", "UserProfile") + + UserProfile.objects.filter( + desktop_icon_count_display=OLD_DESKTOP_ICON_COUNT_DISPLAY_NONE + ).update(desktop_icon_count_display=NEW_DESKTOP_ICON_COUNT_DISPLAY_NONE) + RealmUserDefault.objects.filter( + desktop_icon_count_display=OLD_DESKTOP_ICON_COUNT_DISPLAY_NONE + ).update(desktop_icon_count_display=NEW_DESKTOP_ICON_COUNT_DISPLAY_NONE) + + UserProfile.objects.filter( + desktop_icon_count_display=OLD_DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION + ).update(desktop_icon_count_display=NEW_DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION) + RealmUserDefault.objects.filter( + desktop_icon_count_display=OLD_DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION + ).update(desktop_icon_count_display=NEW_DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION) + + +def reverse_code(apps: StateApps, schema_editor: BaseDatabaseSchemaEditor) -> None: + RealmUserDefault = apps.get_model("zerver", "RealmUserDefault") + UserProfile = apps.get_model("zerver", "UserProfile") + + UserProfile.objects.filter( + desktop_icon_count_display=NEW_DESKTOP_ICON_COUNT_DISPLAY_NONE + ).update(desktop_icon_count_display=OLD_DESKTOP_ICON_COUNT_DISPLAY_NONE) + RealmUserDefault.objects.filter( + desktop_icon_count_display=NEW_DESKTOP_ICON_COUNT_DISPLAY_NONE + ).update(desktop_icon_count_display=OLD_DESKTOP_ICON_COUNT_DISPLAY_NONE) + + UserProfile.objects.filter( + desktop_icon_count_display=NEW_DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION + ).update(desktop_icon_count_display=OLD_DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION) + RealmUserDefault.objects.filter( + desktop_icon_count_display=NEW_DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION + ).update(desktop_icon_count_display=OLD_DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION) + + +class Migration(migrations.Migration): + dependencies = [ + ("zerver", "0489_alter_realm_can_access_all_users_group"), + ] + + operations = [ + migrations.RunPython(renumber_options, reverse_code=reverse_code, elidable=True), + ] diff --git a/zerver/models.py b/zerver/models.py index dfcff72005..454fd820f3 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -1680,11 +1680,13 @@ class UserBaseSettings(models.Model): enable_online_push_notifications = models.BooleanField(default=True) DESKTOP_ICON_COUNT_DISPLAY_MESSAGES = 1 - DESKTOP_ICON_COUNT_DISPLAY_NOTIFIABLE = 2 - DESKTOP_ICON_COUNT_DISPLAY_NONE = 3 + DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION_FOLLOWED_TOPIC = 2 + DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION = 3 + DESKTOP_ICON_COUNT_DISPLAY_NONE = 4 DESKTOP_ICON_COUNT_DISPLAY_CHOICES = [ DESKTOP_ICON_COUNT_DISPLAY_MESSAGES, - DESKTOP_ICON_COUNT_DISPLAY_NOTIFIABLE, + DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION, + DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION_FOLLOWED_TOPIC, DESKTOP_ICON_COUNT_DISPLAY_NONE, ] desktop_icon_count_display = models.PositiveSmallIntegerField( diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index d480e77f52..f5e17477cd 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -10461,15 +10461,20 @@ paths: description: | Unread count badge (appears in desktop sidebar and browser tab) - - 1 - All unreads - - 2 - Direct messages and mentions - - 3 - None + - 1 - All unread messages + - 2 - DMs, mentions, and followed topics + - 3 - DMs and mentions + - 4 - None + + **Changes**: In Zulip 8.0 (feature level 227), added `DMs, mentions, and followed + topics` option, renumbering the options to insert it in order. schema: type: integer enum: - 1 - 2 - 3 + - 4 example: 1 - name: realm_name_in_email_notifications_policy in: query @@ -12742,9 +12747,14 @@ paths: description: | Unread count badge (appears in desktop sidebar and browser tab) - - 1 - All unreads - - 2 - Direct messages and mentions - - 3 - None + - 1 - All unread messages + - 2 - DMs, mentions, and followed topics + - 3 - DMs and mentions + - 4 - None + + **Changes**: In Zulip 8.0 (feature level 227), added `DMs, mentions, + and followed topics` option, renumbering the options to insert it in + order. realm_name_in_email_notifications_policy: type: integer description: | @@ -14934,9 +14944,14 @@ paths: description: | Unread count badge (appears in desktop sidebar and browser tab) - - 1 - All unreads - - 2 - Direct messages and mentions - - 3 - None + - 1 - All unread messages + - 2 - DMs, mentions, and followed topics + - 3 - DMs and mentions + - 4 - None + + **Changes**: In Zulip 8.0 (feature level 227), added `DMs, mentions, + and followed topics` option, renumbering the options to insert it in + order. realm_name_in_email_notifications_policy: type: integer description: | @@ -16219,18 +16234,23 @@ paths: description: | Unread count badge (appears in desktop sidebar and browser tab) - - 1 - All unreads - - 2 - Direct messages and mentions - - 3 - None + - 1 - All unread messages + - 2 - DMs, mentions, and followed topics + - 3 - DMs and mentions + - 4 - None - **Changes**: Before Zulip 5.0 (feature level 80), this setting was managed by - the `PATCH /settings/notifications` endpoint. + **Changes**: In Zulip 8.0 (feature level 227), added `DMs, mentions, and followed + topics` option, renumbering the options to insert it in order. + + Before Zulip 5.0 (feature level 80), this setting was managed by the + `PATCH /settings/notifications` endpoint. schema: type: integer enum: - 1 - 2 - 3 + - 4 example: 1 - name: realm_name_in_email_notifications_policy in: query