data export: Add UI to trigger data export.

This commit serves as the frontend piece for the "public export"
webapp feature.

Fixes: #11930
This commit is contained in:
Wyatt Hoodes 2019-04-09 09:49:07 -10:00 committed by Tim Abbott
parent 8ac8247dbe
commit f623540409
14 changed files with 200 additions and 2 deletions

View File

@ -148,6 +148,7 @@
"settings_bots": false, "settings_bots": false,
"settings_display": false, "settings_display": false,
"settings_emoji": false, "settings_emoji": false,
"settings_exports": false,
"settings_linkifiers": false, "settings_linkifiers": false,
"settings_invites": false, "settings_invites": false,
"settings_muting": false, "settings_muting": false,

View File

@ -56,6 +56,12 @@ set_global('settings_bots', {
update_bot_permissions_ui: noop, update_bot_permissions_ui: noop,
}); });
set_global('settings_exports', {
populate_exports_table: function (exports) {
return exports;
},
});
// page_params is highly coupled to dispatching now // page_params is highly coupled to dispatching now
set_global('page_params', {test_suite: false}); set_global('page_params', {test_suite: false});
var page_params = global.page_params; var page_params = global.page_params;
@ -712,6 +718,14 @@ var event_fixtures = {
user_id: test_user.user_id, user_id: test_user.user_id,
status_text: 'out to lunch', 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) { function assert_same(actual, expected) {
@ -1512,3 +1526,18 @@ with_overrides(function (override) {
assert.equal(status_text, 'out to lunch'); 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');
});
});

View File

@ -838,6 +838,10 @@ run_test('set_up', () => {
const allow_topic_edit_label_parent = $.create('allow-topic-edit-label-parent'); 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); $('#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 // TEST set_up() here, but this mostly just allows us to
// get access to the click handlers. // get access to the click handlers.
settings_org.maybe_disable_widgets = noop; settings_org.maybe_disable_widgets = noop;

View File

@ -174,6 +174,7 @@ import "../settings_bots.js";
import "../settings_muting.js"; import "../settings_muting.js";
import "../settings_sections.js"; import "../settings_sections.js";
import "../settings_emoji.js"; import "../settings_emoji.js";
import "../settings_exports.js";
import "../settings_org.js"; import "../settings_org.js";
import "../settings_users.js"; import "../settings_users.js";
import "../settings_streams.js"; import "../settings_streams.js";

View File

@ -111,6 +111,7 @@ declare var settings_account: any;
declare var settings_bots: any; declare var settings_bots: any;
declare var settings_display: any; declare var settings_display: any;
declare var settings_emoji: any; declare var settings_emoji: any;
declare var settings_exports: any;
declare var settings_invites: any; declare var settings_invites: any;
declare var settings: any; declare var settings: any;
declare var settings_linkifiers: any; declare var settings_linkifiers: any;

View File

@ -510,6 +510,9 @@ exports.dispatch_normal_event = function dispatch_normal_event(event) {
activity.redraw_user(event.user_id); activity.redraw_user(event.user_id);
} }
break; break;
case 'realm_export':
settings_exports.populate_exports_table(event.exports);
break;
} }
}; };

View File

@ -24,6 +24,7 @@ var header_map = {
"invites-list-admin": i18n.t("Invitations"), "invites-list-admin": i18n.t("Invitations"),
"user-groups-admin": i18n.t("User groups"), "user-groups-admin": i18n.t("User groups"),
"profile-field-settings": i18n.t("Profile field settings"), "profile-field-settings": i18n.t("Profile field settings"),
"data-exports-admin": i18n.t("Data exports"),
}; };
$("body").ready(function () { $("body").ready(function () {

View File

@ -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;

View File

@ -46,6 +46,7 @@ exports.initialize = function () {
load_func_dict.set('invites-list-admin', settings_invites.set_up); 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('user-groups-admin', settings_user_groups.set_up);
load_func_dict.set('profile-field-settings', settings_profile_fields.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) { exports.load_settings_section = function (section) {
@ -72,6 +73,7 @@ exports.load_settings_section = function (section) {
exports.reset_sections = function () { exports.reset_sections = function () {
is_loaded.clear(); is_loaded.clear();
settings_emoji.reset(); settings_emoji.reset();
settings_exports.reset();
settings_linkifiers.reset(); settings_linkifiers.reset();
settings_invites.reset(); settings_invites.reset();
settings_org.reset(); settings_org.reset();

View File

@ -723,13 +723,15 @@ input[type=checkbox].inline-block {
.add-new-emoji-box, .add-new-emoji-box,
.add-new-user-group-box, .add-new-user-group-box,
.add-new-alert-word-box { .add-new-alert-word-box,
.add-new-export-box {
margin-bottom: 20px; margin-bottom: 20px;
} }
.add-new-emoji-box .new-emoji-form, .add-new-emoji-box .new-emoji-form,
.add-new-user-group-box .new-user-group-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; margin: 10px 0px;
} }
@ -1949,3 +1951,7 @@ thead .actions {
margin-left: 2px; margin-left: 2px;
display: inline-block; display: inline-block;
} }
.admin_exports_table {
margin: 20px;
}

View File

@ -0,0 +1,22 @@
{{#with realm_export}}
<tr class="export_row" id="export_{{id}}">
<td>
<span class="acting_user">{{acting_user}}</span>
</td>
<td>
<span class="export_time">{{event_time}}</span>
</td>
<td>
{{#if path}}
<span class="export_url"><a href="{{path}}">{{t 'Download' }}</a></span>
{{else}}
<span class="export_url">{{t 'The export URL is not yet available... Check back soon.' }}</span>
{{/if}}
</td>
<td>
<button class="button rounded small delete btn-danger" data-export-id="{{id}}">
<i class="fa fa-trash-o" aria-hidden="true"></i>
</button>
</td>
</tr>
{{/with}}

View File

@ -28,3 +28,5 @@
{{> user_groups_admin }} {{> user_groups_admin }}
{{> settings/profile_field_settings_admin }} {{> settings/profile_field_settings_admin }}
{{> settings/data_exports_admin }}

View File

@ -0,0 +1,36 @@
<div id="data-exports" class="settings-section" data-name="data-exports-admin">
<p class="alert-word-settings-note">{{#tr this}}Depending on the size of your organization, the time to complete an export can range from several minutes to an hour.{{/tr}}</p>
<h3>{{t "Data exports" }}
<a href="/help/export-your-organization" target="_blank">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</h3>
{{#if is_admin}}
<div class="alert" id="export_status" role="alert">
<span class="export_status_text"></span>
</div>
<form class="form-horizontal">
<div class="add-new-export-box grey-box">
<div class="wrapper">
<button type="submit" class="button rounded sea-green" id="export-data">
{{t 'Start public export' }}
</button>
</div>
<p class="alert-word-settings-note">{{#tr this}}Zulip also supports exporting private streams and messages under some circumstances. <a href="/help/export-your-organization" target="_blank">Read more</a>{{/tr}}</p>
</div>
</form>
{{/if}}
<p class="alert-word-settings-note">{{#tr this}}Any member with administrative access can conduct an export. Please note that individual organizations are limited to five exports per week.{{/tr}}</p>
<div class="admin-table-wrapper">
<table class="table table-condensed table-striped wrapped-table admin_exports_table">
<thead>
<th>{{t "Requesting user" }}</th>
<th>{{t "Time" }}</th>
<th>{{t "File" }}</th>
<th>{{t "Actions" }}</th>
</thead>
<tbody id="admin_exports_table" class="required-text" data-empty="{{t 'No exports.' }}"></tbody>
</table>
</div>
</div>

View File

@ -145,6 +145,10 @@
<i class="icon fa fa-user" aria-hidden="true"></i> <i class="icon fa fa-user" aria-hidden="true"></i>
<div class="text">{{ _('Invitations') }}</div> <div class="text">{{ _('Invitations') }}</div>
</li> </li>
<li tabindex="0" data-section="data-exports-admin">
<i class="icon fa fa-database" aria-hidden="true"></i>
<div class="text">{{ _('Data exports') }}</div>
</li>
{% endif %} {% endif %}
{% if not is_admin %} {% if not is_admin %}
<div class="collapse-settings-btn"> <div class="collapse-settings-btn">