diff --git a/frontend_tests/node_tests/templates.js b/frontend_tests/node_tests/templates.js index c36a56882c..139da37b2c 100644 --- a/frontend_tests/node_tests/templates.js +++ b/frontend_tests/node_tests/templates.js @@ -1410,6 +1410,24 @@ run_test('typing_notifications', () => { }); +run_test('upload_space_stats', () => { + var args = { + show_upgrade_message: true, + percent_used: 50, + upload_quota: "1 GB", + }; + var html = render('upload-space-stats', args); + assert.equal($(html).text().trim(), "translated: Organization using 50% of 1 GB.\n translated: Upgrade for more space."); + + args = { + show_upgrade_message: false, + percent_used: 10, + upload_quota: "5 GB", + }; + html = render('upload-space-stats', args); + assert.equal($(html).text().trim(), "translated: Organization using 10% of 5 GB."); +}); + run_test('user_group_info_popover', () => { var html = render('user_group_info_popover'); diff --git a/static/js/attachments_ui.js b/static/js/attachments_ui.js index 2a66b7cb77..bdc60ec16f 100644 --- a/static/js/attachments_ui.js +++ b/static/js/attachments_ui.js @@ -3,6 +3,7 @@ var attachments_ui = (function () { var exports = {}; var attachments; +var upload_space_used; exports.bytes_to_size = function (bytes, kb_with_1024_bytes) { if (kb_with_1024_bytes === undefined) { @@ -21,6 +22,26 @@ exports.bytes_to_size = function (bytes, kb_with_1024_bytes) { return size + ' ' + sizes[i]; }; +exports.percentage_used_space = function (uploads_size) { + if (page_params.realm_upload_quota === null) { + return null; + } + return (100 * uploads_size / page_params.realm_upload_quota).toFixed(1); +}; + +function set_upload_space_stats() { + if (page_params.realm_upload_quota === null) { + return; + } + var args = { + show_upgrade_message: page_params.realm_plan_type === 2, + percent_used: exports.percentage_used_space(upload_space_used), + upload_quota: attachments_ui.bytes_to_size(page_params.realm_upload_quota, true), + }; + var rendered_upload_stats_html = templates.render("upload-space-stats", args); + $("#attachment-stats-holder").html(rendered_upload_stats_html); +} + function delete_attachments(attachment) { var status = $('#delete-upload-status'); channel.del({ @@ -36,6 +57,8 @@ function delete_attachments(attachment) { } function render_attachments_ui() { + set_upload_space_stats(); + var uploaded_files_table = $("#uploaded_files_table").expectOne(); var $search_input = $("#upload_file_search"); @@ -99,7 +122,7 @@ exports.update_attachments = function (event) { format_attachment_data([event.attachment]); attachments.push(event.attachment); } - + upload_space_used = event.upload_space_used; // TODO: This is inefficient and we should be able to do some sort // of incremental list_render update instead. render_attachments_ui(); @@ -125,6 +148,7 @@ exports.set_up_attachments = function () { loading.destroy_indicator($('#attachments_loading_indicator')); format_attachment_data(data.attachments); attachments = data.attachments; + upload_space_used = data.upload_space_used; render_attachments_ui(); }, error: function (xhr) { diff --git a/static/styles/settings.scss b/static/styles/settings.scss index 00d6351aee..fe0cac646a 100644 --- a/static/styles/settings.scss +++ b/static/styles/settings.scss @@ -1911,3 +1911,9 @@ thead .actions { #get_api_key_button { display: block; } + +#attachment-stats-holder { + position: relative; + margin-top: 13px; + display: inline-block; +} diff --git a/static/templates/settings/attachments-settings.handlebars b/static/templates/settings/attachments-settings.handlebars index bb24bbd82d..878c99899d 100644 --- a/static/templates/settings/attachments-settings.handlebars +++ b/static/templates/settings/attachments-settings.handlebars @@ -1,4 +1,5 @@
+
diff --git a/static/templates/settings/upload-space-stats.handlebars b/static/templates/settings/upload-space-stats.handlebars new file mode 100644 index 0000000000..351b74afe3 --- /dev/null +++ b/static/templates/settings/upload-space-stats.handlebars @@ -0,0 +1,4 @@ + + {{#tr this}}Organization using __percent_used__% of __upload_quota__.{{/tr}} + {{#if show_upgrade_message}}{{#tr this}}Upgrade for more space.{{/tr}}{{/if}} + diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py index 6bb346729b..108c305f90 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -5221,6 +5221,7 @@ def notify_attachment_update(user_profile: UserProfile, op: str, 'type': 'attachment', 'op': op, 'attachment': attachment_dict, + "upload_space_used": user_profile.realm.currently_used_upload_space_bytes(), } send_event(user_profile.realm, event, [user_profile.id]) diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index 3f848f6b67..c8a7d0135c 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -2525,6 +2525,7 @@ class EventsRegisterTest(ZulipTestCase): ('name', check_float), ]))), ])), + ('upload_space_used', equals(6)), ]) self.login(self.example_email("hamlet")) @@ -2567,6 +2568,7 @@ class EventsRegisterTest(ZulipTestCase): ('name', check_float), ]))), ])), + ('upload_space_used', equals(6)), ]) self.subscribe(self.example_user("hamlet"), "Denmark") @@ -2584,6 +2586,7 @@ class EventsRegisterTest(ZulipTestCase): ('attachment', check_dict_only([ ('id', check_int), ])), + ('upload_space_used', equals(0)), ]) events = self.do_test( diff --git a/zerver/tests/test_upload.py b/zerver/tests/test_upload.py index 9bea68526f..1208f018b2 100644 --- a/zerver/tests/test_upload.py +++ b/zerver/tests/test_upload.py @@ -1762,15 +1762,15 @@ class UploadSpaceTests(UploadSerializeMixin, ZulipTestCase): data = b'zulip!' upload_message_file(u'dummy.txt', len(data), u'text/plain', data, self.user_profile) - self.assertEqual(None, cache_get(get_realm_used_upload_space_cache_key(self.realm))) - self.assertEqual(len(data), self.realm.currently_used_upload_space_bytes()) + # notify_attachment_update function calls currently_used_upload_space_bytes which + # updates the cache. self.assertEqual(len(data), cache_get(get_realm_used_upload_space_cache_key(self.realm))[0]) + self.assertEqual(len(data), self.realm.currently_used_upload_space_bytes()) data2 = b'more-data!' upload_message_file(u'dummy2.txt', len(data2), u'text/plain', data2, self.user_profile) - self.assertEqual(None, cache_get(get_realm_used_upload_space_cache_key(self.realm))) - self.assertEqual(len(data) + len(data2), self.realm.currently_used_upload_space_bytes()) self.assertEqual(len(data) + len(data2), cache_get(get_realm_used_upload_space_cache_key(self.realm))[0]) + self.assertEqual(len(data) + len(data2), self.realm.currently_used_upload_space_bytes()) attachment = Attachment.objects.get(file_name="dummy.txt") attachment.file_name = "dummy1.txt" @@ -1781,17 +1781,6 @@ class UploadSpaceTests(UploadSerializeMixin, ZulipTestCase): attachment.delete() self.assertEqual(None, cache_get(get_realm_used_upload_space_cache_key(self.realm))) self.assertEqual(len(data2), self.realm.currently_used_upload_space_bytes()) - self.assertEqual(len(data2), cache_get(get_realm_used_upload_space_cache_key(self.realm))[0]) - - def test_upload_space_used_api(self) -> None: - self.login(self.user_profile.email) - response = self.client_get("/json/realm/upload_space_used") - self.assert_in_success_response(["0"], response) - - data = b'zulip!' - upload_message_file(u'dummy.txt', len(data), u'text/plain', data, self.user_profile) - response = self.client_get("/json/realm/upload_space_used") - self.assert_in_success_response([str(len(data))], response) class ExifRotateTests(TestCase): def test_image_do_not_rotate(self) -> None: diff --git a/zerver/views/attachments.py b/zerver/views/attachments.py index fc6fcdebb5..250c004b87 100644 --- a/zerver/views/attachments.py +++ b/zerver/views/attachments.py @@ -8,8 +8,10 @@ from zerver.lib.attachments import user_attachments, remove_attachment, \ def list_by_user(request: HttpRequest, user_profile: UserProfile) -> HttpResponse: - return json_success({"attachments": user_attachments(user_profile)}) - + return json_success({ + "attachments": user_attachments(user_profile), + "upload_space_used": user_profile.realm.currently_used_upload_space_bytes(), + }) def remove(request: HttpRequest, user_profile: UserProfile, attachment_id: str) -> HttpResponse: attachment = access_attachment_by_id(user_profile, int(attachment_id), @@ -17,6 +19,3 @@ def remove(request: HttpRequest, user_profile: UserProfile, attachment_id: str) remove_attachment(user_profile, attachment) notify_attachment_update(user_profile, "remove", {"id": int(attachment_id)}) return json_success() - -def upload_space_used(request: HttpRequest, user_profile: UserProfile) -> HttpResponse: - return json_success({"upload_space_used": user_profile.realm.currently_used_upload_space_bytes()}) diff --git a/zproject/urls.py b/zproject/urls.py index c367c27280..377a698485 100644 --- a/zproject/urls.py +++ b/zproject/urls.py @@ -127,9 +127,6 @@ v1_api_and_json_patterns = [ url(r'^realm/presence$', rest_dispatch, {'GET': 'zerver.views.presence.get_statuses_for_realm'}), - url(r'^realm/upload_space_used$', rest_dispatch, - {'GET': 'zerver.views.attachments.upload_space_used'}), - # users -> zerver.views.users # # Since some of these endpoints do something different if used on