events: Add 'onboarding_steps' event deprecating 'hotspots'.

Earlier, the event sent when an onboarding step (hotspot till now)
is marked as read generated an event with type='hotspots' and
'hotspots' named array in it.

This commit renames the type to 'onboarding_steps' and the array
to 'onboarding_steps' to reflect the fact that it'll also contain
data for elements other than hotspots.
This commit is contained in:
Prakhar Pratyush 2023-12-02 16:00:35 +05:30 committed by Tim Abbott
parent dde3d72100
commit 83bd9955e3
13 changed files with 90 additions and 60 deletions

View File

@ -22,6 +22,14 @@ format used by the Zulip server that they are interacting with.
**Feature level 233**
* [`POST /register`](/api/register-queue), [`GET /events`](/api/get-events):
Renamed the event type `hotspots` and the `hotspots` array field in it
to `onboarding_steps` as this event is sent to clients with remaining
onboarding steps data that includes hotspots and one-time notices to display.
Earlier, we had hotspots only. Added a `type` field to the objects in
the renamed `onboarding_steps` array to distinguish between the two type
of onboarding steps.
* `POST /users/me/onboarding_steps`: Added a new endpoint that
deprecates the `/users/me/hotspots` endpoint. Added support for
displaying one-time notices in addition to existing hotspots.

View File

@ -336,7 +336,7 @@ export function load_new(new_hotspots) {
}
export function initialize() {
load_new(page_params.hotspots);
load_new(onboarding_steps.filter_new_hotspots(page_params.onboarding_steps));
// open
$("body").on("click", ".hotspot-icon", function (e) {

View File

@ -16,3 +16,7 @@ export function post_onboarding_step_as_read(onboarding_step_name) {
},
});
}
export function filter_new_hotspots(onboarding_steps) {
return onboarding_steps.filter((onboarding_step) => onboarding_step.type === "hotspot");
}

View File

@ -30,6 +30,7 @@ import * as muted_users_ui from "./muted_users_ui";
import * as narrow_state from "./narrow_state";
import * as narrow_title from "./narrow_title";
import * as navbar_alerts from "./navbar_alerts";
import * as onboarding_steps from "./onboarding_steps";
import * as overlays from "./overlays";
import {page_params} from "./page_params";
import * as peer_data from "./peer_data";
@ -142,11 +143,11 @@ export function dispatch_normal_event(event) {
}
break;
case "hotspots":
hotspots.load_new(event.hotspots);
page_params.hotspots = page_params.hotspots
? [...page_params.hotspots, ...event.hotspots]
: event.hotspots;
case "onboarding_steps":
hotspots.load_new(onboarding_steps.filter_new_hotspots(event.onboarding_steps));
page_params.onboarding_steps = page_params.onboarding_steps
? [...page_params.onboarding_steps, ...event.onboarding_steps]
: event.onboarding_steps;
break;
case "invites_changed":

View File

@ -306,12 +306,12 @@ run_test("default_streams", ({override}) => {
assert_same(args.realm_default_streams, event.default_streams);
});
run_test("hotspots", ({override}) => {
page_params.hotspots = [];
const event = event_fixtures.hotspots;
run_test("onboarding_steps", ({override}) => {
page_params.onboarding_steps = [];
const event = event_fixtures.onboarding_steps;
override(hotspots, "load_new", noop);
dispatch(event);
assert_same(page_params.hotspots, event.hotspots);
assert_same(page_params.onboarding_steps, event.onboarding_steps);
});
run_test("invites_changed", ({override}) => {

View File

@ -169,26 +169,6 @@ exports.fixtures = {
value: true,
},
hotspots: {
type: "hotspots",
hotspots: [
{
name: "topics",
title: "About topics",
description: "Topics are good.",
delay: 1.5,
has_trigger: false,
},
{
name: "compose",
title: "Compose box",
description: "This is where you compose messages.",
delay: 3.14159,
has_trigger: false,
},
],
},
invites_changed: {
type: "invites_changed",
},
@ -207,6 +187,28 @@ exports.fixtures = {
],
},
onboarding_steps: {
type: "onboarding_steps",
onboarding_steps: [
{
type: "hotspot",
name: "topics",
title: "About topics",
description: "Topics are good.",
delay: 1.5,
has_trigger: false,
},
{
type: "hotspot",
name: "compose",
title: "Compose box",
description: "This is where you compose messages.",
delay: 3.14159,
has_trigger: false,
},
],
},
presence: {
type: "presence",
email: "alice@example.com",

View File

@ -5,5 +5,5 @@ from zerver.tornado.django_api import send_event
def do_mark_onboarding_step_as_read(user: UserProfile, onboarding_step: str) -> None:
OnboardingStep.objects.get_or_create(user=user, onboarding_step=onboarding_step)
event = dict(type="hotspots", hotspots=get_next_onboarding_steps(user))
event = dict(type="onboarding_steps", onboarding_steps=get_next_onboarding_steps(user))
send_event(user.realm, event, [user.id])

View File

@ -332,27 +332,29 @@ def check_heartbeat(
_check_heartbeat(var_name, event)
_hotspot = DictType(
_onboarding_steps = DictType(
required_keys=[
("type", str),
("name", str),
],
optional_keys=[
("title", str),
("description", str),
("delay", NumberType()),
("has_trigger", bool),
]
],
)
hotspots_event = event_dict_type(
onboarding_steps_event = event_dict_type(
required_keys=[
("type", Equals("hotspots")),
("type", Equals("onboarding_steps")),
(
"hotspots",
ListType(_hotspot),
"onboarding_steps",
ListType(_onboarding_steps),
),
]
)
check_hotspots = make_checker(hotspots_event)
check_onboarding_steps = make_checker(onboarding_steps_event)
invites_changed_event = event_dict_type(
required_keys=[

View File

@ -184,11 +184,13 @@ def fetch_initial_state_data(
del state["custom_profile_field_types"]["PRONOUNS"]
if want("hotspots"):
# Even if we offered special hotspots for guests without an
if want("onboarding_steps"):
# Even if we offered special onboarding steps for guests without an
# account, we'd maybe need to store their state using cookies
# or local storage, rather than in the database.
state["hotspots"] = [] if user_profile is None else get_next_onboarding_steps(user_profile)
state["onboarding_steps"] = (
[] if user_profile is None else get_next_onboarding_steps(user_profile)
)
if want("message"):
# Since the introduction of `anchor="latest"` in the API,
@ -854,8 +856,8 @@ def apply_event(
if scheduled_message["scheduled_message_id"] == event["scheduled_message_id"]:
del state["scheduled_messages"][idx]
elif event["type"] == "hotspots":
state["hotspots"] = event["hotspots"]
elif event["type"] == "onboarding_steps":
state["onboarding_steps"] = event["onboarding_steps"]
elif event["type"] == "custom_profile_fields":
state["custom_profile_fields"] = event["fields"]
custom_profile_field_ids = {field["id"] for field in state["custom_profile_fields"]}

View File

@ -2272,11 +2272,14 @@ paths:
- type: object
additionalProperties: false
description: |
Event sent when the set of onboarding "hotspots" to show for
Event sent when the set of onboarding steps to show for
the current user have changed (E.g. because the user dismissed one).
Clients that feature a similar tutorial experience to the Zulip
web app may want to handle these events.
**Changes**: Before Zulip 8.0 (feature level 233), this was named
as `Hotspots`. One-time notice wasn't available earlier.
properties:
id:
$ref: "#/components/schemas/EventIdSchema"
@ -2284,18 +2287,21 @@ paths:
allOf:
- $ref: "#/components/schemas/EventTypeSchema"
- enum:
- hotspots
hotspots:
- onboarding_steps
onboarding_steps:
type: array
description: |
An array of dictionaries where each
dictionary contains details about a single hotspot.
dictionary contains details about a single onboarding step.
**Changes**: Before Zulip 8.0 (feature level 233), this array
was named as `hotspots`. One-time notice wasn't available earlier.
items:
$ref: "#/components/schemas/Hotspot"
$ref: "#/components/schemas/OnboardingStep"
example:
{
"type": "hotspots",
"hotspots":
"type": "onboarding_steps",
"onboarding_steps":
[
{
"type": "hotspot",
@ -11815,17 +11821,17 @@ paths:
array will be empty if `enable_drafts_synchronization` is set to `false`.
items:
$ref: "#/components/schemas/Draft"
hotspots:
onboarding_steps:
type: array
description: |
Present if `hotspots` is present in `fetch_event_types`.
Present if `onboarding_steps` is present in `fetch_event_types`.
An array of dictionaries, where each dictionary contains details about
a single onboarding hotspot that should be shown to new users.
a single onboarding step that should be shown to new users.
We expect that only official Zulip clients will interact with these data.
items:
$ref: "#/components/schemas/Hotspot"
$ref: "#/components/schemas/OnboardingStep"
max_message_id:
type: integer
deprecated: true
@ -18552,17 +18558,22 @@ components:
[profile field types](/help/custom-profile-fields#profile-field-types).
**Changes**: New in Zulip 6.0 (feature level 146).
Hotspot:
OnboardingStep:
type: object
additionalProperties: false
description: |
Dictionary containing details of a single hotspot.
Dictionary containing details of a single onboarding step.
**Changes**: Before Zulip 8.0 (feature level 233), this was
named as `Hotspot`. One-time notice wasn't available earlier.
properties:
type:
type: string
description: |
The type of the onboarding step. Valid values are either
'hotspot' or 'one_time_notice'.
**Changes**: New in Zulip 8.0 (feature level 233).
name:
type: string
description: |

View File

@ -1259,10 +1259,10 @@ class FetchQueriesTest(ZulipTestCase):
default_streams=1,
default_stream_groups=1,
drafts=1,
hotspots=1,
message=1,
muted_topics=1,
muted_users=1,
onboarding_steps=1,
presence=1,
realm=1,
realm_bot=1,

View File

@ -145,11 +145,11 @@ from zerver.lib.event_schema import (
check_draft_update,
check_has_zoom_token,
check_heartbeat,
check_hotspots,
check_invites_changed,
check_message,
check_muted_topics,
check_muted_users,
check_onboarding_steps,
check_presence,
check_reaction_add,
check_reaction_remove,
@ -3055,7 +3055,7 @@ class NormalActionsTest(BaseAction):
events = self.verify_action(
lambda: do_mark_onboarding_step_as_read(self.user_profile, "intro_streams")
)
check_hotspots("events[0]", events[0])
check_onboarding_steps("events[0]", events[0])
def test_rename_stream(self) -> None:
for i, include_streams in enumerate([True, False]):

View File

@ -73,7 +73,6 @@ class HomeTest(ZulipTestCase):
"giphy_api_key",
"giphy_rating_options",
"has_zoom_token",
"hotspots",
"insecure_desktop_app",
"is_admin",
"is_billing_admin",
@ -101,6 +100,7 @@ class HomeTest(ZulipTestCase):
"needs_tutorial",
"never_subscribed",
"no_event_queue",
"onboarding_steps",
"password_min_guesses",
"password_min_length",
"presences",