diff --git a/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js b/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js index f527051f512..2418c785e0b 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js @@ -167,6 +167,15 @@ export default Controller.extend({ return errorMessage && !updating; }, + @discourseComputed( + "model.remote_theme.remote_url", + "model.remote_theme.local_version", + "model.remote_theme.commits_behind" + ) + finishInstall(remoteUrl, localVersion, commitsBehind) { + return remoteUrl && !localVersion && !commitsBehind; + }, + editedFieldsForTarget(target) { return this.get("model.editedFields").filter( (field) => field.target === target diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-install-theme.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-install-theme.js index df91acd0943..02907601804 100644 --- a/app/assets/javascripts/admin/addon/controllers/modals/admin-install-theme.js +++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-install-theme.js @@ -119,8 +119,12 @@ export default Controller.extend(ModalFunctionality, { } }, - @discourseComputed("selection") - submitLabel(selection) { + @discourseComputed("selection", "themeCannotBeInstalled") + submitLabel(selection, themeCannotBeInstalled) { + if (themeCannotBeInstalled) { + return "admin.customize.theme.create_placeholder"; + } + return `admin.customize.theme.${ selection === "create" ? "create" : "install" }`; @@ -216,6 +220,12 @@ export default Controller.extend(ModalFunctionality, { } } + // User knows that theme cannot be installed, but they want to continue + // to force install it. + if (this.themeCannotBeInstalled) { + options.data["force"] = true; + } + if (this.get("model.user_id")) { // Used by theme-creator options.data["user_id"] = this.get("model.user_id"); @@ -231,7 +241,16 @@ export default Controller.extend(ModalFunctionality, { .then(() => { this.setProperties({ privateKey: null, publicKey: null }); }) - .catch(popupAjaxError) + .catch((error) => { + if (!this.privateKey || this.themeCannotBeInstalled) { + return popupAjaxError(error); + } + + this.set( + "themeCannotBeInstalled", + I18n.t("admin.customize.theme.force_install") + ); + }) .finally(() => this.set("loading", false)); }, }, diff --git a/app/assets/javascripts/admin/addon/templates/customize-themes-show.hbs b/app/assets/javascripts/admin/addon/templates/customize-themes-show.hbs index 31690ff15f4..4fe285d33eb 100644 --- a/app/assets/javascripts/admin/addon/templates/customize-themes-show.hbs +++ b/app/assets/javascripts/admin/addon/templates/customize-themes-show.hbs @@ -15,281 +15,312 @@
{{error}}
{{/each}} - {{#unless this.model.supported}} -
- {{i18n "admin.customize.theme.required_version.error"}} - {{#if this.model.remote_theme.minimum_discourse_version}} - {{i18n "admin.customize.theme.required_version.minimum" version=this.model.remote_theme.minimum_discourse_version}} - {{/if}} - {{#if this.model.remote_theme.maximum_discourse_version}} - {{i18n "admin.customize.theme.required_version.maximum" version=this.model.remote_theme.maximum_discourse_version}} - {{/if}} -
- {{/unless}} - - {{#unless this.model.enabled}} -
- {{#if this.model.disabled_by}} - {{i18n "admin.customize.theme.disabled_by"}} - - {{avatar this.model.disabled_by imageSize="tiny"}} - {{this.model.disabled_by.username}} - - {{format-date this.model.disabled_at leaveAgo="true"}} + {{#if this.finishInstall}} +
+ {{#if this.sourceIsHttp}} + {{i18n "admin.customize.theme.source_url"}}{{d-icon "link"}} {{else}} - {{i18n "admin.customize.theme.disabled"}} - {{/if}} - -
- {{/unless}} - -
- {{#if this.model.remote_theme}} - {{#if this.model.remote_theme.remote_url}} - {{#if this.sourceIsHttp}} - {{i18n "admin.customize.theme.source_url"}}{{d-icon "link"}} - {{else}} -
- {{this.model.remote_theme.remote_url}} - {{#if this.model.remote_theme.branch}} - ({{this.model.remote_theme.branch}}) - {{/if}} -
- {{/if}} - {{/if}} - {{#if this.model.remote_theme.about_url}} - {{i18n "admin.customize.theme.about_theme"}}{{d-icon "link"}} - {{/if}} - {{#if this.model.remote_theme.license_url}} - {{i18n "admin.customize.theme.license"}}{{d-icon "link"}} - {{/if}} - - {{#if this.model.description}} - {{this.model.description}} - {{/if}} - - {{#if this.model.remote_theme.authors}}{{i18n "admin.customize.theme.authors"}} {{this.model.remote_theme.authors}}{{/if}} - {{#if this.model.remote_theme.theme_version}}{{i18n "admin.customize.theme.version"}} {{this.model.remote_theme.theme_version}}{{/if}} - -
- {{#if this.model.remote_theme.is_git}} -
- {{html-safe (i18n "admin.customize.theme.remote_theme_edits" repoURL=this.remoteThemeLink)}} -
- - {{#if this.showRemoteError}} -
- {{d-icon "exclamation-triangle"}} {{i18n "admin.customize.theme.repo_unreachable"}} -
-
- {{this.model.remoteError}} -
+
+ {{this.model.remote_theme.remote_url}} + {{#if this.model.remote_theme.branch}} + ({{this.model.remote_theme.branch}}) {{/if}} +
+ {{/if}} - {{#if this.model.remote_theme.commits_behind}} - - {{else}} - - {{/if}} + {{#if this.showRemoteError}} +
+ {{d-icon "exclamation-triangle"}} {{i18n "admin.customize.theme.repo_unreachable"}} +
+
+ {{this.model.remoteError}} +
+ {{/if}} - - {{#if this.updatingRemote}} - {{i18n "admin.customize.theme.updating"}} - {{else}} - {{#if this.model.remote_theme.commits_behind}} - {{#if this.hasOverwrittenHistory}} - {{i18n "admin.customize.theme.has_overwritten_history"}} - {{else}} - {{i18n "admin.customize.theme.commits_behind" count=this.model.remote_theme.commits_behind}} - {{/if}} - {{#if this.model.remote_theme.github_diff_link}} - - {{i18n "admin.customize.theme.compare_commits"}} - - {{/if}} - {{else}} - {{#unless this.showRemoteError}} - {{i18n "admin.customize.theme.up_to_date"}} {{format-date this.model.remote_theme.updated_at leaveAgo="true"}} - {{/unless}} - {{/if}} - {{/if}} - - {{else}} - - {{d-icon "info-circle"}} {{i18n "admin.customize.theme.imported_from_archive"}} - - {{/if}} -
- {{else}} - {{i18n "admin.customize.theme.creator"}} - - - {{format-username this.model.user.username}} - + + + + + {{i18n "admin.customize.theme.last_attempt"}} {{format-date this.model.remote_theme.updated_at leaveAgo="true"}} - {{/if}} -
- - {{#if this.showCheckboxes}} -
- {{#unless this.model.component}} - - - {{/unless}} - {{#if this.model.remote_theme}} - - {{/if}}
- {{/if}} - - {{#unless this.model.component}} - -
-
- {{i18n "admin.customize.theme.color_scheme"}} -
-
- - -
{{i18n "admin.customize.theme.color_scheme_select"}}
-
-
- {{#if this.colorSchemeChanged}} - - - {{/if}} -
-
-
- {{/unless}} - - {{#if this.parentThemes}} -
-
{{i18n "admin.customize.theme.component_of"}}
- -
- {{/if}} - - {{#if this.model.component}} - -
- -
-
{{else}} - -
- + {{#unless this.model.supported}} +
+ {{i18n "admin.customize.theme.required_version.error"}} + {{#if this.model.remote_theme.minimum_discourse_version}} + {{i18n "admin.customize.theme.required_version.minimum" version=this.model.remote_theme.minimum_discourse_version}} + {{/if}} + {{#if this.model.remote_theme.maximum_discourse_version}} + {{i18n "admin.customize.theme.required_version.maximum" version=this.model.remote_theme.maximum_discourse_version}} + {{/if}}
- - {{/if}} + {{/unless}} - {{#unless this.model.remote_theme.is_git}} -
-
{{i18n "admin.customize.theme.css_html"}}
- {{#if this.model.hasEditedFields}} -
{{i18n "admin.customize.theme.custom_sections"}}
-
    - {{#each this.editedFieldsFormatted as |field|}} -
  • {{field}}
  • - {{/each}} -
- {{else}} -
- {{i18n "admin.customize.theme.edit_css_html_help"}} -
- {{/if}} + {{#unless this.model.enabled}} +
+ {{#if this.model.disabled_by}} + {{i18n "admin.customize.theme.disabled_by"}} + + {{avatar this.model.disabled_by imageSize="tiny"}} + {{this.model.disabled_by.username}} + + {{format-date this.model.disabled_at leaveAgo="true"}} + {{else}} + {{i18n "admin.customize.theme.disabled"}} + {{/if}} + +
+ {{/unless}} - -
- -
-
{{i18n "admin.customize.theme.uploads"}}
- {{#if this.model.uploads}} -
    - {{#each this.model.uploads as |upload|}} -
  • - ${{upload.name}}: {{upload.filename}} - - - -
  • - {{/each}} -
- {{else}} -
{{i18n "admin.customize.theme.no_uploads"}}
- {{/if}} - -
- {{/unless}} - - {{#if this.extraFiles.length}} -
-
{{i18n "admin.customize.theme.extra_files"}}
-
- - {{#if this.model.remote_theme}} - {{i18n "admin.customize.theme.extra_files_remote"}} + + {{/if}} + {{#if this.model.remote_theme.about_url}} + {{i18n "admin.customize.theme.about_theme"}}{{d-icon "link"}} + {{/if}} + {{#if this.model.remote_theme.license_url}} + {{i18n "admin.customize.theme.license"}}{{d-icon "link"}} + {{/if}} + + {{#if this.model.description}} + {{this.model.description}} + {{/if}} + + {{#if this.model.remote_theme.authors}}{{i18n "admin.customize.theme.authors"}} {{this.model.remote_theme.authors}}{{/if}} + {{#if this.model.remote_theme.theme_version}}{{i18n "admin.customize.theme.version"}} {{this.model.remote_theme.theme_version}}{{/if}} + +
+ {{#if this.model.remote_theme.is_git}} +
+ {{html-safe (i18n "admin.customize.theme.remote_theme_edits" repoURL=this.remoteThemeLink)}} +
+ + {{#if this.showRemoteError}} +
+ {{d-icon "exclamation-triangle"}} {{i18n "admin.customize.theme.repo_unreachable"}} +
+
+ {{this.model.remoteError}} +
+ {{/if}} + + {{#if this.model.remote_theme.commits_behind}} + + {{else}} + + {{/if}} + + + {{#if this.updatingRemote}} + {{i18n "admin.customize.theme.updating"}} + {{else}} + {{#if this.model.remote_theme.commits_behind}} + {{#if this.hasOverwrittenHistory}} + {{i18n "admin.customize.theme.has_overwritten_history"}} + {{else}} + {{i18n "admin.customize.theme.commits_behind" count=this.model.remote_theme.commits_behind}} + {{/if}} + {{#if this.model.remote_theme.github_diff_link}} + + {{i18n "admin.customize.theme.compare_commits"}} + + {{/if}} + {{else}} + {{#unless this.showRemoteError}} + {{i18n "admin.customize.theme.up_to_date"}} {{format-date this.model.remote_theme.updated_at leaveAgo="true"}} + {{/unless}} + {{/if}} + {{/if}} + + {{else}} + + {{d-icon "info-circle"}} {{i18n "admin.customize.theme.imported_from_archive"}} + + {{/if}} +
+ {{else}} + {{i18n "admin.customize.theme.creator"}} + + + {{format-username this.model.user.username}} + + + {{/if}} +
+ + {{#if this.showCheckboxes}} +
+ {{#unless this.model.component}} + + + {{/unless}} + {{#if this.model.remote_theme}} + + {{/if}} +
+ {{/if}} + + {{#unless this.model.component}} + +
+
+ {{i18n "admin.customize.theme.color_scheme"}} +
+
+ + +
{{i18n "admin.customize.theme.color_scheme_select"}}
+
+
+ {{#if this.colorSchemeChanged}} + + + {{/if}} +
+
+
+ {{/unless}} + + {{#if this.parentThemes}} +
+
{{i18n "admin.customize.theme.component_of"}}
    - {{#each this.extraFiles as |extraFile|}} -
  • {{extraFile.name}}
  • + {{#each this.parentThemes as |theme|}} +
  • {{theme.name}}
  • {{/each}}
- -
- {{/if}} - - {{#if this.hasSettings}} -
-
{{i18n "admin.customize.theme.theme_settings"}}
- - {{#each this.settings as |setting|}} - - {{/each}} - -
- {{/if}} - - {{#if this.hasTranslations}} -
-
{{i18n "admin.customize.theme.theme_translations"}}
- - {{#each this.translations as |translation|}} - - {{/each}} - -
- {{/if}} - - {{/if}} {{#if this.model.component}} - {{#if this.model.enabled}} - - {{else}} - - {{/if}} + +
+ +
+
+ {{else}} + +
+ +
+
{{/if}} - + {{#unless this.model.remote_theme.is_git}} +
+
{{i18n "admin.customize.theme.css_html"}}
+ {{#if this.model.hasEditedFields}} +
{{i18n "admin.customize.theme.custom_sections"}}
+
    + {{#each this.editedFieldsFormatted as |field|}} +
  • {{field}}
  • + {{/each}} +
+ {{else}} +
+ {{i18n "admin.customize.theme.edit_css_html_help"}} +
+ {{/if}} -
+ +
+ +
+
{{i18n "admin.customize.theme.uploads"}}
+ {{#if this.model.uploads}} +
    + {{#each this.model.uploads as |upload|}} +
  • + ${{upload.name}}: {{upload.filename}} + + + +
  • + {{/each}} +
+ {{else}} +
{{i18n "admin.customize.theme.no_uploads"}}
+ {{/if}} + +
+ {{/unless}} + + {{#if this.extraFiles.length}} +
+
{{i18n "admin.customize.theme.extra_files"}}
+
+ + {{#if this.model.remote_theme}} + {{i18n "admin.customize.theme.extra_files_remote"}} + {{else}} + {{i18n "admin.customize.theme.extra_files_upload"}} + {{/if}} + +
    + {{#each this.extraFiles as |extraFile|}} +
  • {{extraFile.name}}
  • + {{/each}} +
+
+
+ {{/if}} + + {{#if this.hasSettings}} +
+
{{i18n "admin.customize.theme.theme_settings"}}
+ + {{#each this.settings as |setting|}} + + {{/each}} + +
+ {{/if}} + + {{#if this.hasTranslations}} +
+
{{i18n "admin.customize.theme.theme_translations"}}
+ + {{#each this.translations as |translation|}} + + {{/each}} + +
+ {{/if}} + +
+ + {{d-icon "desktop"}}{{i18n "admin.customize.theme.preview"}} + {{d-icon "download"}} {{i18n "admin.export_json.button_text"}} + + {{#if this.showConvert}} + + {{/if}} + + {{#if this.model.component}} + {{#if this.model.enabled}} + + {{else}} + + {{/if}} + {{/if}} + + + +
+ {{/if}}
diff --git a/app/assets/javascripts/admin/addon/templates/modal/admin-install-theme.hbs b/app/assets/javascripts/admin/addon/templates/modal/admin-install-theme.hbs index 0918c6aee4e..cebb9a6121f 100644 --- a/app/assets/javascripts/admin/addon/templates/modal/admin-install-theme.hbs +++ b/app/assets/javascripts/admin/addon/templates/modal/admin-install-theme.hbs @@ -111,7 +111,12 @@ ⚠️ {{this.duplicateRemoteThemeWarning}} {{/if}} - + {{#if this.themeCannotBeInstalled}} +
+ ⚠️ {{this.themeCannotBeInstalled}} +
+ {{/if}} + {{/unless}} diff --git a/app/assets/javascripts/discourse/tests/acceptance/themes-test.js b/app/assets/javascripts/discourse/tests/acceptance/themes-test.js new file mode 100644 index 00000000000..40595569dcc --- /dev/null +++ b/app/assets/javascripts/discourse/tests/acceptance/themes-test.js @@ -0,0 +1,243 @@ +import { click, fillIn, visit } from "@ember/test-helpers"; +import { + acceptance, + exists, + query, +} from "discourse/tests/helpers/qunit-helpers"; +import I18n from "I18n"; +import { test } from "qunit"; + +acceptance("Theme", function (needs) { + needs.user(); + + needs.pretender((server, helper) => { + server.get("/admin/themes", () => { + return helper.response(200, { + themes: [ + { + id: 42, + name: "discourse-incomplete-theme", + created_at: "2022-01-01T12:00:00.000Z", + updated_at: "2022-01-01T12:00:00.000Z", + component: false, + color_scheme: null, + color_scheme_id: null, + user_selectable: false, + auto_update: true, + remote_theme_id: 42, + settings: [], + supported: true, + description: null, + enabled: true, + user: { + id: 1, + username: "foo", + name: null, + avatar_template: + "/letter_avatar_proxy/v4/letter/f/3be4f8/{size}.png", + title: "Tester", + }, + theme_fields: [], + child_themes: [], + parent_themes: [], + remote_theme: { + id: 42, + remote_url: + "git@github.com:discourse/discourse-incomplete-theme.git", + remote_version: null, + local_version: null, + commits_behind: null, + branch: null, + remote_updated_at: null, + updated_at: "2022-01-01T12:00:00.000Z", + last_error_text: null, + is_git: true, + license_url: null, + about_url: null, + authors: null, + theme_version: null, + minimum_discourse_version: null, + maximum_discourse_version: null, + }, + translations: [], + }, + ], + }); + }); + + server.post("/admin/themes/import", (request) => { + const data = helper.parsePostData(request.requestBody); + + if (!data.force) { + return helper.response(422, { + errors: [ + "Error cloning git repository, access is denied or repository is not found", + ], + }); + } + + return helper.response(201, { + theme: { + id: 42, + name: "discourse-inexistent-theme", + created_at: "2022-01-01T12:00:00.000Z", + updated_at: "2022-01-01T12:00:00.000Z", + component: false, + color_scheme: null, + color_scheme_id: null, + user_selectable: false, + auto_update: true, + remote_theme_id: 42, + settings: [], + supported: true, + description: null, + enabled: true, + user: { + id: 1, + username: "foo", + name: null, + avatar_template: + "/letter_avatar_proxy/v4/letter/f/3be4f8/{size}.png", + }, + theme_fields: [], + child_themes: [], + parent_themes: [], + remote_theme: { + id: 42, + remote_url: + "git@github.com:discourse/discourse-inexistent-theme.git", + remote_version: null, + local_version: null, + commits_behind: null, + branch: null, + remote_updated_at: null, + updated_at: "2022-01-01T12:00:00.000Z", + last_error_text: null, + is_git: true, + license_url: null, + about_url: null, + authors: null, + theme_version: null, + minimum_discourse_version: null, + maximum_discourse_version: null, + }, + translations: [], + }, + }); + }); + + server.put("/admin/themes/42", () => { + return helper.response(200, { + theme: { + id: 42, + name: "discourse-complete-theme", + created_at: "2022-01-01T12:00:00.000Z", + updated_at: "2022-01-01T12:00:00.000Z", + component: false, + color_scheme: null, + color_scheme_id: null, + user_selectable: false, + auto_update: true, + remote_theme_id: 42, + settings: [], + supported: true, + description: null, + enabled: true, + user: { + id: 1, + username: "foo", + name: null, + avatar_template: + "/letter_avatar_proxy/v4/letter/f/3be4f8/{size}.png", + }, + theme_fields: [], + child_themes: [], + parent_themes: [], + remote_theme: { + id: 42, + remote_url: + "git@github.com:discourse-org/discourse-incomplete-theme.git", + remote_version: "0000000000000000000000000000000000000000", + local_version: "0000000000000000000000000000000000000000", + commits_behind: 0, + branch: null, + remote_updated_at: "2022-01-01T12:00:30.000Z", + updated_at: "2022-01-01T12:00:30.000Z", + last_error_text: null, + is_git: true, + license_url: "URL", + about_url: "URL", + authors: null, + theme_version: null, + minimum_discourse_version: null, + maximum_discourse_version: null, + }, + translations: [], + }, + }); + }); + }); + + test("can force install themes", async function (assert) { + await visit("/admin/customize/themes"); + + await click(".themes-list .create-actions button"); + await click(".install-theme-items #remote"); + await fillIn( + ".install-theme-content .repo input", + "git@github.com:discourse/discourse-inexistent-theme.git" + ); + await click(".install-theme-content button.advanced-repo"); + await click(".install-theme-content .check-private input"); + + assert.notOk( + exists(".admin-install-theme-modal .modal-footer .install-theme-warning"), + "no Git warning is displayed" + ); + + await click(".admin-install-theme-modal .modal-footer .btn-primary"); + assert.ok( + exists(".admin-install-theme-modal .modal-footer .install-theme-warning"), + "Git warning is displayed" + ); + + await click(".admin-install-theme-modal .modal-footer .btn-danger"); + + assert.notOk( + exists(".admin-install-theme-modal:visible"), + "modal is closed" + ); + }); + + test("can continue installation", async function (assert) { + await visit("/admin/customize/themes"); + + await click(".themes-list-container .themes-list-item"); + assert.ok( + query(".control-unit .status-message").innerText.includes( + I18n.t("admin.customize.theme.last_attempt") + ), + "it says that theme is not completely installed" + ); + + await click(".control-unit .btn-primary.finish-install"); + + assert.equal( + query(".show-current-style .title span").innerText, + "discourse-complete-theme", + "it updates theme title" + ); + + assert.notOk( + query(".metadata.control-unit").innerText.includes( + I18n.t("admin.customize.theme.last_attempt") + ), + "it does not say that theme is not completely installed" + ); + + assert.notOk( + query(".control-unit .btn-primary.finish-install"), + "it does not show finish install button" + ); + }); +}); diff --git a/app/assets/stylesheets/common/admin/customize.scss b/app/assets/stylesheets/common/admin/customize.scss index 8109dcf7f48..2c876a8abf7 100644 --- a/app/assets/stylesheets/common/admin/customize.scss +++ b/app/assets/stylesheets/common/admin/customize.scss @@ -27,9 +27,12 @@ .admin-container { padding: 0; } - .error-message { + .error-message, + .raw-error { margin-top: 5px; margin-bottom: 5px; + } + .error-message { .fa { color: var(--danger); } diff --git a/app/controllers/admin/themes_controller.rb b/app/controllers/admin/themes_controller.rb index 428f8ff5795..50749db4777 100644 --- a/app/controllers/admin/themes_controller.rb +++ b/app/controllers/admin/themes_controller.rb @@ -104,7 +104,23 @@ class Admin::ThemesController < Admin::AdminController @theme = RemoteTheme.import_theme(remote, theme_user, private_key: params[:private_key], branch: branch) render json: @theme, status: :created rescue RemoteTheme::ImportError => e - render_json_error e.message + if params[:force] + theme_name = params[:remote].gsub(/.git$/, "").split("/").last + + remote_theme = RemoteTheme.new + remote_theme.private_key = params[:private_key] + remote_theme.branch = params[:branch] ? params[:branch] : nil + remote_theme.remote_url = params[:remote] + remote_theme.save! + + @theme = Theme.new(user_id: theme_user&.id || -1, name: theme_name) + @theme.remote_theme = remote_theme + @theme.save! + + render json: @theme, status: :created + else + render_json_error e.message + end end elsif params[:bundle] || (params[:theme] && THEME_CONTENT_TYPES.include?(params[:theme].content_type)) diff --git a/app/models/remote_theme.rb b/app/models/remote_theme.rb index 21f6bef275b..f72f77c1bc7 100644 --- a/app/models/remote_theme.rb +++ b/app/models/remote_theme.rb @@ -171,6 +171,13 @@ class RemoteTheme < ActiveRecord::Base end end + # Update all theme attributes if this is just a placeholder + if self.remote_url.present? && !self.local_version && !self.commits_behind + self.theme.name = theme_info["name"] + self.theme.component = [true, "true"].include?(theme_info["component"]) + self.theme.child_components = theme_info["components"].presence || [] + end + METADATA_PROPERTIES.each do |property| self.public_send(:"#{property}=", theme_info[property.to_s]) end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 5365a990bc5..3b776812efb 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -4719,6 +4719,8 @@ en: import_web_advanced: "Advanced..." import_file_tip: ".tar.gz, .zip, or .dcstyle.json file containing theme" is_private: "Theme is in a private git repository" + finish_install: "Finish Theme Installation" + last_attempt: "Installation process did not finish, last attempted:" remote_branch: "Branch name (optional)" public_key: "Grant the following public key access to the repo:" public_key_note: "After entering a valid private repository URL above, an SSH key will be generated and displayed here." @@ -4729,6 +4731,8 @@ en: install_git_repo: "From a git repository" install_create: "Create new" duplicate_remote_theme: "The theme component “%{name}” is already installed, are you sure you want to install another copy?" + force_install: "The theme cannot be installed because the Git repository is inaccessible. Are you sure you want to continue installing it?" + create_placeholder: "Create Placeholder" about_theme: "About" license: "License" version: "Version:" diff --git a/spec/requests/admin/themes_controller_spec.rb b/spec/requests/admin/themes_controller_spec.rb index a3cf8cca71a..e42e67787a0 100644 --- a/spec/requests/admin/themes_controller_spec.rb +++ b/spec/requests/admin/themes_controller_spec.rb @@ -151,6 +151,25 @@ RSpec.describe Admin::ThemesController do expect(UserHistory.where(action: UserHistory.actions[:change_theme]).count).to eq(1) end + it 'can fail if theme is not accessible' do + post "/admin/themes/import.json", params: { + remote: 'git@github.com:discourse/discourse-inexistent-theme.git' + } + + expect(response.status).to eq(422) + expect(response.parsed_body["errors"]).to contain_exactly(I18n.t("themes.import_error.git")) + end + + it 'can force install theme' do + post "/admin/themes/import.json", params: { + remote: 'git@github.com:discourse/discourse-inexistent-theme.git', + force: true + } + + expect(response.status).to eq(201) + expect(response.parsed_body["theme"]["name"]).to eq("discourse-inexistent-theme") + end + it 'fails to import with an error if uploads are not allowed' do SiteSetting.theme_authorized_extensions = "nothing"