DEV: Add `skip_migrations` param when importing remote theme (#25218)
Why this change? Importing theme with the `bundle` params is used mainly by `discourse_theme` CLI in the development environment. However, we do not want migrations to automatically run in the development environment and instead want the developer to be intentional about running theme migrations. As such, this commit adds support for a `skip_migrations` param when importing a theme with the `bundle` params. This commit also adds a `migrated` attribute for migrations theme fields to indicate whether a migrations theme field has been migrated or not.
This commit is contained in:
parent
30bea5c7c2
commit
59839e428f
|
@ -142,15 +142,19 @@ class Admin::ThemesController < Admin::AdminController
|
||||||
bundle = params[:bundle] || params[:theme]
|
bundle = params[:bundle] || params[:theme]
|
||||||
theme_id = params[:theme_id]
|
theme_id = params[:theme_id]
|
||||||
update_components = params[:components]
|
update_components = params[:components]
|
||||||
|
run_migrations = !params[:skip_migrations]
|
||||||
|
|
||||||
begin
|
begin
|
||||||
@theme =
|
@theme =
|
||||||
RemoteTheme.update_zipped_theme(
|
RemoteTheme.update_zipped_theme(
|
||||||
bundle.path,
|
bundle.path,
|
||||||
bundle.original_filename,
|
bundle.original_filename,
|
||||||
user: theme_user,
|
user: theme_user,
|
||||||
theme_id: theme_id,
|
theme_id:,
|
||||||
update_components: update_components,
|
update_components:,
|
||||||
|
run_migrations:,
|
||||||
)
|
)
|
||||||
|
|
||||||
log_theme_change(nil, @theme)
|
log_theme_change(nil, @theme)
|
||||||
render json: @theme, status: :created
|
render json: @theme, status: :created
|
||||||
rescue RemoteTheme::ImportError => e
|
rescue RemoteTheme::ImportError => e
|
||||||
|
|
|
@ -70,13 +70,15 @@ class RemoteTheme < ActiveRecord::Base
|
||||||
original_filename,
|
original_filename,
|
||||||
user: Discourse.system_user,
|
user: Discourse.system_user,
|
||||||
theme_id: nil,
|
theme_id: nil,
|
||||||
update_components: nil
|
update_components: nil,
|
||||||
|
run_migrations: true
|
||||||
)
|
)
|
||||||
update_theme(
|
update_theme(
|
||||||
ThemeStore::ZipImporter.new(filename, original_filename),
|
ThemeStore::ZipImporter.new(filename, original_filename),
|
||||||
user:,
|
user:,
|
||||||
theme_id:,
|
theme_id:,
|
||||||
update_components:,
|
update_components:,
|
||||||
|
run_migrations:,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -91,7 +93,8 @@ class RemoteTheme < ActiveRecord::Base
|
||||||
importer,
|
importer,
|
||||||
user: Discourse.system_user,
|
user: Discourse.system_user,
|
||||||
theme_id: nil,
|
theme_id: nil,
|
||||||
update_components: nil
|
update_components: nil,
|
||||||
|
run_migrations: true
|
||||||
)
|
)
|
||||||
importer.import!
|
importer.import!
|
||||||
|
|
||||||
|
@ -112,8 +115,14 @@ class RemoteTheme < ActiveRecord::Base
|
||||||
remote_theme.remote_url = ""
|
remote_theme.remote_url = ""
|
||||||
|
|
||||||
do_update_child_components = false
|
do_update_child_components = false
|
||||||
|
|
||||||
theme.transaction do
|
theme.transaction do
|
||||||
remote_theme.update_from_remote(importer, skip_update: true, already_in_transaction: true)
|
remote_theme.update_from_remote(
|
||||||
|
importer,
|
||||||
|
skip_update: true,
|
||||||
|
already_in_transaction: true,
|
||||||
|
run_migrations:,
|
||||||
|
)
|
||||||
|
|
||||||
if existing && update_components.present? && update_components != "none"
|
if existing && update_components.present? && update_components != "none"
|
||||||
child_components = child_components.map { |url| ThemeStore::GitImporter.new(url.strip).url }
|
child_components = child_components.map { |url| ThemeStore::GitImporter.new(url.strip).url }
|
||||||
|
@ -216,7 +225,8 @@ class RemoteTheme < ActiveRecord::Base
|
||||||
importer = nil,
|
importer = nil,
|
||||||
skip_update: false,
|
skip_update: false,
|
||||||
raise_if_theme_save_fails: true,
|
raise_if_theme_save_fails: true,
|
||||||
already_in_transaction: false
|
already_in_transaction: false,
|
||||||
|
run_migrations: true
|
||||||
)
|
)
|
||||||
cleanup = false
|
cleanup = false
|
||||||
|
|
||||||
|
@ -356,12 +366,14 @@ class RemoteTheme < ActiveRecord::Base
|
||||||
update_theme_color_schemes(theme, theme_info["color_schemes"]) unless theme.component
|
update_theme_color_schemes(theme, theme_info["color_schemes"]) unless theme.component
|
||||||
|
|
||||||
self.save!
|
self.save!
|
||||||
|
|
||||||
if raise_if_theme_save_fails
|
if raise_if_theme_save_fails
|
||||||
theme.save!
|
theme.save!
|
||||||
else
|
else
|
||||||
raise ActiveRecord::Rollback if !theme.save
|
raise ActiveRecord::Rollback if !theme.save
|
||||||
end
|
end
|
||||||
theme.migrate_settings(start_transaction: false)
|
|
||||||
|
theme.migrate_settings(start_transaction: false) if run_migrations
|
||||||
end
|
end
|
||||||
|
|
||||||
if already_in_transaction
|
if already_in_transaction
|
||||||
|
|
|
@ -86,7 +86,7 @@ class Theme < ActiveRecord::Base
|
||||||
:user,
|
:user,
|
||||||
:color_scheme,
|
:color_scheme,
|
||||||
:theme_translation_overrides,
|
:theme_translation_overrides,
|
||||||
theme_fields: :upload,
|
theme_fields: %i[upload theme_settings_migration],
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -130,6 +130,7 @@ class Theme < ActiveRecord::Base
|
||||||
|
|
||||||
remove_from_cache!
|
remove_from_cache!
|
||||||
ColorScheme.hex_cache.clear
|
ColorScheme.hex_cache.clear
|
||||||
|
|
||||||
notify_theme_change(with_scheme: notify_with_scheme)
|
notify_theme_change(with_scheme: notify_with_scheme)
|
||||||
|
|
||||||
if theme_setting_requests_refresh
|
if theme_setting_requests_refresh
|
||||||
|
@ -647,13 +648,14 @@ class Theme < ActiveRecord::Base
|
||||||
def settings
|
def settings
|
||||||
field = settings_field
|
field = settings_field
|
||||||
return [] unless field && field.error.nil?
|
return [] unless field && field.error.nil?
|
||||||
|
|
||||||
settings = []
|
settings = []
|
||||||
|
|
||||||
ThemeSettingsParser
|
ThemeSettingsParser
|
||||||
.new(field)
|
.new(field)
|
||||||
.load do |name, default, type, opts|
|
.load do |name, default, type, opts|
|
||||||
settings << ThemeSettingsManager.create(name, default, type, self, opts)
|
settings << ThemeSettingsManager.create(name, default, type, self, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
settings
|
settings
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -847,6 +849,7 @@ class Theme < ActiveRecord::Base
|
||||||
self.theme_settings.destroy_all
|
self.theme_settings.destroy_all
|
||||||
|
|
||||||
final_result = results.last
|
final_result = results.last
|
||||||
|
|
||||||
final_result[:settings_after].each do |key, val|
|
final_result[:settings_after].each do |key, val|
|
||||||
self.update_setting(key.to_sym, val)
|
self.update_setting(key.to_sym, val)
|
||||||
rescue Discourse::NotFound
|
rescue Discourse::NotFound
|
||||||
|
@ -874,7 +877,8 @@ class Theme < ActiveRecord::Base
|
||||||
record.calculate_diff(res[:settings_before], res[:settings_after])
|
record.calculate_diff(res[:settings_before], res[:settings_after])
|
||||||
record.save!
|
record.save!
|
||||||
end
|
end
|
||||||
self.save!
|
|
||||||
|
self.reload
|
||||||
end
|
end
|
||||||
|
|
||||||
if start_transaction
|
if start_transaction
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ThemeFieldSerializer < ApplicationSerializer
|
class ThemeFieldSerializer < ApplicationSerializer
|
||||||
attributes :name, :target, :value, :error, :type_id, :upload_id, :url, :filename
|
attributes :name, :target, :value, :error, :type_id, :upload_id, :url, :filename, :migrated
|
||||||
|
|
||||||
def include_url?
|
def include_url?
|
||||||
object.upload
|
object.upload
|
||||||
|
@ -34,4 +34,12 @@ class ThemeFieldSerializer < ApplicationSerializer
|
||||||
def include_error?
|
def include_error?
|
||||||
object.error.present?
|
object.error.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def migrated
|
||||||
|
!!object.theme_settings_migration
|
||||||
|
end
|
||||||
|
|
||||||
|
def include_migrated?
|
||||||
|
target == "migrations"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Binary file not shown.
3
spec/fixtures/themes/discourse-test-theme/migrations/settings/0001-some-migration.js
vendored
Normal file
3
spec/fixtures/themes/discourse-test-theme/migrations/settings/0001-some-migration.js
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export default function migrate(settings) {
|
||||||
|
return settings;
|
||||||
|
}
|
|
@ -494,7 +494,7 @@ RSpec.describe RemoteTheme do
|
||||||
theme = RemoteTheme.import_theme_from_directory(theme_dir)
|
theme = RemoteTheme.import_theme_from_directory(theme_dir)
|
||||||
|
|
||||||
expect(theme.name).to eq("Header Icons")
|
expect(theme.name).to eq("Header Icons")
|
||||||
expect(theme.theme_fields.count).to eq(5)
|
expect(theme.theme_fields.count).to eq(6)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -243,6 +243,7 @@ RSpec.describe Admin::ThemesController do
|
||||||
}
|
}
|
||||||
JS
|
JS
|
||||||
)
|
)
|
||||||
|
|
||||||
repo_url = MockGitImporter.register("https://example.com/initial_repo.git", repo_path)
|
repo_url = MockGitImporter.register("https://example.com/initial_repo.git", repo_path)
|
||||||
|
|
||||||
post "/admin/themes/import.json", params: { remote: repo_url }
|
post "/admin/themes/import.json", params: { remote: repo_url }
|
||||||
|
@ -336,7 +337,7 @@ RSpec.describe Admin::ThemesController do
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
|
||||||
expect(json["theme"]["name"]).to eq("Header Icons")
|
expect(json["theme"]["name"]).to eq("Header Icons")
|
||||||
expect(json["theme"]["theme_fields"].length).to eq(5)
|
expect(json["theme"]["theme_fields"].length).to eq(6)
|
||||||
expect(json["theme"]["auto_update"]).to eq(false)
|
expect(json["theme"]["auto_update"]).to eq(false)
|
||||||
expect(UserHistory.where(action: UserHistory.actions[:change_theme]).count).to eq(1)
|
expect(UserHistory.where(action: UserHistory.actions[:change_theme]).count).to eq(1)
|
||||||
end
|
end
|
||||||
|
@ -347,30 +348,45 @@ RSpec.describe Admin::ThemesController do
|
||||||
other_existing_theme = Fabricate(:theme, name: "Some other name")
|
other_existing_theme = Fabricate(:theme, name: "Some other name")
|
||||||
|
|
||||||
messages =
|
messages =
|
||||||
MessageBus.track_publish do
|
MessageBus.track_publish("/file-change") do
|
||||||
expect do
|
expect do
|
||||||
post "/admin/themes/import.json",
|
post "/admin/themes/import.json",
|
||||||
params: {
|
params: {
|
||||||
bundle: theme_archive,
|
bundle: theme_archive,
|
||||||
theme_id: other_existing_theme.id,
|
theme_id: other_existing_theme.id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expect(response.status).to eq(201)
|
||||||
end.not_to change { Theme.count }
|
end.not_to change { Theme.count }
|
||||||
end
|
end
|
||||||
expect(response.status).to eq(201)
|
|
||||||
json = response.parsed_body
|
json = response.parsed_body
|
||||||
|
|
||||||
# Ensure only one refresh message is sent.
|
# Ensure only one refresh message is sent.
|
||||||
# More than 1 is wasteful, and can trigger unusual race conditions in the client
|
# More than 1 is wasteful, and can trigger unusual race conditions in the client
|
||||||
# If this test fails, it probably means `theme.save` is being called twice - check any 'autosave' relations
|
# If this test fails, it probably means `theme.save` is being called twice - check any 'autosave' relations
|
||||||
file_change_messages = messages.filter { |m| m[:channel] == "/file-change" }
|
expect(messages.count).to eq(1)
|
||||||
expect(file_change_messages.count).to eq(1)
|
|
||||||
|
|
||||||
expect(json["theme"]["name"]).to eq("Some other name")
|
expect(json["theme"]["name"]).to eq("Some other name")
|
||||||
expect(json["theme"]["id"]).to eq(other_existing_theme.id)
|
expect(json["theme"]["id"]).to eq(other_existing_theme.id)
|
||||||
expect(json["theme"]["theme_fields"].length).to eq(5)
|
expect(json["theme"]["theme_fields"].length).to eq(6)
|
||||||
expect(UserHistory.where(action: UserHistory.actions[:change_theme]).count).to eq(1)
|
expect(UserHistory.where(action: UserHistory.actions[:change_theme]).count).to eq(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "does not run migrations when importing a theme from an archive and `skip_settings_migrations` params is present" do
|
||||||
|
other_existing_theme = Fabricate(:theme, name: "Some other name")
|
||||||
|
|
||||||
|
post "/admin/themes/import.json",
|
||||||
|
params: {
|
||||||
|
bundle: theme_archive,
|
||||||
|
theme_id: other_existing_theme.id,
|
||||||
|
skip_migrations: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response.status).to eq(201)
|
||||||
|
expect(other_existing_theme.theme_settings_migrations.exists?).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
it "creates a new theme when id specified as nil" do
|
it "creates a new theme when id specified as nil" do
|
||||||
# Used by theme CLI
|
# Used by theme CLI
|
||||||
existing_theme = Fabricate(:theme, name: "Header Icons")
|
existing_theme = Fabricate(:theme, name: "Header Icons")
|
||||||
|
@ -383,7 +399,7 @@ RSpec.describe Admin::ThemesController do
|
||||||
|
|
||||||
expect(json["theme"]["name"]).to eq("Header Icons")
|
expect(json["theme"]["name"]).to eq("Header Icons")
|
||||||
expect(json["theme"]["id"]).not_to eq(existing_theme.id)
|
expect(json["theme"]["id"]).not_to eq(existing_theme.id)
|
||||||
expect(json["theme"]["theme_fields"].length).to eq(5)
|
expect(json["theme"]["theme_fields"].length).to eq(6)
|
||||||
expect(json["theme"]["auto_update"]).to eq(false)
|
expect(json["theme"]["auto_update"]).to eq(false)
|
||||||
expect(UserHistory.where(action: UserHistory.actions[:change_theme]).count).to eq(1)
|
expect(UserHistory.where(action: UserHistory.actions[:change_theme]).count).to eq(1)
|
||||||
end
|
end
|
||||||
|
@ -423,6 +439,12 @@ RSpec.describe Admin::ThemesController do
|
||||||
theme.set_field(target: :common, name: :scss, value: ".body{color: black;}")
|
theme.set_field(target: :common, name: :scss, value: ".body{color: black;}")
|
||||||
theme.set_field(target: :desktop, name: :after_header, value: "<b>test</b>")
|
theme.set_field(target: :desktop, name: :after_header, value: "<b>test</b>")
|
||||||
|
|
||||||
|
theme.set_field(
|
||||||
|
target: :migrations,
|
||||||
|
name: "0001-some-migration",
|
||||||
|
value: "export default function migrate(settings) { return settings; }",
|
||||||
|
)
|
||||||
|
|
||||||
theme.remote_theme =
|
theme.remote_theme =
|
||||||
RemoteTheme.new(
|
RemoteTheme.new(
|
||||||
remote_url: "awesome.git",
|
remote_url: "awesome.git",
|
||||||
|
@ -444,7 +466,14 @@ RSpec.describe Admin::ThemesController do
|
||||||
|
|
||||||
expect(json["extras"]["color_schemes"].length).to eq(1)
|
expect(json["extras"]["color_schemes"].length).to eq(1)
|
||||||
theme_json = json["themes"].find { |t| t["id"] == theme.id }
|
theme_json = json["themes"].find { |t| t["id"] == theme.id }
|
||||||
expect(theme_json["theme_fields"].length).to eq(2)
|
expect(theme_json["theme_fields"].length).to eq(3)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
theme_json["theme_fields"].find { |theme_field| theme_field["target"] == "migrations" }[
|
||||||
|
"migrated"
|
||||||
|
],
|
||||||
|
).to eq(false)
|
||||||
|
|
||||||
expect(theme_json["remote_theme"]["remote_version"]).to eq("7")
|
expect(theme_json["remote_theme"]["remote_version"]).to eq("7")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue