2019-02-27 19:19:49 +01:00
|
|
|
# See https://zulip.readthedocs.io/en/latest/subsystems/hotspots.html
|
|
|
|
# for documentation on this subsystem.
|
2020-06-11 00:54:34 +02:00
|
|
|
from typing import Dict, List
|
|
|
|
|
2017-08-30 02:42:52 +02:00
|
|
|
from django.conf import settings
|
2020-10-17 03:00:33 +02:00
|
|
|
from django.utils.functional import Promise
|
2021-04-16 00:57:30 +02:00
|
|
|
from django.utils.translation import gettext_lazy
|
2018-04-18 18:16:30 +02:00
|
|
|
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.models import UserHotspot, UserProfile
|
2017-04-15 05:50:59 +02:00
|
|
|
|
2021-02-18 15:17:03 +01:00
|
|
|
INTRO_HOTSPOTS: Dict[str, Dict[str, Promise]] = {
|
2021-02-12 08:20:45 +01:00
|
|
|
"intro_reply": {
|
2021-04-16 00:57:30 +02:00
|
|
|
"title": gettext_lazy("Reply to a message"),
|
|
|
|
"description": gettext_lazy("Click anywhere on a message to reply."),
|
2017-07-14 03:20:27 +02:00
|
|
|
},
|
2021-02-12 08:20:45 +01:00
|
|
|
"intro_streams": {
|
2021-04-16 00:57:30 +02:00
|
|
|
"title": gettext_lazy("Catch up on a stream"),
|
|
|
|
"description": gettext_lazy(
|
2021-02-12 08:20:45 +01:00
|
|
|
"Messages sent to a stream are seen by everyone subscribed "
|
|
|
|
"to that stream. Try clicking on one of the stream links below."
|
2020-10-17 03:00:33 +02:00
|
|
|
),
|
2017-08-30 02:19:55 +02:00
|
|
|
},
|
2021-02-12 08:20:45 +01:00
|
|
|
"intro_topics": {
|
2021-04-16 00:57:30 +02:00
|
|
|
"title": gettext_lazy("Topics"),
|
|
|
|
"description": gettext_lazy(
|
2021-02-12 08:20:45 +01:00
|
|
|
"Every message has a topic. Topics keep conversations "
|
|
|
|
"easy to follow, and make it easy to reply to conversations that start "
|
|
|
|
"while you are offline."
|
2020-10-17 03:00:33 +02:00
|
|
|
),
|
2017-08-30 02:19:55 +02:00
|
|
|
},
|
2021-02-12 08:20:45 +01:00
|
|
|
"intro_gear": {
|
2021-04-16 00:57:30 +02:00
|
|
|
"title": gettext_lazy("Settings"),
|
|
|
|
"description": gettext_lazy(
|
2021-02-12 08:20:45 +01:00
|
|
|
"Go to Settings to configure your notifications and display settings."
|
2020-10-17 03:00:33 +02:00
|
|
|
),
|
2019-02-05 19:28:56 +01:00
|
|
|
},
|
2021-02-12 08:20:45 +01:00
|
|
|
"intro_compose": {
|
2021-04-16 00:57:30 +02:00
|
|
|
"title": gettext_lazy("Compose"),
|
|
|
|
"description": gettext_lazy(
|
2021-02-12 08:20:45 +01:00
|
|
|
"Click here to start a new conversation. Pick a topic "
|
|
|
|
"(2-3 words is best), and give it a go!"
|
2020-10-17 03:00:33 +02:00
|
|
|
),
|
2017-07-14 03:20:27 +02:00
|
|
|
},
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
}
|
2017-01-24 01:48:35 +01:00
|
|
|
|
2021-02-18 15:17:03 +01:00
|
|
|
# We would most likely implement new hotspots in the future that aren't
|
|
|
|
# a part of the initial tutorial. To that end, classifying them into
|
|
|
|
# categories which are aggregated in ALL_HOTSPOTS, seems like a good start.
|
|
|
|
ALL_HOTSPOTS: Dict[str, Dict[str, Promise]] = {
|
|
|
|
**INTRO_HOTSPOTS,
|
|
|
|
}
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def get_next_hotspots(user: UserProfile) -> List[Dict[str, object]]:
|
2018-03-18 20:59:10 +01:00
|
|
|
# For manual testing, it can be convenient to set
|
|
|
|
# ALWAYS_SEND_ALL_HOTSPOTS=True in `zproject/dev_settings.py` to
|
2019-02-27 19:19:49 +01:00
|
|
|
# make it easy to click on all of the hotspots. Note that
|
|
|
|
# ALWAYS_SEND_ALL_HOTSPOTS has some bugs; see ReadTheDocs (link
|
|
|
|
# above) for details.
|
2021-02-18 15:17:03 +01:00
|
|
|
#
|
|
|
|
# Since this is just for development purposes, it's convinient for us to send
|
|
|
|
# all the hotspots rather than any specific category.
|
2018-03-18 20:59:10 +01:00
|
|
|
if settings.ALWAYS_SEND_ALL_HOTSPOTS:
|
2021-02-12 08:19:30 +01:00
|
|
|
return [
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"name": hotspot,
|
|
|
|
"title": str(ALL_HOTSPOTS[hotspot]["title"]),
|
|
|
|
"description": str(ALL_HOTSPOTS[hotspot]["description"]),
|
|
|
|
"delay": 0,
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
for hotspot in ALL_HOTSPOTS
|
|
|
|
]
|
2017-08-02 07:23:27 +02:00
|
|
|
|
2021-03-11 17:19:49 +01:00
|
|
|
# If a Zulip server has disabled the tutorial, never send hotspots.
|
|
|
|
if not settings.TUTORIAL_ENABLED:
|
|
|
|
return []
|
|
|
|
|
2017-08-31 18:20:03 +02:00
|
|
|
if user.tutorial_status == UserProfile.TUTORIAL_FINISHED:
|
|
|
|
return []
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
seen_hotspots = frozenset(
|
2021-02-12 08:20:45 +01:00
|
|
|
UserHotspot.objects.filter(user=user).values_list("hotspot", flat=True)
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2021-02-18 15:17:03 +01:00
|
|
|
for hotspot in INTRO_HOTSPOTS.keys():
|
2017-01-24 01:48:35 +01:00
|
|
|
if hotspot not in seen_hotspots:
|
2021-02-12 08:19:30 +01:00
|
|
|
return [
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"name": hotspot,
|
2021-02-18 15:17:03 +01:00
|
|
|
"title": str(INTRO_HOTSPOTS[hotspot]["title"]),
|
|
|
|
"description": str(INTRO_HOTSPOTS[hotspot]["description"]),
|
2021-02-12 08:20:45 +01:00
|
|
|
"delay": 0.5,
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
]
|
2017-08-31 18:20:03 +02:00
|
|
|
|
|
|
|
user.tutorial_status = UserProfile.TUTORIAL_FINISHED
|
2021-02-12 08:20:45 +01:00
|
|
|
user.save(update_fields=["tutorial_status"])
|
2017-01-24 01:48:35 +01:00
|
|
|
return []
|
2018-06-13 14:10:53 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-12-20 13:03:32 +01:00
|
|
|
def copy_hotspots(source_profile: UserProfile, target_profile: UserProfile) -> None:
|
2018-06-13 14:10:53 +02:00
|
|
|
for userhotspot in frozenset(UserHotspot.objects.filter(user=source_profile)):
|
2021-02-12 08:19:30 +01:00
|
|
|
UserHotspot.objects.create(
|
|
|
|
user=target_profile, hotspot=userhotspot.hotspot, timestamp=userhotspot.timestamp
|
|
|
|
)
|
2018-06-13 14:10:53 +02:00
|
|
|
|
|
|
|
target_profile.tutorial_status = source_profile.tutorial_status
|
|
|
|
target_profile.onboarding_steps = source_profile.onboarding_steps
|
2021-02-12 08:20:45 +01:00
|
|
|
target_profile.save(update_fields=["tutorial_status", "onboarding_steps"])
|