diff --git a/api_docs/changelog.md b/api_docs/changelog.md index a1c5240933..ed937fc199 100644 --- a/api_docs/changelog.md +++ b/api_docs/changelog.md @@ -20,6 +20,12 @@ format used by the Zulip server that they are interacting with. ## Changes in Zulip 8.0 +**Feature level 206** + +* `POST /calls/zoom/create`: Added `is_video_call` parameter + controlling whether to request a Zoom meeting that defaults to + having video enabled. + **Feature level 205** * [`POST /register`](/api/register-queue): `streams` field in the response diff --git a/version.py b/version.py index c273b93491..eebc233bc4 100644 --- a/version.py +++ b/version.py @@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.9.3" # Changes should be accompanied by documentation explaining what the # new level means in api_docs/changelog.md, as well as "**Changes**" # entries in the endpoint's documentation in `zulip.yaml`. -API_FEATURE_LEVEL = 205 +API_FEATURE_LEVEL = 206 # 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 diff --git a/web/src/compose.js b/web/src/compose.js index ddddd0363b..70f5a85d52 100644 --- a/web/src/compose.js +++ b/web/src/compose.js @@ -89,8 +89,10 @@ export function update_video_chat_button_display() { export function compute_show_audio_chat_button() { const available_providers = page_params.realm_available_video_chat_providers; if ( - available_providers.jitsi_meet && - page_params.realm_video_chat_provider === available_providers.jitsi_meet.id + (available_providers.jitsi_meet && + page_params.realm_video_chat_provider === available_providers.jitsi_meet.id) || + (available_providers.zoom && + page_params.realm_video_chat_provider === available_providers.zoom.id) ) { return true; } @@ -856,21 +858,26 @@ function generate_and_insert_audio_or_video_call_link($target_element, is_audio_ available_providers.zoom && page_params.realm_video_chat_provider === available_providers.zoom.id ) { - if (is_audio_call) { - // TODO: Add support for generating audio-only Zoom calls here. - return; - } abort_video_callbacks(edit_message_id); const key = edit_message_id || ""; + const request = { + is_video_call: !is_audio_call, + }; + const make_zoom_call = () => { video_call_xhrs.set( key, channel.post({ url: "/json/calls/zoom/create", + data: request, success(res) { video_call_xhrs.delete(key); - insert_video_call_url(res.url, $target_textarea); + if (is_audio_call) { + insert_audio_call_url(res.url, $target_textarea); + } else { + insert_video_call_url(res.url, $target_textarea); + } }, error(xhr, status) { video_call_xhrs.delete(key); diff --git a/web/tests/compose_video.test.js b/web/tests/compose_video.test.js index 76af1e9074..27fc177bda 100644 --- a/web/tests/compose_video.test.js +++ b/web/tests/compose_video.test.js @@ -132,7 +132,7 @@ test("videos", ({override}) => { assert.match(syntax_to_insert, video_link_regex); })(); - (function test_zoom_video_link_compose_clicked() { + (function test_zoom_video_and_audio_links_compose_clicked() { let syntax_to_insert; let called = false; @@ -152,9 +152,6 @@ test("videos", ({override}) => { called = true; }); - const handler = $("body").get_on_handler("click", ".video_link"); - $("#compose-textarea").val(""); - page_params.realm_video_chat_provider = realm_available_video_chat_providers.zoom.id; page_params.has_zoom_token = false; @@ -172,10 +169,19 @@ test("videos", ({override}) => { return {abort() {}}; }; - handler(ev); + $("#compose-textarea").val(""); + const video_handler = $("body").get_on_handler("click", ".video_link"); + video_handler(ev); const video_link_regex = /\[translated: Join video call\.]\(example\.zoom\.com\)/; assert.ok(called); assert.match(syntax_to_insert, video_link_regex); + + $("#compose-textarea").val(""); + const audio_handler = $("body").get_on_handler("click", ".audio_link"); + audio_handler(ev); + const audio_link_regex = /\[translated: Join audio call\.]\(example\.zoom\.com\)/; + assert.ok(called); + assert.match(syntax_to_insert, audio_link_regex); })(); (function test_bbb_video_link_compose_clicked() { diff --git a/zerver/tests/test_create_video_call.py b/zerver/tests/test_create_video_call.py index 4eae085cce..ee3e35c179 100644 --- a/zerver/tests/test_create_video_call.py +++ b/zerver/tests/test_create_video_call.py @@ -36,7 +36,7 @@ class TestVideoCall(ZulipTestCase): self.assertEqual(response.status_code, 302) @responses.activate - def test_create_video_request_success(self) -> None: + def test_create_zoom_video_and_audio_links(self) -> None: responses.add( responses.POST, "https://zoom.us/oauth/token", @@ -49,6 +49,7 @@ class TestVideoCall(ZulipTestCase): ) self.assertEqual(response.status_code, 200) + # Test creating a video link responses.replace( responses.POST, "https://zoom.us/oauth/token", @@ -61,7 +62,7 @@ class TestVideoCall(ZulipTestCase): json={"join_url": "example.com"}, ) - response = self.client_post("/json/calls/zoom/create") + response = self.client_post("/json/calls/zoom/create", {"is_video_call": "true"}) self.assertEqual( responses.calls[-1].request.url, "https://api.zoom.us/v2/users/me/meetings", @@ -73,6 +74,32 @@ class TestVideoCall(ZulipTestCase): json = self.assert_json_success(response) self.assertEqual(json["url"], "example.com") + # Test creating an audio link + responses.replace( + responses.POST, + "https://zoom.us/oauth/token", + json={"access_token": "newtoken", "expires_in": 60}, + ) + + responses.add( + responses.POST, + "https://api.zoom.us/v2/users/me/meetings", + json={"join_url": "example.com"}, + ) + + response = self.client_post("/json/calls/zoom/create", {"is_video_call": "false"}) + self.assertEqual( + responses.calls[-1].request.url, + "https://api.zoom.us/v2/users/me/meetings", + ) + self.assertEqual( + responses.calls[-1].request.headers["Authorization"], + "Bearer newtoken", + ) + json = self.assert_json_success(response) + self.assertEqual(json["url"], "example.com") + + # Test for authentication error self.logout() self.login_user(self.user) diff --git a/zerver/views/video_calls.py b/zerver/views/video_calls.py index c831edf21e..4d26681794 100644 --- a/zerver/views/video_calls.py +++ b/zerver/views/video_calls.py @@ -31,7 +31,7 @@ from zerver.lib.request import REQ, has_request_variables from zerver.lib.response import json_success from zerver.lib.subdomains import get_subdomain from zerver.lib.url_encoding import append_url_query_string -from zerver.lib.validator import check_dict, check_string +from zerver.lib.validator import check_bool, check_dict, check_string from zerver.models import UserProfile, get_realm @@ -142,13 +142,30 @@ def complete_zoom_user_in_realm( return render(request, "zerver/close_window.html") -def make_zoom_video_call(request: HttpRequest, user: UserProfile) -> HttpResponse: +@has_request_variables +def make_zoom_video_call( + request: HttpRequest, + user: UserProfile, + is_video_call: bool = REQ(json_validator=check_bool, default=True), +) -> HttpResponse: oauth = get_zoom_session(user) if not oauth.authorized: raise InvalidZoomTokenError + # The meeting host has the ability to configure both their own and + # participants' default video on/off state for the meeting. That's + # why when creating a meeting, configure the video on/off default + # according to the desired call type. Each Zoom user can still have + # their own personal setting to not start video by default. + payload = { + "settings": { + "host_video": is_video_call, + "participant_video": is_video_call, + } + } + try: - res = oauth.post("https://api.zoom.us/v2/users/me/meetings", json={}) + res = oauth.post("https://api.zoom.us/v2/users/me/meetings", json=payload) except OAuth2Error: do_set_zoom_token(user, None) raise InvalidZoomTokenError