From f623540409c939cbaf5fc82fac022fad40b8e283 Mon Sep 17 00:00:00 2001 From: Wyatt Hoodes Date: Tue, 9 Apr 2019 09:49:07 -1000 Subject: [PATCH] data export: Add UI to trigger data export. This commit serves as the frontend piece for the "public export" webapp feature. Fixes: #11930 --- .eslintrc.json | 1 + frontend_tests/node_tests/dispatch.js | 29 +++++++ frontend_tests/node_tests/settings_org.js | 4 + static/js/bundles/app.js | 1 + static/js/js_typings/zulip/index.d.ts | 1 + static/js/server_events_dispatch.js | 3 + static/js/settings.js | 1 + static/js/settings_exports.js | 86 +++++++++++++++++++ static/js/settings_sections.js | 2 + static/styles/settings.scss | 10 ++- static/templates/admin_export_list.hbs | 22 +++++ static/templates/admin_tab.hbs | 2 + .../templates/settings/data_exports_admin.hbs | 36 ++++++++ templates/zerver/app/settings_overlay.html | 4 + 14 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 static/js/settings_exports.js create mode 100644 static/templates/admin_export_list.hbs create mode 100644 static/templates/settings/data_exports_admin.hbs diff --git a/.eslintrc.json b/.eslintrc.json index 82621c198c..4ed5a6f727 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -148,6 +148,7 @@ "settings_bots": false, "settings_display": false, "settings_emoji": false, + "settings_exports": false, "settings_linkifiers": false, "settings_invites": false, "settings_muting": false, diff --git a/frontend_tests/node_tests/dispatch.js b/frontend_tests/node_tests/dispatch.js index 8d16455565..0e3c14d237 100644 --- a/frontend_tests/node_tests/dispatch.js +++ b/frontend_tests/node_tests/dispatch.js @@ -56,6 +56,12 @@ set_global('settings_bots', { update_bot_permissions_ui: noop, }); +set_global('settings_exports', { + populate_exports_table: function (exports) { + return exports; + }, +}); + // page_params is highly coupled to dispatching now set_global('page_params', {test_suite: false}); var page_params = global.page_params; @@ -712,6 +718,14 @@ var event_fixtures = { user_id: test_user.user_id, status_text: 'out to lunch', }, + realm_export: { + type: 'realm_export', + exports: { + acting_user_id: 55, + event_time: 'noon', + path: 'some_path', + }, + }, }; function assert_same(actual, expected) { @@ -1512,3 +1526,18 @@ with_overrides(function (override) { assert.equal(status_text, 'out to lunch'); }); }); + +with_overrides(function (override) { + var event = event_fixtures.realm_export; + override('settings_exports.populate_exports_table', noop); + dispatch(event); + global.with_stub(function (stub) { + override('settings_exports.populate_exports_table', stub.f); + dispatch(event); + + var args = stub.get_args('exports'); + assert.equal(args.exports.acting_user_id, 55); + assert.equal(args.exports.event_time, 'noon'); + assert.equal(args.exports.path, 'some_path'); + }); +}); diff --git a/frontend_tests/node_tests/settings_org.js b/frontend_tests/node_tests/settings_org.js index ed1c55faba..e31d936bb1 100644 --- a/frontend_tests/node_tests/settings_org.js +++ b/frontend_tests/node_tests/settings_org.js @@ -838,6 +838,10 @@ run_test('set_up', () => { const allow_topic_edit_label_parent = $.create('allow-topic-edit-label-parent'); $('#id_realm_allow_community_topic_editing_label').set_parent(allow_topic_edit_label_parent); + channel.get = function (opts) { + assert.equal(opts.url, '/json/export/realm'); + }; + // TEST set_up() here, but this mostly just allows us to // get access to the click handlers. settings_org.maybe_disable_widgets = noop; diff --git a/static/js/bundles/app.js b/static/js/bundles/app.js index 4cc846b10d..c01939824f 100644 --- a/static/js/bundles/app.js +++ b/static/js/bundles/app.js @@ -174,6 +174,7 @@ import "../settings_bots.js"; import "../settings_muting.js"; import "../settings_sections.js"; import "../settings_emoji.js"; +import "../settings_exports.js"; import "../settings_org.js"; import "../settings_users.js"; import "../settings_streams.js"; diff --git a/static/js/js_typings/zulip/index.d.ts b/static/js/js_typings/zulip/index.d.ts index 24c8b014da..41cca7eb41 100644 --- a/static/js/js_typings/zulip/index.d.ts +++ b/static/js/js_typings/zulip/index.d.ts @@ -111,6 +111,7 @@ declare var settings_account: any; declare var settings_bots: any; declare var settings_display: any; declare var settings_emoji: any; +declare var settings_exports: any; declare var settings_invites: any; declare var settings: any; declare var settings_linkifiers: any; diff --git a/static/js/server_events_dispatch.js b/static/js/server_events_dispatch.js index 4b780fbf53..45eecf4760 100644 --- a/static/js/server_events_dispatch.js +++ b/static/js/server_events_dispatch.js @@ -510,6 +510,9 @@ exports.dispatch_normal_event = function dispatch_normal_event(event) { activity.redraw_user(event.user_id); } break; + case 'realm_export': + settings_exports.populate_exports_table(event.exports); + break; } }; diff --git a/static/js/settings.js b/static/js/settings.js index 3081c80d84..044719289e 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -24,6 +24,7 @@ var header_map = { "invites-list-admin": i18n.t("Invitations"), "user-groups-admin": i18n.t("User groups"), "profile-field-settings": i18n.t("Profile field settings"), + "data-exports-admin": i18n.t("Data exports"), }; $("body").ready(function () { diff --git a/static/js/settings_exports.js b/static/js/settings_exports.js new file mode 100644 index 0000000000..e57ad6c757 --- /dev/null +++ b/static/js/settings_exports.js @@ -0,0 +1,86 @@ +var render_admin_export_list = require('../templates/admin_export_list.hbs'); + +var settings_exports = (function () { + +var exports = {}; + +var meta = { + loaded: false, +}; + +exports.reset = function () { + meta.loaded = false; +}; + +exports.populate_exports_table = function (exports) { + if (!meta.loaded) { + return; + } + + var exports_table = $('#admin_exports_table').expectOne(); + exports_table.find('tr.export_row').remove(); + _.each(exports, function (data) { + if (data.export_data.deleted_timestamp === undefined) { + exports_table.append(render_admin_export_list({ + realm_export: { + id: data.id, + acting_user: people.my_full_name(data.acting_user_id), + // Convert seconds -> milliseconds + event_time: timerender.last_seen_status_from_date( + new XDate(data.export_time * 1000) + ), + path: data.export_data.export_path, + }, + })); + } + }); +}; + +exports.set_up = function () { + meta.loaded = true; + + $("#export-data").on('click', function (e) { + e.preventDefault(); + e.stopPropagation(); + var export_status = $('#export_status'); + + channel.post({ + url: '/json/export/realm', + success: function () { + ui_report.success(i18n.t("Export started. Check back in a few minutes."), export_status); + }, + error: function (xhr) { + ui_report.error(i18n.t("Export failed"), xhr, export_status); + }, + }); + }); + + // Do an initial population of the table + channel.get({ + url: '/json/export/realm', + success: function (data) { + exports.populate_exports_table(data.exports); + }, + }); + + $('.admin_exports_table').on('click', '.delete', function (e) { + e.preventDefault(); + e.stopPropagation(); + var btn = $(this); + + channel.del({ + url: '/json/export/realm/' + encodeURIComponent(btn.attr('data-export-id')), + error: function (xhr) { + ui_report.generic_row_button_error(xhr, btn); + }, + // No success function, since UI updates are done via server_events + }); + }); +}; + +return exports; +}()); +if (typeof modules !== 'undefined') { + module.exports = settings_exports; +} +window.settings_exports = settings_exports; diff --git a/static/js/settings_sections.js b/static/js/settings_sections.js index 3c6a749867..f06bb6ed54 100644 --- a/static/js/settings_sections.js +++ b/static/js/settings_sections.js @@ -46,6 +46,7 @@ exports.initialize = function () { load_func_dict.set('invites-list-admin', settings_invites.set_up); load_func_dict.set('user-groups-admin', settings_user_groups.set_up); load_func_dict.set('profile-field-settings', settings_profile_fields.set_up); + load_func_dict.set('data-exports-admin', settings_exports.set_up); }; exports.load_settings_section = function (section) { @@ -72,6 +73,7 @@ exports.load_settings_section = function (section) { exports.reset_sections = function () { is_loaded.clear(); settings_emoji.reset(); + settings_exports.reset(); settings_linkifiers.reset(); settings_invites.reset(); settings_org.reset(); diff --git a/static/styles/settings.scss b/static/styles/settings.scss index 7766b59839..5cb5ea2b23 100644 --- a/static/styles/settings.scss +++ b/static/styles/settings.scss @@ -723,13 +723,15 @@ input[type=checkbox].inline-block { .add-new-emoji-box, .add-new-user-group-box, -.add-new-alert-word-box { +.add-new-alert-word-box, +.add-new-export-box { margin-bottom: 20px; } .add-new-emoji-box .new-emoji-form, .add-new-user-group-box .new-user-group-form, -.add-new-alert-word-box .new-alert-word-form { +.add-new-alert-word-box .new-alert-word-form, +.add-new-export-box { margin: 10px 0px; } @@ -1949,3 +1951,7 @@ thead .actions { margin-left: 2px; display: inline-block; } + +.admin_exports_table { + margin: 20px; +} diff --git a/static/templates/admin_export_list.hbs b/static/templates/admin_export_list.hbs new file mode 100644 index 0000000000..86c214d30f --- /dev/null +++ b/static/templates/admin_export_list.hbs @@ -0,0 +1,22 @@ +{{#with realm_export}} + + + {{acting_user}} + + + {{event_time}} + + + {{#if path}} + {{t 'Download' }} + {{else}} + {{t 'The export URL is not yet available... Check back soon.' }} + {{/if}} + + + + + +{{/with}} diff --git a/static/templates/admin_tab.hbs b/static/templates/admin_tab.hbs index 0c315f92be..c14d7fc1e9 100644 --- a/static/templates/admin_tab.hbs +++ b/static/templates/admin_tab.hbs @@ -28,3 +28,5 @@ {{> user_groups_admin }} {{> settings/profile_field_settings_admin }} + +{{> settings/data_exports_admin }} diff --git a/static/templates/settings/data_exports_admin.hbs b/static/templates/settings/data_exports_admin.hbs new file mode 100644 index 0000000000..300bf6010a --- /dev/null +++ b/static/templates/settings/data_exports_admin.hbs @@ -0,0 +1,36 @@ +
+

{{#tr this}}Depending on the size of your organization, the time to complete an export can range from several minutes to an hour.{{/tr}}

+

{{t "Data exports" }} + + + +

+ + {{#if is_admin}} + +
+
+
+ +
+

{{#tr this}}Zulip also supports exporting private streams and messages under some circumstances. Read more{{/tr}}

+
+
+ {{/if}} +

{{#tr this}}Any member with administrative access can conduct an export. Please note that individual organizations are limited to five exports per week.{{/tr}}

+
+ + + + + + + + +
{{t "Requesting user" }}{{t "Time" }}{{t "File" }}{{t "Actions" }}
+
+
diff --git a/templates/zerver/app/settings_overlay.html b/templates/zerver/app/settings_overlay.html index 04e52cc324..7987b44db2 100644 --- a/templates/zerver/app/settings_overlay.html +++ b/templates/zerver/app/settings_overlay.html @@ -145,6 +145,10 @@
{{ _('Invitations') }}
+
  • + +
    {{ _('Data exports') }}
    +
  • {% endif %} {% if not is_admin %}