FIX/FEATURE: don't blow up when can't reach theme's repo, show problem themes on dashboard
This commit is contained in:
parent
ca28548762
commit
c7d81e2682
|
@ -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
|
||||||
|
|
|
@ -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}`;
|
||||||
},
|
},
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
#
|
#
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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 ■■■■"
|
censored_words: "Words that will be automatically replaced with ■■■■"
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddLastErrorTextToRemoteThemes < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
add_column :remote_themes, :last_error_text, :text
|
||||||
|
end
|
||||||
|
end
|
|
@ -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
|
||||||
|
|
|
@ -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< Theme</a></li></ul>"
|
||||||
|
)
|
||||||
|
|
||||||
|
remote.update!(last_error_text: nil)
|
||||||
|
expect(dashboard_data.out_of_date_themes).to eq(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue