mirror of https://github.com/zulip/zulip.git
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:
parent
dde3d72100
commit
83bd9955e3
|
@ -22,6 +22,14 @@ format used by the Zulip server that they are interacting with.
|
||||||
|
|
||||||
**Feature level 233**
|
**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
|
* `POST /users/me/onboarding_steps`: Added a new endpoint that
|
||||||
deprecates the `/users/me/hotspots` endpoint. Added support for
|
deprecates the `/users/me/hotspots` endpoint. Added support for
|
||||||
displaying one-time notices in addition to existing hotspots.
|
displaying one-time notices in addition to existing hotspots.
|
||||||
|
|
|
@ -336,7 +336,7 @@ export function load_new(new_hotspots) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initialize() {
|
export function initialize() {
|
||||||
load_new(page_params.hotspots);
|
load_new(onboarding_steps.filter_new_hotspots(page_params.onboarding_steps));
|
||||||
|
|
||||||
// open
|
// open
|
||||||
$("body").on("click", ".hotspot-icon", function (e) {
|
$("body").on("click", ".hotspot-icon", function (e) {
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import * as muted_users_ui from "./muted_users_ui";
|
||||||
import * as narrow_state from "./narrow_state";
|
import * as narrow_state from "./narrow_state";
|
||||||
import * as narrow_title from "./narrow_title";
|
import * as narrow_title from "./narrow_title";
|
||||||
import * as navbar_alerts from "./navbar_alerts";
|
import * as navbar_alerts from "./navbar_alerts";
|
||||||
|
import * as onboarding_steps from "./onboarding_steps";
|
||||||
import * as overlays from "./overlays";
|
import * as overlays from "./overlays";
|
||||||
import {page_params} from "./page_params";
|
import {page_params} from "./page_params";
|
||||||
import * as peer_data from "./peer_data";
|
import * as peer_data from "./peer_data";
|
||||||
|
@ -142,11 +143,11 @@ export function dispatch_normal_event(event) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "hotspots":
|
case "onboarding_steps":
|
||||||
hotspots.load_new(event.hotspots);
|
hotspots.load_new(onboarding_steps.filter_new_hotspots(event.onboarding_steps));
|
||||||
page_params.hotspots = page_params.hotspots
|
page_params.onboarding_steps = page_params.onboarding_steps
|
||||||
? [...page_params.hotspots, ...event.hotspots]
|
? [...page_params.onboarding_steps, ...event.onboarding_steps]
|
||||||
: event.hotspots;
|
: event.onboarding_steps;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "invites_changed":
|
case "invites_changed":
|
||||||
|
|
|
@ -306,12 +306,12 @@ run_test("default_streams", ({override}) => {
|
||||||
assert_same(args.realm_default_streams, event.default_streams);
|
assert_same(args.realm_default_streams, event.default_streams);
|
||||||
});
|
});
|
||||||
|
|
||||||
run_test("hotspots", ({override}) => {
|
run_test("onboarding_steps", ({override}) => {
|
||||||
page_params.hotspots = [];
|
page_params.onboarding_steps = [];
|
||||||
const event = event_fixtures.hotspots;
|
const event = event_fixtures.onboarding_steps;
|
||||||
override(hotspots, "load_new", noop);
|
override(hotspots, "load_new", noop);
|
||||||
dispatch(event);
|
dispatch(event);
|
||||||
assert_same(page_params.hotspots, event.hotspots);
|
assert_same(page_params.onboarding_steps, event.onboarding_steps);
|
||||||
});
|
});
|
||||||
|
|
||||||
run_test("invites_changed", ({override}) => {
|
run_test("invites_changed", ({override}) => {
|
||||||
|
|
|
@ -169,26 +169,6 @@ exports.fixtures = {
|
||||||
value: true,
|
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: {
|
invites_changed: {
|
||||||
type: "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: {
|
presence: {
|
||||||
type: "presence",
|
type: "presence",
|
||||||
email: "alice@example.com",
|
email: "alice@example.com",
|
||||||
|
|
|
@ -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:
|
def do_mark_onboarding_step_as_read(user: UserProfile, onboarding_step: str) -> None:
|
||||||
OnboardingStep.objects.get_or_create(user=user, onboarding_step=onboarding_step)
|
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])
|
send_event(user.realm, event, [user.id])
|
||||||
|
|
|
@ -332,27 +332,29 @@ def check_heartbeat(
|
||||||
_check_heartbeat(var_name, event)
|
_check_heartbeat(var_name, event)
|
||||||
|
|
||||||
|
|
||||||
_hotspot = DictType(
|
_onboarding_steps = DictType(
|
||||||
required_keys=[
|
required_keys=[
|
||||||
("type", str),
|
("type", str),
|
||||||
("name", str),
|
("name", str),
|
||||||
|
],
|
||||||
|
optional_keys=[
|
||||||
("title", str),
|
("title", str),
|
||||||
("description", str),
|
("description", str),
|
||||||
("delay", NumberType()),
|
("delay", NumberType()),
|
||||||
("has_trigger", bool),
|
("has_trigger", bool),
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
hotspots_event = event_dict_type(
|
onboarding_steps_event = event_dict_type(
|
||||||
required_keys=[
|
required_keys=[
|
||||||
("type", Equals("hotspots")),
|
("type", Equals("onboarding_steps")),
|
||||||
(
|
(
|
||||||
"hotspots",
|
"onboarding_steps",
|
||||||
ListType(_hotspot),
|
ListType(_onboarding_steps),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
check_hotspots = make_checker(hotspots_event)
|
check_onboarding_steps = make_checker(onboarding_steps_event)
|
||||||
|
|
||||||
invites_changed_event = event_dict_type(
|
invites_changed_event = event_dict_type(
|
||||||
required_keys=[
|
required_keys=[
|
||||||
|
|
|
@ -184,11 +184,13 @@ def fetch_initial_state_data(
|
||||||
|
|
||||||
del state["custom_profile_field_types"]["PRONOUNS"]
|
del state["custom_profile_field_types"]["PRONOUNS"]
|
||||||
|
|
||||||
if want("hotspots"):
|
if want("onboarding_steps"):
|
||||||
# Even if we offered special hotspots for guests without an
|
# Even if we offered special onboarding steps for guests without an
|
||||||
# account, we'd maybe need to store their state using cookies
|
# account, we'd maybe need to store their state using cookies
|
||||||
# or local storage, rather than in the database.
|
# 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"):
|
if want("message"):
|
||||||
# Since the introduction of `anchor="latest"` in the API,
|
# 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"]:
|
if scheduled_message["scheduled_message_id"] == event["scheduled_message_id"]:
|
||||||
del state["scheduled_messages"][idx]
|
del state["scheduled_messages"][idx]
|
||||||
|
|
||||||
elif event["type"] == "hotspots":
|
elif event["type"] == "onboarding_steps":
|
||||||
state["hotspots"] = event["hotspots"]
|
state["onboarding_steps"] = event["onboarding_steps"]
|
||||||
elif event["type"] == "custom_profile_fields":
|
elif event["type"] == "custom_profile_fields":
|
||||||
state["custom_profile_fields"] = event["fields"]
|
state["custom_profile_fields"] = event["fields"]
|
||||||
custom_profile_field_ids = {field["id"] for field in state["custom_profile_fields"]}
|
custom_profile_field_ids = {field["id"] for field in state["custom_profile_fields"]}
|
||||||
|
|
|
@ -2272,11 +2272,14 @@ paths:
|
||||||
- type: object
|
- type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
description: |
|
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).
|
the current user have changed (E.g. because the user dismissed one).
|
||||||
|
|
||||||
Clients that feature a similar tutorial experience to the Zulip
|
Clients that feature a similar tutorial experience to the Zulip
|
||||||
web app may want to handle these events.
|
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:
|
properties:
|
||||||
id:
|
id:
|
||||||
$ref: "#/components/schemas/EventIdSchema"
|
$ref: "#/components/schemas/EventIdSchema"
|
||||||
|
@ -2284,18 +2287,21 @@ paths:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: "#/components/schemas/EventTypeSchema"
|
- $ref: "#/components/schemas/EventTypeSchema"
|
||||||
- enum:
|
- enum:
|
||||||
- hotspots
|
- onboarding_steps
|
||||||
hotspots:
|
onboarding_steps:
|
||||||
type: array
|
type: array
|
||||||
description: |
|
description: |
|
||||||
An array of dictionaries where each
|
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:
|
items:
|
||||||
$ref: "#/components/schemas/Hotspot"
|
$ref: "#/components/schemas/OnboardingStep"
|
||||||
example:
|
example:
|
||||||
{
|
{
|
||||||
"type": "hotspots",
|
"type": "onboarding_steps",
|
||||||
"hotspots":
|
"onboarding_steps":
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"type": "hotspot",
|
"type": "hotspot",
|
||||||
|
@ -11815,17 +11821,17 @@ paths:
|
||||||
array will be empty if `enable_drafts_synchronization` is set to `false`.
|
array will be empty if `enable_drafts_synchronization` is set to `false`.
|
||||||
items:
|
items:
|
||||||
$ref: "#/components/schemas/Draft"
|
$ref: "#/components/schemas/Draft"
|
||||||
hotspots:
|
onboarding_steps:
|
||||||
type: array
|
type: array
|
||||||
description: |
|
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
|
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.
|
We expect that only official Zulip clients will interact with these data.
|
||||||
items:
|
items:
|
||||||
$ref: "#/components/schemas/Hotspot"
|
$ref: "#/components/schemas/OnboardingStep"
|
||||||
max_message_id:
|
max_message_id:
|
||||||
type: integer
|
type: integer
|
||||||
deprecated: true
|
deprecated: true
|
||||||
|
@ -18552,17 +18558,22 @@ components:
|
||||||
[profile field types](/help/custom-profile-fields#profile-field-types).
|
[profile field types](/help/custom-profile-fields#profile-field-types).
|
||||||
|
|
||||||
**Changes**: New in Zulip 6.0 (feature level 146).
|
**Changes**: New in Zulip 6.0 (feature level 146).
|
||||||
Hotspot:
|
OnboardingStep:
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
description: |
|
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:
|
properties:
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
description: |
|
description: |
|
||||||
The type of the onboarding step. Valid values are either
|
The type of the onboarding step. Valid values are either
|
||||||
'hotspot' or 'one_time_notice'.
|
'hotspot' or 'one_time_notice'.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 8.0 (feature level 233).
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
description: |
|
description: |
|
||||||
|
|
|
@ -1259,10 +1259,10 @@ class FetchQueriesTest(ZulipTestCase):
|
||||||
default_streams=1,
|
default_streams=1,
|
||||||
default_stream_groups=1,
|
default_stream_groups=1,
|
||||||
drafts=1,
|
drafts=1,
|
||||||
hotspots=1,
|
|
||||||
message=1,
|
message=1,
|
||||||
muted_topics=1,
|
muted_topics=1,
|
||||||
muted_users=1,
|
muted_users=1,
|
||||||
|
onboarding_steps=1,
|
||||||
presence=1,
|
presence=1,
|
||||||
realm=1,
|
realm=1,
|
||||||
realm_bot=1,
|
realm_bot=1,
|
||||||
|
|
|
@ -145,11 +145,11 @@ from zerver.lib.event_schema import (
|
||||||
check_draft_update,
|
check_draft_update,
|
||||||
check_has_zoom_token,
|
check_has_zoom_token,
|
||||||
check_heartbeat,
|
check_heartbeat,
|
||||||
check_hotspots,
|
|
||||||
check_invites_changed,
|
check_invites_changed,
|
||||||
check_message,
|
check_message,
|
||||||
check_muted_topics,
|
check_muted_topics,
|
||||||
check_muted_users,
|
check_muted_users,
|
||||||
|
check_onboarding_steps,
|
||||||
check_presence,
|
check_presence,
|
||||||
check_reaction_add,
|
check_reaction_add,
|
||||||
check_reaction_remove,
|
check_reaction_remove,
|
||||||
|
@ -3055,7 +3055,7 @@ class NormalActionsTest(BaseAction):
|
||||||
events = self.verify_action(
|
events = self.verify_action(
|
||||||
lambda: do_mark_onboarding_step_as_read(self.user_profile, "intro_streams")
|
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:
|
def test_rename_stream(self) -> None:
|
||||||
for i, include_streams in enumerate([True, False]):
|
for i, include_streams in enumerate([True, False]):
|
||||||
|
|
|
@ -73,7 +73,6 @@ class HomeTest(ZulipTestCase):
|
||||||
"giphy_api_key",
|
"giphy_api_key",
|
||||||
"giphy_rating_options",
|
"giphy_rating_options",
|
||||||
"has_zoom_token",
|
"has_zoom_token",
|
||||||
"hotspots",
|
|
||||||
"insecure_desktop_app",
|
"insecure_desktop_app",
|
||||||
"is_admin",
|
"is_admin",
|
||||||
"is_billing_admin",
|
"is_billing_admin",
|
||||||
|
@ -101,6 +100,7 @@ class HomeTest(ZulipTestCase):
|
||||||
"needs_tutorial",
|
"needs_tutorial",
|
||||||
"never_subscribed",
|
"never_subscribed",
|
||||||
"no_event_queue",
|
"no_event_queue",
|
||||||
|
"onboarding_steps",
|
||||||
"password_min_guesses",
|
"password_min_guesses",
|
||||||
"password_min_length",
|
"password_min_length",
|
||||||
"presences",
|
"presences",
|
||||||
|
|
Loading…
Reference in New Issue