From 1318e0b288a2077b29ffa5f77ebecb8ffcdfc066 Mon Sep 17 00:00:00 2001 From: Ralph Rooding Date: Thu, 4 Jul 2019 20:33:05 +0200 Subject: [PATCH] FEATURE: Rake themes installer (#7848) * Delete remote_theme when deleting the theme * Install themes and theme components through rake * Removed unnecessary test --- app/models/theme.rb | 2 +- app/services/themes_install_task.rb | 54 +++++++++++++++++++ lib/tasks/themes.rake | 43 +++++++++++++++ spec/services/themes_spec.rb | 83 +++++++++++++++++++++++++++++ 4 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 app/services/themes_install_task.rb create mode 100644 lib/tasks/themes.rake create mode 100644 spec/services/themes_spec.rb diff --git a/app/models/theme.rb b/app/models/theme.rb index 81a347861c7db..798bb2e03fdc7 100644 --- a/app/models/theme.rb +++ b/app/models/theme.rb @@ -22,7 +22,7 @@ class Theme < ActiveRecord::Base has_many :child_themes, -> { order(:name) }, through: :child_theme_relation, source: :child_theme has_many :parent_themes, -> { order(:name) }, through: :parent_theme_relation, source: :parent_theme has_many :color_schemes - belongs_to :remote_theme, autosave: true + belongs_to :remote_theme, autosave: true, dependent: :destroy has_one :settings_field, -> { where(target_id: Theme.targets[:settings], name: "yaml") }, class_name: 'ThemeField' has_one :javascript_cache, dependent: :destroy diff --git a/app/services/themes_install_task.rb b/app/services/themes_install_task.rb new file mode 100644 index 0000000000000..1a3ac1bbcf3a5 --- /dev/null +++ b/app/services/themes_install_task.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +class ThemesInstallTask + def self.install(yml) + counts = { installed: 0, updated: 0, skipped: 0, errors: 0 } + log = [] + themes = YAML::load(yml) + themes.each do |theme| + name = theme[0] + val = theme[1] + installer = new(val) + + if installer.theme_exists? + log << "#{name}: is already installed" + counts[:skipped] += 1 + else + begin + installer.install + log << "#{name}: installed from #{installer.url}" + counts[:installed] += 1 + rescue RemoteTheme::ImportError, Discourse::InvalidParameters => err + log << "#{name}: #{err.message}" + counts[:errors] += 1 + end + end + end + + return log, counts + end + + attr_reader :url, :options + + def initialize(url_or_options = nil) + if url_or_options.is_a?(Hash) + @url = url_or_options.fetch("url") + @options = url_or_options + else + @url = url_or_options + @options = {} + end + end + + def theme_exists? + RemoteTheme + .where(remote_url: url) + .where(branch: options.fetch("branch", nil)) + .exists? + end + + def install + theme = RemoteTheme.import_theme(url, Discourse.system_user, private_key: options["private_key"], branch: options["branch"]) + theme.set_default! if options.fetch("default", false) + end +end diff --git a/lib/tasks/themes.rake b/lib/tasks/themes.rake new file mode 100644 index 0000000000000..bce21369d2516 --- /dev/null +++ b/lib/tasks/themes.rake @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'yaml' + +# == YAML file format +# +# 2 different formats are accepted: +# +# theme_name: https://github.com/example/theme.git +# +# theme_name: +# url: https://github.com/example/theme.git +# branch: abc +# private_key: ... +# default: true +# +# In the second form, only the url is required. +# +desc "Install themes & theme components" +task "themes:install" => :environment do + yml = (STDIN.tty?) ? '' : STDIN.read + if yml == '' + puts + puts "Please specify a themes yml file" + puts "Example: rake themes:install < themes.yml" + exit 1 + end + + log, counts = ThemesInstallTask.install(yml) + + puts log + + puts + puts "Results:" + puts " Installed: #{counts[:installed]}" + puts " Updated: #{counts[:updated]}" + puts " Skipped: #{counts[:skipped]}" + puts " Errors: #{counts[:errors]}" + + if counts[:errors] > 0 + exit 1 + end +end diff --git a/spec/services/themes_spec.rb b/spec/services/themes_spec.rb new file mode 100644 index 0000000000000..cabf942df1122 --- /dev/null +++ b/spec/services/themes_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe ThemesInstallTask do + + before do + Discourse::Application.load_tasks + end + + let(:github_repo) { 'https://github.com/example/theme.git' } + let(:branch) { 'master' } + + describe '.new' do + context 'with url' do + subject { described_class.new(github_repo) } + + it 'configures the url' do + expect(subject.url).to eq github_repo + end + + it 'initializes without options' do + expect(subject.options).to eq({}) + end + end + + context 'with options' do + subject { described_class.new(options) } + let(:options) { { 'url' => github_repo, 'branch' => branch } } + + it 'configures the url' do + expect(subject.url).to eq github_repo + end + + it 'initializes options' do + expect(subject.options).to eq("url" => github_repo, "branch" => branch) + end + end + end + + describe '#theme_exists?' do + let(:theme) { Fabricate(:theme) } + subject { described_class.new(options) } + + context 'without branch' do + let(:options) { github_repo } + + it 'returns true when a branchless theme exists' do + theme.create_remote_theme(remote_url: github_repo) + expect(subject.theme_exists?).to be true + end + + it 'returns false when the url exists but with a branch' do + theme.create_remote_theme(remote_url: github_repo, branch: branch) + expect(subject.theme_exists?).to be false + end + + it 'returns false when it doesnt exist' do + theme.create_remote_theme(remote_url: 'https://github.com/example/different_theme.git') + expect(subject.theme_exists?).to be false + end + end + + context 'with branch' do + let(:options) { { 'url' => github_repo, 'branch' => branch } } + + it 'returns false when a branchless theme exists' do + theme.create_remote_theme(remote_url: github_repo) + expect(subject.theme_exists?).to be false + end + + it 'returns true when the url exists with a branch' do + theme.create_remote_theme(remote_url: github_repo, branch: branch) + expect(subject.theme_exists?).to be true + end + + it 'returns false when it doesnt exist' do + theme.create_remote_theme(remote_url: 'https://github.com/example/different_theme.git') + expect(subject.theme_exists?).to be false + end + end + end +end