mirror of https://github.com/zulip/zulip.git
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.
This commit is contained in:
parent
34f4622091
commit
cc934429fe
|
@ -20,6 +20,15 @@ format used by the Zulip server that they are interacting with.
|
||||||
|
|
||||||
## Changes in Zulip 8.0
|
## 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**
|
**Feature level 226**
|
||||||
|
|
||||||
* [`POST /register`](/api/register-queue), [`GET /events`](/api/get-events),
|
* [`POST /register`](/api/register-queue), [`GET /events`](/api/get-events),
|
||||||
|
|
|
@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
|
||||||
# Changes should be accompanied by documentation explaining what the
|
# Changes should be accompanied by documentation explaining what the
|
||||||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
# 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
|
# 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
|
# only when going from an old version of the code to a newer version. Bump
|
||||||
|
|
|
@ -872,14 +872,18 @@ export const realm_name_in_email_notifications_policy_values = {
|
||||||
export const desktop_icon_count_display_values = {
|
export const desktop_icon_count_display_values = {
|
||||||
messages: {
|
messages: {
|
||||||
code: 1,
|
code: 1,
|
||||||
description: $t({defaultMessage: "All unreads"}),
|
description: $t({defaultMessage: "All unread messages"}),
|
||||||
},
|
},
|
||||||
notifiable: {
|
dm_mention_followed_topic: {
|
||||||
code: 2,
|
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: {
|
none: {
|
||||||
code: 3,
|
code: 4,
|
||||||
description: $t({defaultMessage: "None"}),
|
description: $t({defaultMessage: "None"}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -264,6 +264,7 @@ class UnreadTopicCounter {
|
||||||
get_counts(include_per_topic_count = false, include_per_topic_latest_msg_id = false) {
|
get_counts(include_per_topic_count = false, include_per_topic_latest_msg_id = false) {
|
||||||
const res = {};
|
const res = {};
|
||||||
res.stream_unread_messages = 0;
|
res.stream_unread_messages = 0;
|
||||||
|
res.followed_topic_unread_messages = 0;
|
||||||
res.stream_count = new Map(); // hash by stream_id -> count
|
res.stream_count = new Map(); // hash by stream_id -> count
|
||||||
for (const [stream_id, per_stream_bucketer] of this.bucketer) {
|
for (const [stream_id, per_stream_bucketer] of this.bucketer) {
|
||||||
// We track unread counts for streams that may be currently
|
// We track unread counts for streams that may be currently
|
||||||
|
@ -308,6 +309,8 @@ class UnreadTopicCounter {
|
||||||
// unreads.
|
// unreads.
|
||||||
res.stream_count.set(stream_id, this.get_stream_count(stream_id));
|
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.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);
|
const sub = sub_store.get(stream_id);
|
||||||
let unmuted_count = 0;
|
let unmuted_count = 0;
|
||||||
let muted_count = 0;
|
let muted_count = 0;
|
||||||
|
let followed_count = 0;
|
||||||
for (const [topic, msgs] of per_stream_bucketer) {
|
for (const [topic, msgs] of per_stream_bucketer) {
|
||||||
const topic_count = msgs.size;
|
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)) {
|
if (user_topics.is_topic_unmuted_or_followed(stream_id, topic)) {
|
||||||
unmuted_count += topic_count;
|
unmuted_count += topic_count;
|
||||||
} else if (user_topics.is_topic_muted(stream_id, topic)) {
|
} else if (user_topics.is_topic_muted(stream_id, topic)) {
|
||||||
|
@ -381,6 +389,7 @@ class UnreadTopicCounter {
|
||||||
const stream_count = {
|
const stream_count = {
|
||||||
unmuted_count,
|
unmuted_count,
|
||||||
muted_count,
|
muted_count,
|
||||||
|
followed_count,
|
||||||
stream_is_muted: sub.is_muted,
|
stream_is_muted: sub.is_muted,
|
||||||
};
|
};
|
||||||
return stream_count;
|
return stream_count;
|
||||||
|
@ -484,6 +493,24 @@ class UnreadTopicCounter {
|
||||||
return streams_with_unmuted_mentions;
|
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) {
|
topic_has_any_unread(stream_id, topic) {
|
||||||
const per_stream_bucketer = this.bucketer.get_bucket(stream_id);
|
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();
|
const streams_with_unmuted_mentions = unread_topic_counter.get_streams_with_unmuted_mentions();
|
||||||
res.home_unread_messages = topic_res.stream_unread_messages;
|
res.home_unread_messages = topic_res.stream_unread_messages;
|
||||||
res.stream_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.stream_count = topic_res.stream_count;
|
||||||
res.streams_with_mentions = [...streams_with_mentions];
|
res.streams_with_mentions = [...streams_with_mentions];
|
||||||
res.streams_with_unmuted_mentions = [...streams_with_unmuted_mentions];
|
res.streams_with_unmuted_mentions = [...streams_with_unmuted_mentions];
|
||||||
|
@ -824,19 +854,33 @@ export function get_counts() {
|
||||||
export function calculate_notifiable_count(res) {
|
export function calculate_notifiable_count(res) {
|
||||||
let new_message_count = 0;
|
let new_message_count = 0;
|
||||||
|
|
||||||
const only_show_notifiable =
|
const only_show_dm_mention =
|
||||||
user_settings.desktop_icon_count_display ===
|
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 =
|
const no_notifications =
|
||||||
user_settings.desktop_icon_count_display ===
|
user_settings.desktop_icon_count_display ===
|
||||||
settings_config.desktop_icon_count_display_values.none.code;
|
settings_config.desktop_icon_count_display_values.none.code;
|
||||||
if (only_show_notifiable) {
|
|
||||||
// DESKTOP_ICON_COUNT_DISPLAY_NOTIFIABLE
|
if (only_show_dm_mention || only_show_dm_mention_followed_topic) {
|
||||||
new_message_count =
|
// DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION
|
||||||
|
const dm_mention_count =
|
||||||
res.mentioned_message_count +
|
res.mentioned_message_count +
|
||||||
res.direct_message_count -
|
res.direct_message_count -
|
||||||
// Avoid double-counting direct messages containing mentions
|
// Avoid double-counting direct messages containing mentions
|
||||||
res.direct_message_with_mention_count;
|
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) {
|
} else if (no_notifications) {
|
||||||
// DESKTOP_ICON_COUNT_DISPLAY_NONE
|
// DESKTOP_ICON_COUNT_DISPLAY_NONE
|
||||||
new_message_count = 0;
|
new_message_count = 0;
|
||||||
|
|
|
@ -59,6 +59,9 @@ function test_notifiable_count(home_unread_messages, expected_notifiable_count)
|
||||||
assert.deepEqual(notifiable_counts, expected_notifiable_count);
|
assert.deepEqual(notifiable_counts, expected_notifiable_count);
|
||||||
user_settings.desktop_icon_count_display = 3;
|
user_settings.desktop_icon_count_display = 3;
|
||||||
notifiable_counts = unread.get_notifiable_count();
|
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);
|
assert.deepEqual(notifiable_counts, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
|
]
|
|
@ -1680,11 +1680,13 @@ class UserBaseSettings(models.Model):
|
||||||
enable_online_push_notifications = models.BooleanField(default=True)
|
enable_online_push_notifications = models.BooleanField(default=True)
|
||||||
|
|
||||||
DESKTOP_ICON_COUNT_DISPLAY_MESSAGES = 1
|
DESKTOP_ICON_COUNT_DISPLAY_MESSAGES = 1
|
||||||
DESKTOP_ICON_COUNT_DISPLAY_NOTIFIABLE = 2
|
DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION_FOLLOWED_TOPIC = 2
|
||||||
DESKTOP_ICON_COUNT_DISPLAY_NONE = 3
|
DESKTOP_ICON_COUNT_DISPLAY_DM_MENTION = 3
|
||||||
|
DESKTOP_ICON_COUNT_DISPLAY_NONE = 4
|
||||||
DESKTOP_ICON_COUNT_DISPLAY_CHOICES = [
|
DESKTOP_ICON_COUNT_DISPLAY_CHOICES = [
|
||||||
DESKTOP_ICON_COUNT_DISPLAY_MESSAGES,
|
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_NONE,
|
||||||
]
|
]
|
||||||
desktop_icon_count_display = models.PositiveSmallIntegerField(
|
desktop_icon_count_display = models.PositiveSmallIntegerField(
|
||||||
|
|
|
@ -10461,15 +10461,20 @@ paths:
|
||||||
description: |
|
description: |
|
||||||
Unread count badge (appears in desktop sidebar and browser tab)
|
Unread count badge (appears in desktop sidebar and browser tab)
|
||||||
|
|
||||||
- 1 - All unreads
|
- 1 - All unread messages
|
||||||
- 2 - Direct messages and mentions
|
- 2 - DMs, mentions, and followed topics
|
||||||
- 3 - None
|
- 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:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
enum:
|
enum:
|
||||||
- 1
|
- 1
|
||||||
- 2
|
- 2
|
||||||
- 3
|
- 3
|
||||||
|
- 4
|
||||||
example: 1
|
example: 1
|
||||||
- name: realm_name_in_email_notifications_policy
|
- name: realm_name_in_email_notifications_policy
|
||||||
in: query
|
in: query
|
||||||
|
@ -12742,9 +12747,14 @@ paths:
|
||||||
description: |
|
description: |
|
||||||
Unread count badge (appears in desktop sidebar and browser tab)
|
Unread count badge (appears in desktop sidebar and browser tab)
|
||||||
|
|
||||||
- 1 - All unreads
|
- 1 - All unread messages
|
||||||
- 2 - Direct messages and mentions
|
- 2 - DMs, mentions, and followed topics
|
||||||
- 3 - None
|
- 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:
|
realm_name_in_email_notifications_policy:
|
||||||
type: integer
|
type: integer
|
||||||
description: |
|
description: |
|
||||||
|
@ -14934,9 +14944,14 @@ paths:
|
||||||
description: |
|
description: |
|
||||||
Unread count badge (appears in desktop sidebar and browser tab)
|
Unread count badge (appears in desktop sidebar and browser tab)
|
||||||
|
|
||||||
- 1 - All unreads
|
- 1 - All unread messages
|
||||||
- 2 - Direct messages and mentions
|
- 2 - DMs, mentions, and followed topics
|
||||||
- 3 - None
|
- 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:
|
realm_name_in_email_notifications_policy:
|
||||||
type: integer
|
type: integer
|
||||||
description: |
|
description: |
|
||||||
|
@ -16219,18 +16234,23 @@ paths:
|
||||||
description: |
|
description: |
|
||||||
Unread count badge (appears in desktop sidebar and browser tab)
|
Unread count badge (appears in desktop sidebar and browser tab)
|
||||||
|
|
||||||
- 1 - All unreads
|
- 1 - All unread messages
|
||||||
- 2 - Direct messages and mentions
|
- 2 - DMs, mentions, and followed topics
|
||||||
- 3 - None
|
- 3 - DMs and mentions
|
||||||
|
- 4 - None
|
||||||
|
|
||||||
**Changes**: Before Zulip 5.0 (feature level 80), this setting was managed by
|
**Changes**: In Zulip 8.0 (feature level 227), added `DMs, mentions, and followed
|
||||||
the `PATCH /settings/notifications` endpoint.
|
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:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
enum:
|
enum:
|
||||||
- 1
|
- 1
|
||||||
- 2
|
- 2
|
||||||
- 3
|
- 3
|
||||||
|
- 4
|
||||||
example: 1
|
example: 1
|
||||||
- name: realm_name_in_email_notifications_policy
|
- name: realm_name_in_email_notifications_policy
|
||||||
in: query
|
in: query
|
||||||
|
|
Loading…
Reference in New Issue