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

View File

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

View File

@ -87,10 +87,20 @@
</a>
{{/if}}
{{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}}
</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}}
</div>

View File

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

View File

@ -102,7 +102,7 @@ class AdminDashboardData
:failing_emails_check,
:subfolder_ends_in_slash_check,
:pop3_polling_configuration, :email_polling_errored_recently,
:out_of_date_themes
:out_of_date_themes, :unreachable_themes
add_problem_check do
sidekiq_check || queue_size_check
@ -252,11 +252,24 @@ class AdminDashboardData
old_themes = RemoteTheme.out_of_date_themes
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>"
end.join("\n")
message = I18n.t("dashboard.out_of_date_themes")
message = I18n.t(i18n_key)
message += "<ul>#{html}</ul>"
message
end

View File

@ -10,6 +10,9 @@ class RemoteTheme < ActiveRecord::Base
GITHUB_SSH_REGEXP = /^git@github\.com:/
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)
importer = ThemeStore::TgzImporter.new(filename)
@ -61,17 +64,24 @@ class RemoteTheme < ActiveRecord::Base
end
def self.out_of_date_themes
self.joins("JOIN themes ON themes.remote_theme_id = remote_themes.id")
.where.not(remote_url: "")
.where("commits_behind > 0 OR remote_version <> local_version")
self.joined_remotes.where("commits_behind > 0 OR remote_version <> local_version")
.pluck("themes.name", "themes.id")
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
importer = ThemeStore::GitImporter.new(remote_url, private_key: private_key)
importer.import!
self.updated_at = Time.zone.now
self.remote_version, self.commits_behind = importer.commits_since(local_version)
begin
importer.import!
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
def update_from_remote(importer = nil, skip_update: false)
@ -81,7 +91,12 @@ class RemoteTheme < ActiveRecord::Base
unless importer
cleanup = true
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
theme_info = JSON.parse(importer["about.json"])
@ -225,4 +240,5 @@ end
# created_at :datetime not null
# updated_at :datetime not null
# private_key :text
# last_error_text :text
#

View File

@ -47,7 +47,7 @@ end
class RemoteThemeSerializer < ApplicationSerializer
attributes :id, :remote_url, :remote_version, :local_version, :about_url,
: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
# from action dispatch, tell it not to

View File

@ -3287,6 +3287,7 @@ en:
one: "Theme is 1 commit behind!"
other: "Theme is {{count}} commits behind!"
compare_commits: "(See new commits)"
repo_unreachable: "Couldn't contact the Git repository of this theme. Error message:"
scss:
text: "CSS"
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>."
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:"
unreachable_themes: "We were unable to check for updates on the following themes:"
site_settings:
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 ImportFailed < StandardError; end
attr_reader :url
def initialize(url, private_key: nil)
@ -65,7 +66,11 @@ class ThemeStore::GitImporter
protected
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
def import_private!
@ -77,9 +82,13 @@ class ThemeStore::GitImporter
FileUtils.chmod(0600, 'id_rsa')
end
Discourse::Utils.execute_command({
'GIT_SSH_COMMAND' => "ssh -i #{ssh_folder}/id_rsa -o StrictHostKeyChecking=no"
}, "git", "clone", @url, @temp_folder)
begin
Discourse::Utils.execute_command({
'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
FileUtils.rm_rf ssh_folder
end

View File

@ -351,4 +351,19 @@ describe AdminDashboardData do
expect(dashboard_data.out_of_date_themes).to eq(nil)
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

View File

@ -187,6 +187,20 @@ describe RemoteTheme do
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
let(:remote) { RemoteTheme.create!(remote_url: "https://github.com/org/testtheme") }
let!(:theme) { Fabricate(:theme, remote_theme: remote) }
@ -199,4 +213,16 @@ describe RemoteTheme do
expect(described_class.out_of_date_themes).to eq([])
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