diff --git a/api_docs/changelog.md b/api_docs/changelog.md index 14cb232560..333ce0132c 100644 --- a/api_docs/changelog.md +++ b/api_docs/changelog.md @@ -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. diff --git a/web/src/hotspots.js b/web/src/hotspots.js index 3c723c905a..537cb9427b 100644 --- a/web/src/hotspots.js +++ b/web/src/hotspots.js @@ -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) { diff --git a/web/src/onboarding_steps.js b/web/src/onboarding_steps.js index 844e4ffdc8..85bf257884 100644 --- a/web/src/onboarding_steps.js +++ b/web/src/onboarding_steps.js @@ -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"); +} diff --git a/web/src/server_events_dispatch.js b/web/src/server_events_dispatch.js index 5bda5aede7..b54796aeb7 100644 --- a/web/src/server_events_dispatch.js +++ b/web/src/server_events_dispatch.js @@ -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": diff --git a/web/tests/dispatch.test.js b/web/tests/dispatch.test.js index edef50fbdb..cc3e04f5c6 100644 --- a/web/tests/dispatch.test.js +++ b/web/tests/dispatch.test.js @@ -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}) => { diff --git a/web/tests/lib/events.js b/web/tests/lib/events.js index ff20d6bb72..408f279cb3 100644 --- a/web/tests/lib/events.js +++ b/web/tests/lib/events.js @@ -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", diff --git a/zerver/actions/hotspots.py b/zerver/actions/hotspots.py index 75805e8511..52095d6157 100644 --- a/zerver/actions/hotspots.py +++ b/zerver/actions/hotspots.py @@ -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]) diff --git a/zerver/lib/event_schema.py b/zerver/lib/event_schema.py index 0152c9f4e9..b54a008353 100644 --- a/zerver/lib/event_schema.py +++ b/zerver/lib/event_schema.py @@ -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=[ diff --git a/zerver/lib/events.py b/zerver/lib/events.py index 56cc3b3253..c594365140 100644 --- a/zerver/lib/events.py +++ b/zerver/lib/events.py @@ -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"]} diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index fc8ead919d..1bddf90069 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -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: | diff --git a/zerver/tests/test_event_system.py b/zerver/tests/test_event_system.py index 4cf089f4f6..dcb879ac6c 100644 --- a/zerver/tests/test_event_system.py +++ b/zerver/tests/test_event_system.py @@ -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, diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index efbe534014..2c49e68ff6 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -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]): diff --git a/zerver/tests/test_home.py b/zerver/tests/test_home.py index 66a1f0ff6c..52e5f1482f 100644 --- a/zerver/tests/test_home.py +++ b/zerver/tests/test_home.py @@ -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",