From 306dca3a481f231c0c9c481811a3f8f676843089 Mon Sep 17 00:00:00 2001 From: Osama Sayegh Date: Fri, 22 Jul 2022 09:46:52 +0300 Subject: [PATCH] DEV: Add a helper method to clear every possible theme cache (#17585) Our theme system is very complex and it can take a while to figure out how to invalidate the various types of caches that are used throughout the theme system. So, having a single helper method that invalidates everything can be useful in emergency situations where there is no time to read through the code and figure out how to clear the various caches. Internal ticket: t64732. --- app/models/theme.rb | 5 +- lib/discourse.rb | 10 +++ spec/lib/discourse_spec.rb | 124 +++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 3 deletions(-) diff --git a/app/models/theme.rb b/app/models/theme.rb index 923415ecf1d..e1cc0419be7 100644 --- a/app/models/theme.rb +++ b/app/models/theme.rb @@ -151,9 +151,6 @@ class Theme < ActiveRecord::Base end Theme.expire_site_cache! - ColorScheme.hex_cache.clear - CSP::Extension.clear_theme_extensions_cache! - SvgSprite.expire_cache end def self.compiler_version @@ -217,6 +214,8 @@ class Theme < ActiveRecord::Base clear_cache! ApplicationSerializer.expire_cache_fragment!("user_themes") ColorScheme.hex_cache.clear + CSP::Extension.clear_theme_extensions_cache! + SvgSprite.expire_cache end def self.clear_default! diff --git a/lib/discourse.rb b/lib/discourse.rb index 757eeabf325..4616b887d2d 100644 --- a/lib/discourse.rb +++ b/lib/discourse.rb @@ -1028,4 +1028,14 @@ module Discourse def self.allow_dev_populate? Rails.env.development? || ENV["ALLOW_DEV_POPULATE"] == "1" end + + # warning: this method is very expensive and shouldn't be called in places + # where performance matters. it's meant to be called manually (e.g. in the + # rails console) when dealing with an emergency that requires invalidating + # theme cache + def self.clear_all_theme_cache! + ThemeField.force_recompilation! + Theme.all.each(&:update_javascript_cache!) + Theme.expire_site_cache! + end end diff --git a/spec/lib/discourse_spec.rb b/spec/lib/discourse_spec.rb index b419814331c..cd3a85e6520 100644 --- a/spec/lib/discourse_spec.rb +++ b/spec/lib/discourse_spec.rb @@ -472,4 +472,128 @@ describe Discourse do end end + context ".clear_all_theme_cache!" do + before do + setup_s3 + SiteSetting.s3_cdn_url = "https://s3.cdn.com/gg" + stub_s3_store + end + + let!(:theme) { Fabricate(:theme) } + let!(:upload) { Fabricate(:s3_image_upload) } + let!(:upload_theme_field) do + Fabricate( + :theme_field, + theme: theme, + upload: upload, + type_id: ThemeField.types[:theme_upload_var], + target_id: Theme.targets[:common], + name: "imajee", + value: "", + ) + end + let!(:basic_html_field) do + Fabricate( + :theme_field, + theme: theme, + type_id: ThemeField.types[:html], + target_id: Theme.targets[:common], + name: "head_tag", + value: <<~HTML + + HTML + ) + end + let!(:js_field) do + Fabricate( + :theme_field, + theme: theme, + type_id: ThemeField.types[:js], + target_id: Theme.targets[:extra_js], + name: "somefile.js", + value: <<~JS + console.log(settings.uploads.imajee); + JS + ) + end + let!(:scss_field) do + Fabricate( + :theme_field, + theme: theme, + type_id: ThemeField.types[:scss], + target_id: Theme.targets[:common], + name: "scss", + value: <<~SCSS + .something { background: url($imajee); } + SCSS + ) + end + + it "invalidates all JS and CSS caches" do + Stylesheet::Manager.clear_theme_cache! + + old_upload_url = Discourse.store.cdn_url(upload.url) + + head_tag_script = Nokogiri::HTML5.fragment( + Theme.lookup_field(theme.id, :desktop, "head_tag") + ).css('script').first + head_tag_js = JavascriptCache.find_by(digest: head_tag_script[:src][/\h{40}/]).content + expect(head_tag_js).to include(old_upload_url) + + js_file_script = Nokogiri::HTML5.fragment( + Theme.lookup_field(theme.id, :extra_js, nil) + ).css('script').first + file_js = JavascriptCache.find_by(digest: js_file_script[:src][/\h{40}/]).content + expect(file_js).to include(old_upload_url) + + css_link_tag = Nokogiri::HTML5.fragment( + Stylesheet::Manager.new(theme_id: theme.id).stylesheet_link_tag(:desktop_theme, 'all') + ).css('link').first + css = StylesheetCache.find_by(digest: css_link_tag[:href][/\h{40}/]).content + expect(css).to include("url(#{old_upload_url})") + + SiteSetting.s3_cdn_url = "https://new.s3.cdn.com/gg" + new_upload_url = Discourse.store.cdn_url(upload.url) + + head_tag_script = Nokogiri::HTML5.fragment( + Theme.lookup_field(theme.id, :desktop, "head_tag") + ).css('script').first + head_tag_js = JavascriptCache.find_by(digest: head_tag_script[:src][/\h{40}/]).content + expect(head_tag_js).to include(old_upload_url) + + js_file_script = Nokogiri::HTML5.fragment( + Theme.lookup_field(theme.id, :extra_js, nil) + ).css('script').first + file_js = JavascriptCache.find_by(digest: js_file_script[:src][/\h{40}/]).content + expect(file_js).to include(old_upload_url) + + css_link_tag = Nokogiri::HTML5.fragment( + Stylesheet::Manager.new(theme_id: theme.id).stylesheet_link_tag(:desktop_theme, 'all') + ).css('link').first + css = StylesheetCache.find_by(digest: css_link_tag[:href][/\h{40}/]).content + expect(css).to include("url(#{old_upload_url})") + + Discourse.clear_all_theme_cache! + + head_tag_script = Nokogiri::HTML5.fragment( + Theme.lookup_field(theme.id, :desktop, "head_tag") + ).css('script').first + head_tag_js = JavascriptCache.find_by(digest: head_tag_script[:src][/\h{40}/]).content + expect(head_tag_js).to include(new_upload_url) + + js_file_script = Nokogiri::HTML5.fragment( + Theme.lookup_field(theme.id, :extra_js, nil) + ).css('script').first + file_js = JavascriptCache.find_by(digest: js_file_script[:src][/\h{40}/]).content + expect(file_js).to include(new_upload_url) + + css_link_tag = Nokogiri::HTML5.fragment( + Stylesheet::Manager.new(theme_id: theme.id).stylesheet_link_tag(:desktop_theme, 'all') + ).css('link').first + css = StylesheetCache.find_by(digest: css_link_tag[:href][/\h{40}/]).content + expect(css).to include("url(#{new_upload_url})") + end + end end