people: Enable `user_avatar_url_field_optional` in webapp.

In 5200598a31, we introduced a new
client capability that can be used to avoid unreasonable network
bandwidth consumed sending avatar URLs of long term idle users in
organizations with 10,000s members.

This commit enables this feature and adds support for it to the web
client.
This commit is contained in:
Hashir Sarwar 2020-06-24 17:25:17 +05:00 committed by Tim Abbott
parent 138a0cb073
commit 9b8521faee
5 changed files with 33 additions and 7 deletions

View File

@ -646,17 +646,17 @@ run_test('initialize', () => {
// corresponding parts in bold. // corresponding parts in bold.
options.query = 'oth'; options.query = 'oth';
actual_value = options.highlighter(othello); actual_value = options.highlighter(othello);
expected_value = ' <img class="typeahead-image" src="https://secure.gravatar.com/avatar/md5-othello@zulip.com?d&#x3D;identicon&amp;s&#x3D;50" />\n<strong>Othello, the Moor of Venice</strong>'; expected_value = ` <img class="typeahead-image" src="/avatar/${othello.user_id}&amp;s&#x3D;50" />\n<strong>Othello, the Moor of Venice</strong>`;
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
options.query = 'Lear'; options.query = 'Lear';
actual_value = options.highlighter(cordelia); actual_value = options.highlighter(cordelia);
expected_value = ' <img class="typeahead-image" src="https://secure.gravatar.com/avatar/md5-cordelia@zulip.com?d&#x3D;identicon&amp;s&#x3D;50" />\n<strong>Cordelia Lear</strong>'; expected_value = ` <img class="typeahead-image" src="/avatar/${cordelia.user_id}&amp;s&#x3D;50" />\n<strong>Cordelia Lear</strong>`;
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
options.query = 'othello@zulip.com, co'; options.query = 'othello@zulip.com, co';
actual_value = options.highlighter(cordelia); actual_value = options.highlighter(cordelia);
expected_value = ' <img class="typeahead-image" src="https://secure.gravatar.com/avatar/md5-cordelia@zulip.com?d&#x3D;identicon&amp;s&#x3D;50" />\n<strong>Cordelia Lear</strong>'; expected_value = ` <img class="typeahead-image" src="/avatar/${cordelia.user_id}&amp;s&#x3D;50" />\n<strong>Cordelia Lear</strong>`;
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
function matcher(query, person) { function matcher(query, person) {
@ -831,7 +831,7 @@ run_test('initialize', () => {
// content_highlighter. // content_highlighter.
fake_this = { completing: 'mention', token: 'othello' }; fake_this = { completing: 'mention', token: 'othello' };
actual_value = options.highlighter.call(fake_this, othello); actual_value = options.highlighter.call(fake_this, othello);
expected_value = ' <img class="typeahead-image" src="https://secure.gravatar.com/avatar/md5-othello@zulip.com?d&#x3D;identicon&amp;s&#x3D;50" />\n<strong>Othello, the Moor of Venice</strong>'; expected_value = ` <img class="typeahead-image" src="/avatar/${othello.user_id}&amp;s&#x3D;50" />\n<strong>Othello, the Moor of Venice</strong>`;
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this = { completing: 'mention', token: 'hamletcharacters' }; fake_this = { completing: 'mention', token: 'hamletcharacters' };

View File

@ -33,6 +33,7 @@ const me = {
is_admin: false, is_admin: false,
is_guest: false, is_guest: false,
is_bot: false, is_bot: false,
// no avatar, so client should construct a /avatar/{user_id} URL.
}; };
const isaac = { const isaac = {
@ -139,6 +140,8 @@ const maria = {
email: 'Athens@example.com', email: 'Athens@example.com',
user_id: 302, user_id: 302,
full_name: 'Maria Athens', full_name: 'Maria Athens',
// With client_gravatar enabled, requests that client compute gravatar
avatar_url: null,
}; };
const ashton = { const ashton = {
@ -605,6 +608,7 @@ initialize();
run_test('message_methods', () => { run_test('message_methods', () => {
people.add_active_user(charles); people.add_active_user(charles);
people.add_active_user(maria); people.add_active_user(maria);
people.add_active_user(ashton);
// We don't rely on Maria to have all flags set explicitly-- // We don't rely on Maria to have all flags set explicitly--
// undefined values are just treated as falsy. // undefined values are just treated as falsy.
@ -615,7 +619,7 @@ run_test('message_methods', () => {
assert.deepEqual(people.sender_info_with_small_avatar_urls_for_sender_ids([30]), [ assert.deepEqual(people.sender_info_with_small_avatar_urls_for_sender_ids([30]), [
{ {
avatar_url_small: 'https://secure.gravatar.com/avatar/md5-me@example.com?d=identicon&s=50', avatar_url_small: '/avatar/30&s=50',
email: 'me@example.com', email: 'me@example.com',
full_name: 'Me the Third', full_name: 'Me the Third',
is_admin: false, is_admin: false,
@ -681,6 +685,13 @@ run_test('message_methods', () => {
'https://secure.gravatar.com/avatar/md5-foo@example.com?d=identicon&s=50' 'https://secure.gravatar.com/avatar/md5-foo@example.com?d=identicon&s=50'
); );
message = {
sender_id: ashton.user_id,
};
assert.equal(people.small_avatar_url(message),
`/avatar/${ashton.user_id}&s=50`
);
message = { message = {
type: 'private', type: 'private',
display_recipient: [ display_recipient: [

View File

@ -28,7 +28,7 @@ const isaac_item = {
email: 'isaac@example.com', email: 'isaac@example.com',
display_value: 'Isaac Newton', display_value: 'Isaac Newton',
user_id: isaac.user_id, user_id: isaac.user_id,
img_src: 'https://secure.gravatar.com/avatar/md5-isaac@example.com?d=identicon&s=50', img_src: `/avatar/${isaac.user_id}&s=50`,
}; };
run_test('setup', () => { run_test('setup', () => {

View File

@ -604,7 +604,12 @@ exports.small_avatar_url_for_person = function (person) {
if (person.avatar_url) { if (person.avatar_url) {
return exports.format_small_avatar_url(person.avatar_url); return exports.format_small_avatar_url(person.avatar_url);
} }
return gravatar_url_for_email(person.email);
if (person.avatar_url === null) {
return gravatar_url_for_email(person.email);
}
return exports.format_small_avatar_url('/avatar/' + person.user_id);
}; };
exports.sender_info_with_small_avatar_urls_for_sender_ids = function (sender_ids) { exports.sender_info_with_small_avatar_urls_for_sender_ids = function (sender_ids) {
@ -647,6 +652,15 @@ exports.small_avatar_url = function (message) {
return exports.format_small_avatar_url(message.avatar_url); return exports.format_small_avatar_url(message.avatar_url);
} }
if (person && person.avatar_url === undefined) {
// If we don't have an avatar_url at all, we use `GET
// /avatar/{user_id}` endpoint to obtain avatar url. This is
// required to take advantage of the user_avatar_url_field_optional
// optimization, which saves a huge amount of network traffic on
// servers with 10,000s of user accounts.
return exports.format_small_avatar_url('/avatar/' + person.user_id);
}
// For computing the user's email, we first trust the person // For computing the user's email, we first trust the person
// object since that is updated via our real-time sync system, but // object since that is updated via our real-time sync system, but
// if unavailable, we use the sender email. // if unavailable, we use the sender email.

View File

@ -192,6 +192,7 @@ def home_real(request: HttpRequest) -> HttpResponse:
client_capabilities = { client_capabilities = {
'notification_settings_null': True, 'notification_settings_null': True,
'bulk_message_deletion': True, 'bulk_message_deletion': True,
'user_avatar_url_field_optional': True,
} }
register_ret = do_events_register(user_profile, request.client, register_ret = do_events_register(user_profile, request.client,