FIX/FEATURE: don't blow up when can't reach theme's repo, show problem themes on dashboard

This commit is contained in:
OsamaSayegh 2018-09-08 16:24:11 +03:00 committed by Sam
parent ca28548762
commit c7d81e2682
14 changed files with 139 additions and 22 deletions

View File

@ -1,7 +1,4 @@
import { import { default as computed } from "ember-addons/ember-computed-decorators";
default as computed,
observes
} from "ember-addons/ember-computed-decorators";
import { url } from "discourse/lib/computed"; import { url } from "discourse/lib/computed";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import showModal from "discourse/lib/show-modal"; import showModal from "discourse/lib/show-modal";
@ -27,7 +24,7 @@ export default Ember.Controller.extend({
}, },
@computed("model.editedFields") @computed("model.editedFields")
editedFieldsFormatted(fields) { editedFieldsFormatted() {
const descriptions = []; const descriptions = [];
["common", "desktop", "mobile"].forEach(target => { ["common", "desktop", "mobile"].forEach(target => {
const fields = this.editedFieldsForTarget(target); const fields = this.editedFieldsForTarget(target);
@ -96,6 +93,12 @@ export default Ember.Controller.extend({
hasSettings(settings) { hasSettings(settings) {
return settings.length > 0; return settings.length > 0;
}, },
@computed("model.remoteError", "updatingRemote")
showRemoteError(errorMessage, updating) {
return errorMessage && !updating;
},
editedFieldsForTarget(target) { editedFieldsForTarget(target) {
return this.get("model.editedFields").filter( return this.get("model.editedFields").filter(
field => field.target === target field => field.target === target

View File

@ -54,6 +54,13 @@ const Theme = RestModel.extend({
); );
}, },
@computed("remote_theme.last_error_text")
remoteError(errorText) {
if (errorText && errorText.length > 0) {
return errorText;
}
},
getKey(field) { getKey(field) {
return `${field.target} ${field.name}`; return `${field.target} ${field.name}`;
}, },

View File

@ -17,7 +17,7 @@ const externalResources = [
]; ];
export default Ember.Route.extend({ export default Ember.Route.extend({
setupController(controller, model) { setupController(controller) {
this._super(...arguments); this._super(...arguments);
this.controllerFor("adminCustomizeThemes").set("editingTheme", false); this.controllerFor("adminCustomizeThemes").set("editingTheme", false);
controller.set("externalResources", externalResources); controller.set("externalResources", externalResources);

View File

@ -87,10 +87,20 @@
</a> </a>
{{/if}} {{/if}}
{{else}} {{else}}
{{i18n 'admin.customize.theme.up_to_date'}} {{format-date model.remote_theme.updated_at leaveAgo="true"}} {{#unless showRemoteError}}
{{i18n 'admin.customize.theme.up_to_date'}} {{format-date model.remote_theme.updated_at leaveAgo="true"}}
{{/unless}}
{{/if}} {{/if}}
{{/if}} {{/if}}
</span> </span>
{{#if showRemoteError}}
<div class="error-message">
{{d-icon "exclamation-triangle"}} {{I18n "admin.customize.theme.repo_unreachable"}}
</div>
<div class="raw-error">
<code>{{model.remoteError}}</code>
</div>
{{/if}}
{{/if}} {{/if}}
</div> </div>

View File

@ -88,6 +88,17 @@
.admin-container { .admin-container {
padding: 0; padding: 0;
} }
.error-message {
margin-top: 5px;
margin-bottom: 5px;
.fa {
color: $danger;
}
}
.raw-error {
background-color: $primary-very-low;
padding: 5px;
}
} }
.admin-customize { .admin-customize {

View File

@ -102,7 +102,7 @@ class AdminDashboardData
:failing_emails_check, :failing_emails_check,
:subfolder_ends_in_slash_check, :subfolder_ends_in_slash_check,
:pop3_polling_configuration, :email_polling_errored_recently, :pop3_polling_configuration, :email_polling_errored_recently,
:out_of_date_themes :out_of_date_themes, :unreachable_themes
add_problem_check do add_problem_check do
sidekiq_check || queue_size_check sidekiq_check || queue_size_check
@ -252,11 +252,24 @@ class AdminDashboardData
old_themes = RemoteTheme.out_of_date_themes old_themes = RemoteTheme.out_of_date_themes
return unless old_themes.present? return unless old_themes.present?
html = old_themes.map do |name, id| themes_html_format(old_themes, "dashboard.out_of_date_themes")
end
def unreachable_themes
themes = RemoteTheme.unreachable_themes
return unless themes.present?
themes_html_format(themes, "dashboard.unreachable_themes")
end
private
def themes_html_format(themes, i18n_key)
html = themes.map do |name, id|
"<li><a href=\"/admin/customize/themes/#{id}\">#{CGI.escapeHTML(name)}</a></li>" "<li><a href=\"/admin/customize/themes/#{id}\">#{CGI.escapeHTML(name)}</a></li>"
end.join("\n") end.join("\n")
message = I18n.t("dashboard.out_of_date_themes") message = I18n.t(i18n_key)
message += "<ul>#{html}</ul>" message += "<ul>#{html}</ul>"
message message
end end

View File

@ -10,6 +10,9 @@ class RemoteTheme < ActiveRecord::Base
GITHUB_SSH_REGEXP = /^git@github\.com:/ GITHUB_SSH_REGEXP = /^git@github\.com:/
has_one :theme has_one :theme
scope :joined_remotes, -> {
joins("JOIN themes ON themes.remote_theme_id = remote_themes.id").where.not(remote_url: "")
}
def self.update_tgz_theme(filename, user: Discourse.system_user) def self.update_tgz_theme(filename, user: Discourse.system_user)
importer = ThemeStore::TgzImporter.new(filename) importer = ThemeStore::TgzImporter.new(filename)
@ -61,17 +64,24 @@ class RemoteTheme < ActiveRecord::Base
end end
def self.out_of_date_themes def self.out_of_date_themes
self.joins("JOIN themes ON themes.remote_theme_id = remote_themes.id") self.joined_remotes.where("commits_behind > 0 OR remote_version <> local_version")
.where.not(remote_url: "")
.where("commits_behind > 0 OR remote_version <> local_version")
.pluck("themes.name", "themes.id") .pluck("themes.name", "themes.id")
end end
def self.unreachable_themes
self.joined_remotes.where("last_error_text IS NOT NULL").pluck("themes.name", "themes.id")
end
def update_remote_version def update_remote_version
importer = ThemeStore::GitImporter.new(remote_url, private_key: private_key) importer = ThemeStore::GitImporter.new(remote_url, private_key: private_key)
importer.import! begin
self.updated_at = Time.zone.now importer.import!
self.remote_version, self.commits_behind = importer.commits_since(local_version) rescue ThemeStore::GitImporter::ImportFailed => err
self.last_error_text = err.message
else
self.updated_at = Time.zone.now
self.remote_version, self.commits_behind = importer.commits_since(local_version)
end
end end
def update_from_remote(importer = nil, skip_update: false) def update_from_remote(importer = nil, skip_update: false)
@ -81,7 +91,12 @@ class RemoteTheme < ActiveRecord::Base
unless importer unless importer
cleanup = true cleanup = true
importer = ThemeStore::GitImporter.new(remote_url, private_key: private_key) importer = ThemeStore::GitImporter.new(remote_url, private_key: private_key)
importer.import! begin
importer.import!
rescue ThemeStore::GitImporter::ImportFailed => err
self.last_error_text = err.message
return self
end
end end
theme_info = JSON.parse(importer["about.json"]) theme_info = JSON.parse(importer["about.json"])
@ -225,4 +240,5 @@ end
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# private_key :text # private_key :text
# last_error_text :text
# #

View File

@ -47,7 +47,7 @@ end
class RemoteThemeSerializer < ApplicationSerializer class RemoteThemeSerializer < ApplicationSerializer
attributes :id, :remote_url, :remote_version, :local_version, :about_url, attributes :id, :remote_url, :remote_version, :local_version, :about_url,
:license_url, :commits_behind, :remote_updated_at, :updated_at, :license_url, :commits_behind, :remote_updated_at, :updated_at,
:github_diff_link :github_diff_link, :last_error_text
# wow, AMS has some pretty nutty logic where it tries to find the path here # wow, AMS has some pretty nutty logic where it tries to find the path here
# from action dispatch, tell it not to # from action dispatch, tell it not to

View File

@ -3287,6 +3287,7 @@ en:
one: "Theme is 1 commit behind!" one: "Theme is 1 commit behind!"
other: "Theme is {{count}} commits behind!" other: "Theme is {{count}} commits behind!"
compare_commits: "(See new commits)" compare_commits: "(See new commits)"
repo_unreachable: "Couldn't contact the Git repository of this theme. Error message:"
scss: scss:
text: "CSS" text: "CSS"
title: "Enter custom CSS, we accept all valid CSS and SCSS styles" title: "Enter custom CSS, we accept all valid CSS and SCSS styles"

View File

@ -1118,6 +1118,7 @@ en:
poll_pop3_auth_error: "Connection to the POP3 server is failing with an authentication error. Please check your <a href='/admin/site_settings/category/email'>POP3 settings</a>." poll_pop3_auth_error: "Connection to the POP3 server is failing with an authentication error. Please check your <a href='/admin/site_settings/category/email'>POP3 settings</a>."
force_https_warning: "Your website is using SSL. But `<a href='/admin/site_settings/category/all_results?filter=force_https'>force_https</a>` is not yet enabled in your site settings." force_https_warning: "Your website is using SSL. But `<a href='/admin/site_settings/category/all_results?filter=force_https'>force_https</a>` is not yet enabled in your site settings."
out_of_date_themes: "Updates are available for the following themes:" out_of_date_themes: "Updates are available for the following themes:"
unreachable_themes: "We were unable to check for updates on the following themes:"
site_settings: site_settings:
censored_words: "Words that will be automatically replaced with &#9632;&#9632;&#9632;&#9632;" censored_words: "Words that will be automatically replaced with &#9632;&#9632;&#9632;&#9632;"

View File

@ -0,0 +1,5 @@
class AddLastErrorTextToRemoteThemes < ActiveRecord::Migration[5.2]
def change
add_column :remote_themes, :last_error_text, :text
end
end

View File

@ -2,6 +2,7 @@ module ThemeStore; end
class ThemeStore::GitImporter class ThemeStore::GitImporter
class ImportFailed < StandardError; end
attr_reader :url attr_reader :url
def initialize(url, private_key: nil) def initialize(url, private_key: nil)
@ -65,7 +66,11 @@ class ThemeStore::GitImporter
protected protected
def import_public! def import_public!
Discourse::Utils.execute_command("git", "clone", @url, @temp_folder) begin
Discourse::Utils.execute_command("git", "clone", @url, @temp_folder)
rescue => err
raise ImportFailed.new(err.message)
end
end end
def import_private! def import_private!
@ -77,9 +82,13 @@ class ThemeStore::GitImporter
FileUtils.chmod(0600, 'id_rsa') FileUtils.chmod(0600, 'id_rsa')
end end
Discourse::Utils.execute_command({ begin
'GIT_SSH_COMMAND' => "ssh -i #{ssh_folder}/id_rsa -o StrictHostKeyChecking=no" Discourse::Utils.execute_command({
}, "git", "clone", @url, @temp_folder) 'GIT_SSH_COMMAND' => "ssh -i #{ssh_folder}/id_rsa -o StrictHostKeyChecking=no"
}, "git", "clone", @url, @temp_folder)
rescue => err
raise ImportFailed.new(err.message)
end
ensure ensure
FileUtils.rm_rf ssh_folder FileUtils.rm_rf ssh_folder
end end

View File

@ -351,4 +351,19 @@ describe AdminDashboardData do
expect(dashboard_data.out_of_date_themes).to eq(nil) expect(dashboard_data.out_of_date_themes).to eq(nil)
end end
end end
describe '#unreachable_themes' do
let(:remote) { RemoteTheme.create!(remote_url: "https://github.com/org/testtheme", last_error_text: "can't reach repo :'(") }
let!(:theme) { Fabricate(:theme, remote_theme: remote, name: "Test< Theme") }
it "outputs correctly formatted html" do
dashboard_data = described_class.new
expect(dashboard_data.unreachable_themes).to eq(
I18n.t("dashboard.unreachable_themes") + "<ul><li><a href=\"/admin/customize/themes/#{theme.id}\">Test&lt; Theme</a></li></ul>"
)
remote.update!(last_error_text: nil)
expect(dashboard_data.out_of_date_themes).to eq(nil)
end
end
end end

View File

@ -187,6 +187,20 @@ describe RemoteTheme do
end end
end end
context ".joined_remotes" do
it "finds records that are associated with themes" do
github_repo
gitlab_repo
expect(RemoteTheme.joined_remotes).to eq([])
Fabricate(:theme, remote_theme: github_repo)
expect(RemoteTheme.joined_remotes).to eq([github_repo])
Fabricate(:theme, remote_theme: gitlab_repo)
expect(RemoteTheme.joined_remotes).to contain_exactly(github_repo, gitlab_repo)
end
end
context ".out_of_date_themes" do context ".out_of_date_themes" do
let(:remote) { RemoteTheme.create!(remote_url: "https://github.com/org/testtheme") } let(:remote) { RemoteTheme.create!(remote_url: "https://github.com/org/testtheme") }
let!(:theme) { Fabricate(:theme, remote_theme: remote) } let!(:theme) { Fabricate(:theme, remote_theme: remote) }
@ -199,4 +213,16 @@ describe RemoteTheme do
expect(described_class.out_of_date_themes).to eq([]) expect(described_class.out_of_date_themes).to eq([])
end end
end end
context ".unreachable_themes" do
let(:remote) { RemoteTheme.create!(remote_url: "https://github.com/org/testtheme", last_error_text: "can't contact this repo :(") }
let!(:theme) { Fabricate(:theme, remote_theme: remote) }
it "finds out of date themes" do
expect(described_class.unreachable_themes).to eq([[theme.name, theme.id]])
remote.update!(last_error_text: nil)
expect(described_class.unreachable_themes).to eq([])
end
end
end end