mirror of https://github.com/zulip/zulip.git
events/tests/api: Send realm_playground events to clients.
We send the whole data set as a part of the event rather than doing an add/remove operation for couple of reasons: * This would make the client logic simpler. * The playground data is small enough for us to not worry about performance. Tweaked both `fetch_initial_state_data` and `apply_events` to handle the new playground event. Tests added to validate the event matches the expected schema. Documented realm_playgrounds sections inside /events and /register to support our openapi validation system in test_events. Tweaked other tests like test_event_system.py and test_home.py to account for the new event being generated. Lastly, documented the changes to the API endpoints in api/changelog.md and bumped API_FEATURE_LEVEL. Tweaked by tabbott to add an `id` field in RealmPlayground objects sent to clients, which is essential to sending the API request to remove one.
This commit is contained in:
parent
d2e5b62dce
commit
1ac8fe7538
|
@ -10,6 +10,18 @@ below features are supported.
|
|||
|
||||
## Changes in Zulip 4.0
|
||||
|
||||
**Feature level 49**
|
||||
|
||||
* Added new [`POST /realm/playground`](/api/add-playground) and
|
||||
[`DELETE /realm/playground/{playground_id}`](/api/remove-playground)
|
||||
endpoints for realm playgrounds.
|
||||
* [`GET /events`](/api/get-events): A new `realm_playgrounds` events
|
||||
is sent when changes are made to a set of configured playgrounds for
|
||||
an organization.
|
||||
* [`POST /register`](/api/register-queue): Added a new `realm_playgrounds`
|
||||
field, which is required to fetch the set of configured playgrounds for
|
||||
an organization.
|
||||
|
||||
**Feature level 48**
|
||||
|
||||
* [`POST /users/me/muted_users/{muted_user_id}`](/api/mute-user),
|
||||
|
|
|
@ -30,7 +30,7 @@ DESKTOP_WARNING_VERSION = "5.2.0"
|
|||
#
|
||||
# Changes should be accompanied by documentation explaining what the
|
||||
# new level means in templates/zerver/api/changelog.md.
|
||||
API_FEATURE_LEVEL = 48
|
||||
API_FEATURE_LEVEL = 49
|
||||
|
||||
# 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
|
||||
|
|
|
@ -226,6 +226,7 @@ from zerver.models import (
|
|||
get_huddle_recipient,
|
||||
get_huddle_user_ids,
|
||||
get_old_unclaimed_attachments,
|
||||
get_realm_playgrounds,
|
||||
get_stream,
|
||||
get_stream_by_id_in_realm,
|
||||
get_stream_cache_key,
|
||||
|
@ -6592,6 +6593,11 @@ def do_remove_realm_domain(
|
|||
send_event(realm, event, active_user_ids(realm.id))
|
||||
|
||||
|
||||
def notify_realm_playgrounds(realm: Realm) -> None:
|
||||
event = dict(type="realm_playgrounds", realm_playgrounds=get_realm_playgrounds(realm))
|
||||
send_event(realm, event, active_user_ids(realm.id))
|
||||
|
||||
|
||||
def do_add_realm_playground(realm: Realm, **kwargs: Any) -> int:
|
||||
realm_playground = RealmPlayground(realm=realm, **kwargs)
|
||||
# We expect full_clean to always pass since a thorough input validation
|
||||
|
@ -6599,11 +6605,13 @@ def do_add_realm_playground(realm: Realm, **kwargs: Any) -> int:
|
|||
# before calling this function.
|
||||
realm_playground.full_clean()
|
||||
realm_playground.save()
|
||||
notify_realm_playgrounds(realm)
|
||||
return realm_playground.id
|
||||
|
||||
|
||||
def do_remove_realm_playground(realm_playground: RealmPlayground) -> None:
|
||||
def do_remove_realm_playground(realm: Realm, realm_playground: RealmPlayground) -> None:
|
||||
realm_playground.delete()
|
||||
notify_realm_playgrounds(realm)
|
||||
|
||||
|
||||
def get_occupied_streams(realm: Realm) -> QuerySet:
|
||||
|
|
|
@ -680,6 +680,24 @@ realm_domains_remove_event = event_dict_type(
|
|||
)
|
||||
check_realm_domains_remove = make_checker(realm_domains_remove_event)
|
||||
|
||||
realm_playground_type = DictType(
|
||||
required_keys=[("id", int), ("name", str), ("pygments_language", str), ("url_prefix", str)]
|
||||
)
|
||||
|
||||
realm_playgrounds_event = event_dict_type(
|
||||
required_keys=[
|
||||
("type", Equals("realm_playgrounds")),
|
||||
("realm_playgrounds", ListType(realm_playground_type)),
|
||||
]
|
||||
)
|
||||
_check_realm_playgrounds = make_checker(realm_playgrounds_event)
|
||||
|
||||
|
||||
def check_realm_playgrounds(var_name: str, event: Dict[str, object]) -> None:
|
||||
_check_realm_playgrounds(var_name, event)
|
||||
assert isinstance(event["realm_playgrounds"], list)
|
||||
|
||||
|
||||
realm_emoji_type = DictType(
|
||||
required_keys=[
|
||||
("id", str),
|
||||
|
|
|
@ -59,6 +59,7 @@ from zerver.models import (
|
|||
custom_profile_fields_for_realm,
|
||||
get_default_stream_groups,
|
||||
get_realm_domains,
|
||||
get_realm_playgrounds,
|
||||
realm_filters_for_realm,
|
||||
)
|
||||
from zerver.tornado.django_api import get_user_events, request_event_queue
|
||||
|
@ -258,6 +259,9 @@ def fetch_initial_state_data(
|
|||
if want("realm_filters"):
|
||||
state["realm_filters"] = realm_filters_for_realm(realm.id)
|
||||
|
||||
if want("realm_playgrounds"):
|
||||
state["realm_playgrounds"] = get_realm_playgrounds(realm)
|
||||
|
||||
if want("realm_user_groups"):
|
||||
state["realm_user_groups"] = user_groups_in_realm_serialized(realm)
|
||||
|
||||
|
@ -981,6 +985,8 @@ def apply_event(
|
|||
state["muted_users"] = event["muted_users"]
|
||||
elif event["type"] == "realm_filters":
|
||||
state["realm_filters"] = event["realm_filters"]
|
||||
elif event["type"] == "realm_playgrounds":
|
||||
state["realm_playgrounds"] = event["realm_playgrounds"]
|
||||
elif event["type"] == "update_display_settings":
|
||||
assert event["setting_name"] in UserProfile.property_types
|
||||
state[event["setting_name"]] = event["setting"]
|
||||
|
|
|
@ -1001,11 +1001,12 @@ class RealmPlayground(models.Model):
|
|||
return f"<RealmPlayground({self.realm.string_id}): {self.pygments_language} {self.name}>"
|
||||
|
||||
|
||||
def get_realm_playgrounds(realm: Realm) -> List[Dict[str, str]]:
|
||||
playgrounds: List[Dict[str, str]] = []
|
||||
def get_realm_playgrounds(realm: Realm) -> List[Dict[str, Union[int, str]]]:
|
||||
playgrounds: List[Dict[str, Union[int, str]]] = []
|
||||
for playground in RealmPlayground.objects.filter(realm=realm).all():
|
||||
playgrounds.append(
|
||||
dict(
|
||||
id=playground.id,
|
||||
name=playground.name,
|
||||
pygments_language=playground.pygments_language,
|
||||
url_prefix=playground.url_prefix,
|
||||
|
|
|
@ -2413,6 +2413,40 @@ paths:
|
|||
],
|
||||
"id": 0,
|
||||
}
|
||||
- type: object
|
||||
additionalProperties: false
|
||||
description: |
|
||||
Event sent to all users in a Zulip organization when the
|
||||
set of configured playgrounds for the organization has changed.
|
||||
properties:
|
||||
id:
|
||||
$ref: "#/components/schemas/EventIdSchema"
|
||||
type:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/EventTypeSchema"
|
||||
- enum:
|
||||
- realm_playgrounds
|
||||
realm_playgrounds:
|
||||
type: array
|
||||
description: |
|
||||
An array of dictionaries where each dictionary contains
|
||||
data about a single playground entry.
|
||||
items:
|
||||
$ref: "#/components/schemas/RealmPlayground"
|
||||
example:
|
||||
{
|
||||
"type": "realm_playgrounds",
|
||||
"realm_playgrounds":
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Python playground",
|
||||
"pygments_language": "Python",
|
||||
"url_prefix": "https://python.example.com",
|
||||
},
|
||||
],
|
||||
"id": 0,
|
||||
}
|
||||
- type: object
|
||||
additionalProperties: false
|
||||
description: |
|
||||
|
@ -6935,6 +6969,15 @@ paths:
|
|||
The second element is the URL with which the
|
||||
pattern matching string should be linkified with and the third element
|
||||
is the id of the realm filter.
|
||||
realm_playgrounds:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/RealmPlayground"
|
||||
description: |
|
||||
Present if `realm_playgrounds` is present in `fetch_event_types`.
|
||||
|
||||
An array of dictionaries where each dictionary describes a playground entry
|
||||
in this Zulip organization.
|
||||
realm_user_groups:
|
||||
type: array
|
||||
items:
|
||||
|
@ -10097,6 +10140,33 @@ components:
|
|||
type: boolean
|
||||
description: |
|
||||
Whether subdomains are allowed for this domain.
|
||||
RealmPlayground:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
description: |
|
||||
Object containing details about a realm playground.
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
description: |
|
||||
The unique ID for the realm playground.
|
||||
name:
|
||||
type: string
|
||||
description: |
|
||||
The user-visible display name of the playground. Clients
|
||||
should display this in UI for picking which playground to
|
||||
open a code block in, to differentiate between multiple
|
||||
configured playground options for a given pygments
|
||||
language.
|
||||
pygments_language:
|
||||
type: string
|
||||
description: |
|
||||
The name of the Pygments language lexer for that
|
||||
programming language.
|
||||
url_prefix:
|
||||
type: string
|
||||
description: |
|
||||
The url prefix for the playground.
|
||||
RealmExport:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
|
|
|
@ -852,7 +852,7 @@ class FetchQueriesTest(ZulipTestCase):
|
|||
with mock.patch("zerver.lib.events.always_want") as want_mock:
|
||||
fetch_initial_state_data(user)
|
||||
|
||||
self.assert_length(queries, 30)
|
||||
self.assert_length(queries, 31)
|
||||
|
||||
expected_counts = dict(
|
||||
alert_words=1,
|
||||
|
@ -871,6 +871,7 @@ class FetchQueriesTest(ZulipTestCase):
|
|||
realm_incoming_webhook_bots=0,
|
||||
realm_emoji=1,
|
||||
realm_filters=1,
|
||||
realm_playgrounds=1,
|
||||
realm_user=3,
|
||||
realm_user_groups=2,
|
||||
recent_private_conversations=1,
|
||||
|
|
|
@ -27,6 +27,7 @@ from zerver.lib.actions import (
|
|||
do_add_linkifier,
|
||||
do_add_reaction,
|
||||
do_add_realm_domain,
|
||||
do_add_realm_playground,
|
||||
do_add_streams_to_default_stream_group,
|
||||
do_add_submessage,
|
||||
do_change_avatar_fields,
|
||||
|
@ -70,6 +71,7 @@ from zerver.lib.actions import (
|
|||
do_remove_realm_custom_profile_field,
|
||||
do_remove_realm_domain,
|
||||
do_remove_realm_emoji,
|
||||
do_remove_realm_playground,
|
||||
do_remove_streams_from_default_stream_group,
|
||||
do_rename_stream,
|
||||
do_revoke_multi_use_invite,
|
||||
|
@ -126,6 +128,7 @@ from zerver.lib.event_schema import (
|
|||
check_realm_emoji_update,
|
||||
check_realm_export,
|
||||
check_realm_filters,
|
||||
check_realm_playgrounds,
|
||||
check_realm_update,
|
||||
check_realm_update_dict,
|
||||
check_realm_user_add,
|
||||
|
@ -176,6 +179,7 @@ from zerver.models import (
|
|||
Realm,
|
||||
RealmAuditLog,
|
||||
RealmDomain,
|
||||
RealmPlayground,
|
||||
Service,
|
||||
Stream,
|
||||
UserGroup,
|
||||
|
@ -191,6 +195,7 @@ from zerver.tornado.event_queue import (
|
|||
allocate_client_descriptor,
|
||||
clear_client_event_queues_for_testing,
|
||||
)
|
||||
from zerver.views.realm_playgrounds import access_playground_by_id
|
||||
|
||||
|
||||
class BaseAction(ZulipTestCase):
|
||||
|
@ -1358,6 +1363,24 @@ class NormalActionsTest(BaseAction):
|
|||
check_realm_domains_remove("events[0]", events[0])
|
||||
self.assertEqual(events[0]["domain"], "zulip.org")
|
||||
|
||||
def test_realm_playground_events(self) -> None:
|
||||
playground_info = dict(
|
||||
name="Python playground",
|
||||
pygments_language="Python",
|
||||
url_prefix="https://python.example.com",
|
||||
)
|
||||
events = self.verify_action(
|
||||
lambda: do_add_realm_playground(self.user_profile.realm, **playground_info)
|
||||
)
|
||||
check_realm_playgrounds("events[0]", events[0])
|
||||
|
||||
last_id = RealmPlayground.objects.last().id
|
||||
realm_playground = access_playground_by_id(self.user_profile.realm, last_id)
|
||||
events = self.verify_action(
|
||||
lambda: do_remove_realm_playground(self.user_profile.realm, realm_playground)
|
||||
)
|
||||
check_realm_playgrounds("events[0]", events[0])
|
||||
|
||||
def test_create_bot(self) -> None:
|
||||
action = lambda: self.create_bot("test")
|
||||
events = self.verify_action(action, num_events=2)
|
||||
|
|
|
@ -178,6 +178,7 @@ class HomeTest(ZulipTestCase):
|
|||
"realm_notifications_stream_id",
|
||||
"realm_password_auth_enabled",
|
||||
"realm_plan_type",
|
||||
"realm_playgrounds",
|
||||
"realm_presence_disabled",
|
||||
"realm_private_message_policy",
|
||||
"realm_push_notifications_enabled",
|
||||
|
@ -262,7 +263,7 @@ class HomeTest(ZulipTestCase):
|
|||
set(result["Cache-Control"].split(", ")), {"must-revalidate", "no-store", "no-cache"}
|
||||
)
|
||||
|
||||
self.assert_length(queries, 40)
|
||||
self.assert_length(queries, 41)
|
||||
self.assert_length(cache_mock.call_args_list, 5)
|
||||
|
||||
html = result.content.decode("utf-8")
|
||||
|
@ -342,7 +343,7 @@ class HomeTest(ZulipTestCase):
|
|||
result = self._get_home_page()
|
||||
self.check_rendered_logged_in_app(result)
|
||||
self.assert_length(cache_mock.call_args_list, 6)
|
||||
self.assert_length(queries, 37)
|
||||
self.assert_length(queries, 38)
|
||||
|
||||
def test_num_queries_with_streams(self) -> None:
|
||||
main_user = self.example_user("hamlet")
|
||||
|
@ -373,7 +374,7 @@ class HomeTest(ZulipTestCase):
|
|||
with queries_captured() as queries2:
|
||||
result = self._get_home_page()
|
||||
|
||||
self.assert_length(queries2, 35)
|
||||
self.assert_length(queries2, 36)
|
||||
|
||||
# Do a sanity check that our new streams were in the payload.
|
||||
html = result.content.decode("utf-8")
|
||||
|
|
|
@ -60,5 +60,5 @@ def delete_realm_playground(
|
|||
request: HttpRequest, user_profile: UserProfile, playground_id: int
|
||||
) -> HttpResponse:
|
||||
realm_playground = access_playground_by_id(user_profile.realm, playground_id)
|
||||
do_remove_realm_playground(realm_playground)
|
||||
do_remove_realm_playground(user_profile.realm, realm_playground)
|
||||
return json_success()
|
||||
|
|
Loading…
Reference in New Issue