FEATURE: reduce avatar sizes to 6 from 20 (#21319)
* FEATURE: reduce avatar sizes to 6 from 20 This PR introduces 3 changes: 1. SiteSetting.avatar_sizes, now does what is says on the tin. previously it would introduce a large number of extra sizes, to allow for various DPIs. Instead we now trust the admin with the size list. 2. When `avatar_sizes` changes, we ensure consistency and remove resized avatars that are not longer allowed per site setting. This happens on the 12 hourly job and limited out of the box to 20k cleanups per cycle, given this may reach out to AWS 20k times to remove things. 3.Our default avatar sizes are now "24|48|72|96|144|288" these sizes were very specifically picked to limit amount of bluriness introduced by webkit. Our avatars are already blurry due to 1px border, so this corrects old blur. This change heavily reduces storage required by forums which simplifies site moves and more. Co-authored-by: David Taylor <david@taylorhq.com>
This commit is contained in:
parent
70c3248b0e
commit
c2332d7505
|
@ -20,17 +20,17 @@ export function splitString(str, separator = ",") {
|
|||
export function translateSize(size) {
|
||||
switch (size) {
|
||||
case "tiny":
|
||||
return 20;
|
||||
return 24;
|
||||
case "small":
|
||||
return 25;
|
||||
return 24;
|
||||
case "medium":
|
||||
return 32;
|
||||
return 48;
|
||||
case "large":
|
||||
return 45;
|
||||
return 48;
|
||||
case "extra_large":
|
||||
return 60;
|
||||
return 96;
|
||||
case "huge":
|
||||
return 120;
|
||||
return 144;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
@ -62,11 +62,31 @@ export function avatarUrl(template, size, { customGetURL } = {}) {
|
|||
if (!template) {
|
||||
return "";
|
||||
}
|
||||
const rawSize = getRawSize(translateSize(size));
|
||||
const rawSize = getRawAvatarSize(translateSize(size));
|
||||
const templatedPath = template.replace(/\{size\}/g, rawSize);
|
||||
return (customGetURL || getURLWithCDN)(templatedPath);
|
||||
}
|
||||
|
||||
let allowedSizes = null;
|
||||
|
||||
export function getRawAvatarSize(size) {
|
||||
allowedSizes ??=
|
||||
helperContext()
|
||||
.siteSettings["avatar_sizes"].split("|")
|
||||
.map((s) => parseInt(s, 10))
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
size = getRawSize(size);
|
||||
|
||||
for (let i = 0; i < allowedSizes.length; i++) {
|
||||
if (allowedSizes[i] >= size) {
|
||||
return allowedSizes[i];
|
||||
}
|
||||
}
|
||||
|
||||
return allowedSizes[allowedSizes.length - 1];
|
||||
}
|
||||
|
||||
export function getRawSize(size) {
|
||||
const pixelRatio = window.devicePixelRatio || 1;
|
||||
let rawSize = 1;
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
escapeExpression,
|
||||
extractDomainFromUrl,
|
||||
fillMissingDates,
|
||||
getRawSize,
|
||||
getRawAvatarSize,
|
||||
inCodeBlock,
|
||||
initializeDefaultHomepage,
|
||||
mergeSortedLists,
|
||||
|
@ -86,17 +86,26 @@ module("Unit | Utilities", function (hooks) {
|
|||
);
|
||||
});
|
||||
|
||||
test("getRawAvatarSize avoids redirects", function (assert) {
|
||||
assert.strictEqual(
|
||||
getRawAvatarSize(1),
|
||||
24,
|
||||
"returns the first size larger on the menu"
|
||||
);
|
||||
|
||||
assert.strictEqual(getRawAvatarSize(2000), 288, "caps at highest");
|
||||
});
|
||||
|
||||
test("avatarUrl", function (assert) {
|
||||
let rawSize = getRawSize;
|
||||
assert.blank(avatarUrl("", "tiny"), "no template returns blank");
|
||||
assert.strictEqual(
|
||||
avatarUrl("/fake/template/{size}.png", "tiny"),
|
||||
"/fake/template/" + rawSize(20) + ".png",
|
||||
"/fake/template/" + getRawAvatarSize(24) + ".png",
|
||||
"simple avatar url"
|
||||
);
|
||||
assert.strictEqual(
|
||||
avatarUrl("/fake/template/{size}.png", "large"),
|
||||
"/fake/template/" + rawSize(45) + ".png",
|
||||
"/fake/template/" + getRawAvatarSize(48) + ".png",
|
||||
"different size"
|
||||
);
|
||||
|
||||
|
@ -104,7 +113,9 @@ module("Unit | Utilities", function (hooks) {
|
|||
|
||||
assert.strictEqual(
|
||||
avatarUrl("/fake/template/{size}.png", "large"),
|
||||
"https://app-cdn.example.com/fake/template/" + rawSize(45) + ".png",
|
||||
"https://app-cdn.example.com/fake/template/" +
|
||||
getRawAvatarSize(48) +
|
||||
".png",
|
||||
"uses CDN if present"
|
||||
);
|
||||
});
|
||||
|
@ -124,7 +135,7 @@ module("Unit | Utilities", function (hooks) {
|
|||
let avatarTemplate = "/path/to/avatar/{size}.png";
|
||||
assert.strictEqual(
|
||||
avatarImg({ avatarTemplate, size: "tiny" }),
|
||||
"<img loading='lazy' alt='' width='20' height='20' src='/path/to/avatar/40.png' class='avatar'>",
|
||||
"<img loading='lazy' alt='' width='24' height='24' src='/path/to/avatar/48.png' class='avatar'>",
|
||||
"it returns the avatar html"
|
||||
);
|
||||
|
||||
|
@ -134,7 +145,7 @@ module("Unit | Utilities", function (hooks) {
|
|||
size: "tiny",
|
||||
title: "evilest trout",
|
||||
}),
|
||||
"<img loading='lazy' alt='' width='20' height='20' src='/path/to/avatar/40.png' class='avatar' title='evilest trout' aria-label='evilest trout'>",
|
||||
"<img loading='lazy' alt='' width='24' height='24' src='/path/to/avatar/48.png' class='avatar' title='evilest trout' aria-label='evilest trout'>",
|
||||
"it adds a title if supplied"
|
||||
);
|
||||
|
||||
|
@ -144,7 +155,7 @@ module("Unit | Utilities", function (hooks) {
|
|||
size: "tiny",
|
||||
extraClasses: "evil fish",
|
||||
}),
|
||||
"<img loading='lazy' alt='' width='20' height='20' src='/path/to/avatar/40.png' class='avatar evil fish'>",
|
||||
"<img loading='lazy' alt='' width='24' height='24' src='/path/to/avatar/48.png' class='avatar evil fish'>",
|
||||
"it adds extra classes if supplied"
|
||||
);
|
||||
|
||||
|
|
|
@ -370,7 +370,7 @@ module("Unit | Model | report", function (hooks) {
|
|||
const computedUsernameLabel = usernameLabel.compute(row);
|
||||
assert.strictEqual(
|
||||
computedUsernameLabel.formattedValue,
|
||||
"<a href='/admin/users/1/joffrey'><img loading='lazy' alt='' width='20' height='20' src='/' class='avatar' title='joffrey' aria-label='joffrey'><span class='username'>joffrey</span></a>"
|
||||
"<a href='/admin/users/1/joffrey'><img loading='lazy' alt='' width='24' height='24' src='/' class='avatar' title='joffrey' aria-label='joffrey'><span class='username'>joffrey</span></a>"
|
||||
);
|
||||
assert.strictEqual(computedUsernameLabel.value, "joffrey");
|
||||
|
||||
|
@ -459,7 +459,7 @@ module("Unit | Model | report", function (hooks) {
|
|||
const userLink = computedLabels[0].compute(row).formattedValue;
|
||||
assert.strictEqual(
|
||||
userLink,
|
||||
"<a href='/forum/admin/users/1/joffrey'><img loading='lazy' alt='' width='20' height='20' src='/forum/' class='avatar' title='joffrey' aria-label='joffrey'><span class='username'>joffrey</span></a>"
|
||||
"<a href='/forum/admin/users/1/joffrey'><img loading='lazy' alt='' width='24' height='24' src='/forum/' class='avatar' title='joffrey' aria-label='joffrey'><span class='username'>joffrey</span></a>"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -150,7 +150,7 @@ class UserAvatar < ActiveRecord::Base
|
|||
tempfile.close! if tempfile && tempfile.respond_to?(:close!)
|
||||
end
|
||||
|
||||
def self.ensure_consistency!
|
||||
def self.ensure_consistency!(max_optimized_avatars_to_remove: 20_000)
|
||||
DB.exec <<~SQL
|
||||
UPDATE user_avatars
|
||||
SET gravatar_upload_id = NULL
|
||||
|
@ -174,6 +174,29 @@ class UserAvatar < ActiveRecord::Base
|
|||
up.id IS NULL
|
||||
)
|
||||
SQL
|
||||
|
||||
ids =
|
||||
DB.query_single(<<~SQL, sizes: Discourse.avatar_sizes, limit: max_optimized_avatars_to_remove)
|
||||
SELECT oi.id FROM user_avatars a
|
||||
JOIN optimized_images oi ON oi.upload_id = a.custom_upload_id
|
||||
LEFT JOIN upload_references ur ON ur.upload_id = a.custom_upload_id and ur.target_type <> 'UserAvatar'
|
||||
WHERE oi.width not in (:sizes) AND oi.height not in (:sizes) AND ur.upload_id IS NULL
|
||||
LIMIT :limit
|
||||
SQL
|
||||
|
||||
warnings_reported = 0
|
||||
|
||||
ids.each do |id|
|
||||
begin
|
||||
OptimizedImage.find(id).destroy!
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
rescue => e
|
||||
if warnings_reported < 10
|
||||
Discourse.warn_exception(e, message: "Failed to remove optimized image")
|
||||
warnings_reported += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1467,9 +1467,10 @@ files:
|
|||
type: url_list
|
||||
client: true
|
||||
avatar_sizes:
|
||||
default: "20|25|32|45|60|120"
|
||||
default: "24|48|72|96|144|288"
|
||||
type: list
|
||||
list_type: compact
|
||||
client: true
|
||||
external_system_avatars_enabled:
|
||||
default: true
|
||||
client: true
|
||||
|
|
|
@ -327,19 +327,12 @@ module Discourse
|
|||
@anonymous_top_menu_items ||= Discourse.anonymous_filters + %i[categories top]
|
||||
end
|
||||
|
||||
# list of pixel ratios Discourse tries to optimize for
|
||||
PIXEL_RATIOS ||= [1, 1.5, 2, 3]
|
||||
|
||||
def self.avatar_sizes
|
||||
# TODO: should cache these when we get a notification system for site settings
|
||||
set = Set.new
|
||||
|
||||
SiteSetting
|
||||
.avatar_sizes
|
||||
.split("|")
|
||||
.map(&:to_i)
|
||||
.each { |size| PIXEL_RATIOS.each { |pixel_ratio| set << (size * pixel_ratio).to_i } }
|
||||
|
||||
set
|
||||
Set.new(SiteSetting.avatar_sizes.split("|").map(&:to_i))
|
||||
end
|
||||
|
||||
def self.activate_plugins!
|
||||
|
|
|
@ -208,6 +208,7 @@ module PrettyText
|
|||
__optInput.watchedWordsReplace = #{WordWatcher.word_matcher_regexps(:replace, engine: :js).to_json};
|
||||
__optInput.watchedWordsLink = #{WordWatcher.word_matcher_regexps(:link, engine: :js).to_json};
|
||||
__optInput.additionalOptions = #{Site.markdown_additional_options.to_json};
|
||||
__optInput.avatar_sizes = #{SiteSetting.avatar_sizes.to_json};
|
||||
JS
|
||||
|
||||
buffer << "__optInput.topicId = #{opts[:topic_id].to_i};\n" if opts[:topic_id]
|
||||
|
|
|
@ -16,10 +16,11 @@ define("I18n", ["exports"], function (exports) {
|
|||
exports.default = I18n;
|
||||
});
|
||||
|
||||
// Formatting doesn't currently need any helper context
|
||||
define("discourse-common/lib/helpers", ["exports"], function (exports) {
|
||||
exports.helperContext = function () {
|
||||
return {};
|
||||
return {
|
||||
siteSettings: { avatar_sizes: __optInput.avatar_sizes },
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -237,7 +237,7 @@ martin</div>
|
|||
<div class="chat-transcript" data-message-id="#{message1.id}" data-username="#{message1.user.username}" data-datetime="#{message1.created_at.iso8601}" data-channel-name="#{channel.name}" data-channel-id="#{channel.id}">
|
||||
<div class="chat-transcript-user">
|
||||
<div class="chat-transcript-user-avatar">
|
||||
<img loading="lazy" alt="" width="20" height="20" src="//test.localhost#{post.user.avatar_template.gsub("{size}", "40")}" class="avatar">
|
||||
<img loading="lazy" alt="" width="24" height="24" src="//test.localhost#{post.user.avatar_template.gsub("{size}", "48")}" class="avatar">
|
||||
</div>
|
||||
<div class="chat-transcript-username">
|
||||
#{message1.user.username}</div>
|
||||
|
@ -251,7 +251,7 @@ martin</div>
|
|||
<div class="chat-transcript" data-message-id="#{message2.id}" data-username="#{message2.user.username}" data-datetime="#{message2.created_at.iso8601}" data-channel-name="#{channel.name}" data-channel-id="#{channel.id}">
|
||||
<div class="chat-transcript-user">
|
||||
<div class="chat-transcript-user-avatar">
|
||||
<img loading="lazy" alt="" width="20" height="20" src="//test.localhost#{post.user.avatar_template.gsub("{size}", "40")}" class="avatar">
|
||||
<img loading="lazy" alt="" width="24" height="24" src="//test.localhost#{post.user.avatar_template.gsub("{size}", "48")}" class="avatar">
|
||||
</div>
|
||||
<div class="chat-transcript-username">
|
||||
#{message2.user.username}</div>
|
||||
|
|
|
@ -75,7 +75,7 @@ describe Chat::Message do
|
|||
post = Fabricate(:post, topic: topic)
|
||||
SiteSetting.external_system_avatars_enabled = false
|
||||
avatar_src =
|
||||
"//test.localhost#{User.system_avatar_template(post.user.username).gsub("{size}", "40")}"
|
||||
"//test.localhost#{User.system_avatar_template(post.user.username).gsub("{size}", "48")}"
|
||||
|
||||
cooked = described_class.cook(<<~RAW)
|
||||
[quote="#{post.user.username}, post:#{post.post_number}, topic:#{topic.id}"]
|
||||
|
@ -87,7 +87,7 @@ describe Chat::Message do
|
|||
<aside class="quote no-group" data-username="#{post.user.username}" data-post="#{post.post_number}" data-topic="#{topic.id}">
|
||||
<div class="title">
|
||||
<div class="quote-controls"></div>
|
||||
<img loading="lazy" alt="" width="20" height="20" src="#{avatar_src}" class="avatar"><a href="http://test.localhost/t/some-quotable-topic/#{topic.id}/#{post.post_number}">#{topic.title}</a>
|
||||
<img loading="lazy" alt="" width="24" height="24" src="#{avatar_src}" class="avatar"><a href="http://test.localhost/t/some-quotable-topic/#{topic.id}/#{post.post_number}">#{topic.title}</a>
|
||||
</div>
|
||||
<blockquote>
|
||||
<p>Mark me...this will go down in history.</p>
|
||||
|
@ -101,9 +101,9 @@ describe Chat::Message do
|
|||
user = Fabricate(:user, username: "chatbbcodeuser")
|
||||
user2 = Fabricate(:user, username: "otherbbcodeuser")
|
||||
avatar_src =
|
||||
"//test.localhost#{User.system_avatar_template(user.username).gsub("{size}", "40")}"
|
||||
"//test.localhost#{User.system_avatar_template(user.username).gsub("{size}", "48")}"
|
||||
avatar_src2 =
|
||||
"//test.localhost#{User.system_avatar_template(user2.username).gsub("{size}", "40")}"
|
||||
"//test.localhost#{User.system_avatar_template(user2.username).gsub("{size}", "48")}"
|
||||
msg1 =
|
||||
Fabricate(
|
||||
:chat_message,
|
||||
|
@ -135,7 +135,7 @@ describe Chat::Message do
|
|||
</div>
|
||||
<div class="chat-transcript-user">
|
||||
<div class="chat-transcript-user-avatar">
|
||||
<img loading="lazy" alt="" width="20" height="20" src="#{avatar_src}" class="avatar">
|
||||
<img loading="lazy" alt="" width="24" height="24" src="#{avatar_src}" class="avatar">
|
||||
</div>
|
||||
<div class="chat-transcript-username">
|
||||
chatbbcodeuser</div>
|
||||
|
@ -150,7 +150,7 @@ describe Chat::Message do
|
|||
<div class="chat-transcript chat-transcript-chained" data-message-id="#{msg2.id}" data-username="otherbbcodeuser" data-datetime="#{msg2.created_at.iso8601}">
|
||||
<div class="chat-transcript-user">
|
||||
<div class="chat-transcript-user-avatar">
|
||||
<img loading="lazy" alt="" width="20" height="20" src="#{avatar_src2}" class="avatar">
|
||||
<img loading="lazy" alt="" width="24" height="24" src="#{avatar_src2}" class="avatar">
|
||||
</div>
|
||||
<div class="chat-transcript-username">
|
||||
otherbbcodeuser</div>
|
||||
|
|
|
@ -13,28 +13,8 @@ RSpec.describe Discourse do
|
|||
|
||||
describe "avatar_sizes" do
|
||||
it "returns a list of integers" do
|
||||
expect(Discourse.avatar_sizes).to contain_exactly(
|
||||
20,
|
||||
25,
|
||||
30,
|
||||
32,
|
||||
37,
|
||||
40,
|
||||
45,
|
||||
48,
|
||||
50,
|
||||
60,
|
||||
64,
|
||||
67,
|
||||
75,
|
||||
90,
|
||||
96,
|
||||
120,
|
||||
135,
|
||||
180,
|
||||
240,
|
||||
360,
|
||||
)
|
||||
SiteSetting.avatar_sizes = "10|20|30"
|
||||
expect(Discourse.avatar_sizes).to contain_exactly(10, 20, 30)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -94,7 +94,7 @@ RSpec.describe Oneboxer do
|
|||
|
||||
onebox = preview(public_reply.url, user, public_category, public_topic)
|
||||
expect(onebox).not_to include(public_topic.title)
|
||||
expect(onebox).to include(replier.avatar_template_url.sub("{size}", "40"))
|
||||
expect(onebox).to include(replier.avatar_template_url.sub("{size}", "48"))
|
||||
|
||||
expect(preview(public_hidden.url, user, public_category)).to match_html(
|
||||
link(public_hidden.url),
|
||||
|
|
|
@ -251,7 +251,7 @@ RSpec.describe PrettyText do
|
|||
<aside class="quote no-group" data-username="#{user.username}" data-post="123" data-topic="456" data-full="true">
|
||||
<div class="title">
|
||||
<div class="quote-controls"></div>
|
||||
<img loading="lazy" alt="" width="20" height="20" src="//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png" class="avatar"> #{user.username}:</div>
|
||||
<img loading="lazy" alt="" width="24" height="24" src="//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/48.png" class="avatar"> #{user.username}:</div>
|
||||
<blockquote>
|
||||
<p>ddd</p>
|
||||
</blockquote>
|
||||
|
@ -273,7 +273,7 @@ RSpec.describe PrettyText do
|
|||
<aside class="quote no-group" data-username="#{user.username}" data-post="123" data-topic="456" data-full="true">
|
||||
<div class="title">
|
||||
<div class="quote-controls"></div>
|
||||
<img loading="lazy" alt="" width="20" height="20" src="//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png" class="avatar"> #{user.username}:</div>
|
||||
<img loading="lazy" alt="" width="24" height="24" src="//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/48.png" class="avatar"> #{user.username}:</div>
|
||||
<blockquote>
|
||||
<p>ddd</p>
|
||||
</blockquote>
|
||||
|
@ -294,7 +294,7 @@ RSpec.describe PrettyText do
|
|||
<aside class="quote no-group" data-username="#{user.username}" data-post="555" data-topic="666">
|
||||
<div class="title">
|
||||
<div class="quote-controls"></div>
|
||||
<img loading="lazy" alt="" width="20" height="20" src="//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png" class="avatar"> #{user.username}:</div>
|
||||
<img loading="lazy" alt="" width="24" height="24" src="//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/48.png" class="avatar"> #{user.username}:</div>
|
||||
<blockquote>
|
||||
<p>ddd</p>
|
||||
</blockquote>
|
||||
|
@ -320,7 +320,7 @@ RSpec.describe PrettyText do
|
|||
<aside class="quote group-#{group.name}" data-username="#{user.username}" data-post="2" data-topic="#{topic.id}">
|
||||
<div class="title">
|
||||
<div class="quote-controls"></div>
|
||||
<img loading="lazy" alt="" width="20" height="20" src="//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png" class="avatar"><a href="http://test.localhost/t/this-is-a-test-topic/#{topic.id}/2">This is a test topic</a>
|
||||
<img loading="lazy" alt="" width="24" height="24" src="//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/48.png" class="avatar"><a href="http://test.localhost/t/this-is-a-test-topic/#{topic.id}/2">This is a test topic</a>
|
||||
</div>
|
||||
<blockquote>
|
||||
<p>ddd</p>
|
||||
|
|
|
@ -44,11 +44,17 @@ RSpec.describe Badge do
|
|||
end
|
||||
|
||||
it "can ensure consistency" do
|
||||
b = Badge.first
|
||||
b = Badge.find_by_name("Basic User")
|
||||
|
||||
b.grant_count = 100
|
||||
b.save
|
||||
|
||||
UserBadge.create!(user_id: -100, badge_id: b.id, granted_at: 1.minute.ago, granted_by_id: -1)
|
||||
UserBadge.create!(
|
||||
user_id: User.minimum(:id) - 1,
|
||||
badge_id: b.id,
|
||||
granted_at: 1.minute.ago,
|
||||
granted_by_id: -1,
|
||||
)
|
||||
UserBadge.create!(
|
||||
user_id: User.first.id,
|
||||
badge_id: b.id,
|
||||
|
|
|
@ -95,7 +95,7 @@ RSpec.describe UserAvatar do
|
|||
|
||||
stub_request(
|
||||
:get,
|
||||
"https://www.gravatar.com/avatar/#{avatar.user.email_hash}.png?d=404&reset_cache=5555&s=360",
|
||||
"https://www.gravatar.com/avatar/#{avatar.user.email_hash}.png?d=404&reset_cache=5555&s=#{Discourse.avatar_sizes.max}",
|
||||
).to_return(status: 404, body: "", headers: {})
|
||||
|
||||
expect do avatar.update_gravatar! end.to_not change { Upload.count }
|
||||
|
@ -184,12 +184,39 @@ RSpec.describe UserAvatar do
|
|||
end
|
||||
|
||||
describe "ensure_consistency!" do
|
||||
it "cleans up incorrectly sized avatars" do
|
||||
SiteSetting.avatar_sizes = "10|20|30"
|
||||
|
||||
upload = Fabricate(:upload)
|
||||
user_avatar = Fabricate(:user).user_avatar
|
||||
user_avatar.update_columns(custom_upload_id: upload.id)
|
||||
|
||||
Fabricate(:optimized_image, upload: upload, width: 10, height: 10)
|
||||
Fabricate(:optimized_image, upload: upload, width: 15, height: 15)
|
||||
Fabricate(:optimized_image, upload: upload, width: 20, height: 20)
|
||||
|
||||
UserAvatar.ensure_consistency!
|
||||
|
||||
expect(OptimizedImage.where(upload_id: upload.id).pluck(:width, :height).sort).to eq(
|
||||
[[10, 10], [20, 20]],
|
||||
)
|
||||
|
||||
# will not clean up if referenced
|
||||
Fabricate(:optimized_image, upload: upload, width: 15, height: 15)
|
||||
UploadReference.create!(upload: upload, target: Fabricate(:post))
|
||||
|
||||
UserAvatar.ensure_consistency!
|
||||
|
||||
expect(OptimizedImage.where(upload_id: upload.id).pluck(:width, :height).sort).to eq(
|
||||
[[10, 10], [15, 15], [20, 20]],
|
||||
)
|
||||
end
|
||||
|
||||
it "will clean up dangling avatars" do
|
||||
upload1 = Fabricate(:upload)
|
||||
upload2 = Fabricate(:upload)
|
||||
|
||||
user_avatar = Fabricate(:user).user_avatar
|
||||
|
||||
user_avatar.update_columns(gravatar_upload_id: upload1.id, custom_upload_id: upload2.id)
|
||||
|
||||
upload1.destroy!
|
||||
|
|
|
@ -142,7 +142,8 @@ RSpec.describe User do
|
|||
end
|
||||
|
||||
it "should not create any sidebar section link records for non human users" do
|
||||
user = Fabricate(:user, id: -Time.now.to_i)
|
||||
id = -Time.now.to_i
|
||||
user = Fabricate(:user, id: id)
|
||||
|
||||
expect(SidebarSectionLink.exists?(user: user)).to eq(false)
|
||||
end
|
||||
|
@ -1359,7 +1360,7 @@ RSpec.describe User do
|
|||
describe "after 3 days" do
|
||||
it "should log a second visited_at record when we log an update later" do
|
||||
user.update_last_seen!
|
||||
future_date = freeze_time(3.days.from_now)
|
||||
freeze_time(3.days.from_now)
|
||||
user.update_last_seen!
|
||||
|
||||
expect(user.user_visits.count).to eq(2)
|
||||
|
@ -2349,15 +2350,15 @@ RSpec.describe User do
|
|||
end
|
||||
|
||||
it "has the correct counts" do
|
||||
notification = Fabricate(:notification, user: user)
|
||||
notification2 = Fabricate(:notification, user: user, read: true)
|
||||
notification3 =
|
||||
_notification = Fabricate(:notification, user: user)
|
||||
_notification2 = Fabricate(:notification, user: user, read: true)
|
||||
_notification3 =
|
||||
Fabricate(
|
||||
:notification,
|
||||
user: user,
|
||||
notification_type: Notification.types[:private_message],
|
||||
)
|
||||
notification4 =
|
||||
_notification4 =
|
||||
Fabricate(
|
||||
:notification,
|
||||
user: user,
|
||||
|
@ -2377,8 +2378,8 @@ RSpec.describe User do
|
|||
end
|
||||
|
||||
it "does not publish to the /notification channel for users who have not been seen in > 30 days" do
|
||||
notification = Fabricate(:notification, user: user)
|
||||
notification2 = Fabricate(:notification, user: user, read: true)
|
||||
_notification = Fabricate(:notification, user: user)
|
||||
_notification2 = Fabricate(:notification, user: user, read: true)
|
||||
user.update(last_seen_at: 31.days.ago)
|
||||
|
||||
message =
|
||||
|
@ -2934,7 +2935,7 @@ RSpec.describe User do
|
|||
it "only includes enabled totp 2FA" do
|
||||
enabled_totp_2fa =
|
||||
Fabricate(:user_second_factor_totp, user: user, name: "Enabled TOTP", enabled: true)
|
||||
disabled_totp_2fa =
|
||||
_disabled_totp_2fa =
|
||||
Fabricate(:user_second_factor_totp, user: user, name: "Disabled TOTP", enabled: false)
|
||||
|
||||
expect(user.totps.map(&:id)).to eq([enabled_totp_2fa.id])
|
||||
|
@ -2950,7 +2951,7 @@ RSpec.describe User do
|
|||
name: "Enabled YubiKey",
|
||||
enabled: true,
|
||||
)
|
||||
disabled_security_key_2fa =
|
||||
_disabled_security_key_2fa =
|
||||
Fabricate(
|
||||
:user_security_key_with_random_credential,
|
||||
user: user,
|
||||
|
@ -3371,7 +3372,7 @@ RSpec.describe User do
|
|||
last_notification = Fabricate(:notification, user: user)
|
||||
deleted_notification = Fabricate(:notification, user: user)
|
||||
deleted_notification.topic.trash!
|
||||
someone_else_notification = Fabricate(:notification, user: Fabricate(:user))
|
||||
_someone_else_notification = Fabricate(:notification, user: Fabricate(:user))
|
||||
|
||||
expect(user.bump_last_seen_notification!).to eq(true)
|
||||
expect(user.reload.seen_notification_id).to eq(last_notification.id)
|
||||
|
|
|
@ -462,7 +462,7 @@ RSpec.describe "users" do
|
|||
before do
|
||||
stub_request(
|
||||
:get,
|
||||
%r{https://www.gravatar.com/avatar/\w+.png\?d=404&reset_cache=\S+&s=360},
|
||||
%r{https://www.gravatar.com/avatar/\w+.png\?d=404&reset_cache=\S+&s=#{Discourse.avatar_sizes.max}},
|
||||
).with(
|
||||
headers: {
|
||||
"Accept" => "*/*",
|
||||
|
|
|
@ -60,7 +60,7 @@ RSpec.describe UserAvatarsController do
|
|||
|
||||
it "handles non local content correctly" do
|
||||
setup_s3
|
||||
SiteSetting.avatar_sizes = "100|49"
|
||||
SiteSetting.avatar_sizes = "100|98|49"
|
||||
SiteSetting.unicode_usernames = true
|
||||
SiteSetting.s3_cdn_url = "http://cdn.com"
|
||||
|
||||
|
@ -111,7 +111,7 @@ RSpec.describe UserAvatarsController do
|
|||
it "redirects to external store when enabled" do
|
||||
global_setting :redirect_avatar_requests, true
|
||||
setup_s3
|
||||
SiteSetting.avatar_sizes = "100|49"
|
||||
SiteSetting.avatar_sizes = "100|98|49"
|
||||
SiteSetting.s3_cdn_url = "https://s3-cdn.example.com"
|
||||
set_cdn_url("https://app-cdn.example.com")
|
||||
|
||||
|
|
|
@ -162,12 +162,12 @@ RSpec.describe UserAnonymizer do
|
|||
[/quote]
|
||||
RAW
|
||||
|
||||
old_avatar_url = user.avatar_template.gsub("{size}", "40")
|
||||
old_avatar_url = user.avatar_template.gsub("{size}", "48")
|
||||
expect(post.cooked).to include(old_avatar_url)
|
||||
|
||||
make_anonymous
|
||||
post.reload
|
||||
new_avatar_url = user.reload.avatar_template.gsub("{size}", "40")
|
||||
new_avatar_url = user.reload.avatar_template.gsub("{size}", "48")
|
||||
|
||||
expect(post.cooked).to_not include(old_avatar_url)
|
||||
expect(post.cooked).to include(new_avatar_url)
|
||||
|
|
|
@ -425,7 +425,7 @@ RSpec.describe UsernameChanger do
|
|||
let(:quoted_post) do
|
||||
create_post(user: user, topic: topic, post_number: 1, raw: "quoted post")
|
||||
end
|
||||
let(:avatar_url) { user.avatar_template_url.gsub("{size}", "40") }
|
||||
let(:avatar_url) { user.avatar_template_url.gsub("{size}", "48") }
|
||||
|
||||
it "replaces the username in quote tags and updates avatar" do
|
||||
post = create_post_and_change_username(raw: <<~RAW)
|
||||
|
@ -469,7 +469,7 @@ RSpec.describe UsernameChanger do
|
|||
<aside class="quote no-group" data-username="bar" data-post="1" data-topic="#{quoted_post.topic.id}">
|
||||
<div class="title">
|
||||
<div class="quote-controls"></div>
|
||||
<img loading="lazy" alt='' width="20" height="20" src="#{avatar_url}" class="avatar"> bar:</div>
|
||||
<img loading="lazy" alt='' width="24" height="24" src="#{avatar_url}" class="avatar"> bar:</div>
|
||||
<blockquote>
|
||||
<p>quoted post</p>
|
||||
</blockquote>
|
||||
|
@ -477,7 +477,7 @@ RSpec.describe UsernameChanger do
|
|||
<aside class="quote no-group" data-username="bar">
|
||||
<div class="title">
|
||||
<div class="quote-controls"></div>
|
||||
<img loading="lazy" alt="" width="20" height="20" src="#{avatar_url}" class="avatar"> bar:</div>
|
||||
<img loading="lazy" alt="" width="24" height="24" src="#{avatar_url}" class="avatar"> bar:</div>
|
||||
<blockquote>
|
||||
<p>quoted post</p>
|
||||
</blockquote>
|
||||
|
@ -485,7 +485,7 @@ RSpec.describe UsernameChanger do
|
|||
<aside class="quote no-group" data-username="bar" data-post="1" data-topic="#{quoted_post.topic.id}">
|
||||
<div class="title">
|
||||
<div class="quote-controls"></div>
|
||||
<img loading="lazy" alt="" width="20" height="20" src="#{avatar_url}" class="avatar"> bar:</div>
|
||||
<img loading="lazy" alt="" width="24" height="24" src="#{avatar_url}" class="avatar"> bar:</div>
|
||||
<blockquote>
|
||||
<p>quoted post</p>
|
||||
</blockquote>
|
||||
|
@ -516,7 +516,7 @@ RSpec.describe UsernameChanger do
|
|||
<aside class="quote no-group" data-username="bar" data-post="1" data-topic="#{quoted_post.topic.id}">
|
||||
<div class="title">
|
||||
<div class="quote-controls"></div>
|
||||
<img loading="lazy" alt='' width="20" height="20" src="#{avatar_url}" class="avatar"> bar:</div>
|
||||
<img loading="lazy" alt='' width="24" height="24" src="#{avatar_url}" class="avatar"> bar:</div>
|
||||
<blockquote>
|
||||
<p>quoted</p>
|
||||
</blockquote>
|
||||
|
@ -550,7 +550,7 @@ RSpec.describe UsernameChanger do
|
|||
end
|
||||
|
||||
def user_avatar_url(u)
|
||||
u.avatar_template_url.gsub("{size}", "40")
|
||||
u.avatar_template_url.gsub("{size}", "48")
|
||||
end
|
||||
|
||||
it "updates avatar for linked topics and posts" do
|
||||
|
@ -562,7 +562,7 @@ RSpec.describe UsernameChanger do
|
|||
<aside class="quote" data-post="#{quoted_post.post_number}" data-topic="#{quoted_post.topic.id}">
|
||||
<div class="title">
|
||||
<div class="quote-controls"></div>
|
||||
<img loading="lazy" alt="" width="20" height="20" src="#{avatar_url}" class="avatar">
|
||||
<img loading="lazy" alt="" width="24" height="24" src="#{avatar_url}" class="avatar">
|
||||
<a href="#{protocol_relative_url(quoted_post.full_url)}">#{quoted_post.topic.title}</a>
|
||||
</div>
|
||||
<blockquote>
|
||||
|
@ -573,7 +573,7 @@ RSpec.describe UsernameChanger do
|
|||
<aside class="quote" data-post="#{quoted_post.post_number}" data-topic="#{quoted_post.topic.id}">
|
||||
<div class="title">
|
||||
<div class="quote-controls"></div>
|
||||
<img loading="lazy" alt="" width="20" height="20" src="#{avatar_url}" class="avatar">
|
||||
<img loading="lazy" alt="" width="24" height="24" src="#{avatar_url}" class="avatar">
|
||||
<a href="#{protocol_relative_url(quoted_post.topic.url)}">#{quoted_post.topic.title}</a>
|
||||
</div>
|
||||
<blockquote>
|
||||
|
@ -592,7 +592,7 @@ RSpec.describe UsernameChanger do
|
|||
<aside class="quote" data-post="#{quoted_post.post_number}" data-topic="#{quoted_post.topic.id}">
|
||||
<div class="title">
|
||||
<div class="quote-controls"></div>
|
||||
<img loading="lazy" alt="" width="20" height="20" src="#{avatar_url}" class="avatar">
|
||||
<img loading="lazy" alt="" width="24" height="24" src="#{avatar_url}" class="avatar">
|
||||
<a href="#{protocol_relative_url(quoted_post.full_url)}">#{quoted_post.topic.title}</a>
|
||||
</div>
|
||||
<blockquote>
|
||||
|
@ -603,7 +603,7 @@ RSpec.describe UsernameChanger do
|
|||
<aside class="quote" data-post="#{another_quoted_post.post_number}" data-topic="#{another_quoted_post.topic.id}">
|
||||
<div class="title">
|
||||
<div class="quote-controls"></div>
|
||||
<img loading="lazy" alt="" width="20" height="20" src="#{user_avatar_url(evil_trout)}" class="avatar">
|
||||
<img loading="lazy" alt="" width="24" height="24" src="#{user_avatar_url(evil_trout)}" class="avatar">
|
||||
<a href="#{protocol_relative_url(another_quoted_post.full_url)}">#{another_quoted_post.topic.title}</a>
|
||||
</div>
|
||||
<blockquote>
|
||||
|
|
Loading…
Reference in New Issue