From e29c6b1504ccbd9d4c19c7d6598cdbe0d56d7f76 Mon Sep 17 00:00:00 2001 From: Vinoth Kannan Date: Tue, 9 Jul 2024 00:14:26 +0530 Subject: [PATCH] UX: improve UI of software update page & display more info. (#214) This PR will refresh the software update page so that self-hosters can more easily understand the plugins they have installed and which plugins need updates, and so that they are able to quickly and easily update them (or update everything). Co-authored-by: Martin Brennan --- .../components/docker-manager/repo-status.hbs | 62 --------- .../components/docker-manager/repo-status.js | 56 -------- .../discourse/components/repo-status.gjs | 121 ++++++++++++++++++ .../discourse/helpers/commit-url.js | 16 +++ .../javascripts/discourse/models/repo.js | 40 +++++- .../discourse/routes/update-show.js | 6 + .../discourse/templates/update-index.hbs | 44 ++++--- .../docker_manager/admin_controller.rb | 9 +- assets/stylesheets/common/docker-manager.scss | 64 ++++++++- config/locales/client.en.yml | 8 +- lib/docker_manager/upgrader.rb | 2 +- spec/system/admin_update_spec.rb | 25 ++++ .../system/page_objects/pages/admin_update.rb | 21 +++ .../components/repo-status-test.js | 32 ++--- 14 files changed, 341 insertions(+), 165 deletions(-) delete mode 100644 admin/assets/javascripts/discourse/components/docker-manager/repo-status.hbs delete mode 100644 admin/assets/javascripts/discourse/components/docker-manager/repo-status.js create mode 100644 admin/assets/javascripts/discourse/components/repo-status.gjs create mode 100644 admin/assets/javascripts/discourse/helpers/commit-url.js create mode 100644 spec/system/admin_update_spec.rb create mode 100644 spec/system/page_objects/pages/admin_update.rb diff --git a/admin/assets/javascripts/discourse/components/docker-manager/repo-status.hbs b/admin/assets/javascripts/discourse/components/docker-manager/repo-status.hbs deleted file mode 100644 index 3d9288d..0000000 --- a/admin/assets/javascripts/discourse/components/docker-manager/repo-status.hbs +++ /dev/null @@ -1,62 +0,0 @@ - - - {{#if this.officialRepoBadge}} - {{d-icon - this.officialRepoBadge - translatedTitle=this.officialRepoBadgeTitle - class="check-circle" - }} - {{/if}} - - - - {{@repo.name}} - - {{@repo.prettyVersion}} - - - - - {{#if @repo.checkingStatus}} - {{i18n "admin.docker.checking"}} - {{else if @repo.upToDate}} - {{i18n "admin.docker.up_to_date"}} - {{else}} -
-

{{i18n "admin.docker.new_version_available"}}

- -
    -
  • - {{i18n "admin.docker.latest_version"}} - - {{@repo.prettyLatestVersion}} - -
  • -
  • - {{i18n "admin.docker.last_updated"}} - {{#if @repo.latest.date}} - {{format-date @repo.latest.date}} - {{else}} - — - {{/if}} -
  • -
  • - {{new-commits - @repo.latest.commits_behind - @repo.version - @repo.latest.version - @repo.url - }} -
  • -
- - -
- {{/if}} - - \ No newline at end of file diff --git a/admin/assets/javascripts/discourse/components/docker-manager/repo-status.js b/admin/assets/javascripts/discourse/components/docker-manager/repo-status.js deleted file mode 100644 index a870cd9..0000000 --- a/admin/assets/javascripts/discourse/components/docker-manager/repo-status.js +++ /dev/null @@ -1,56 +0,0 @@ -import Component from "@glimmer/component"; -import { action } from "@ember/object"; -import { inject as service } from "@ember/service"; -import I18n from "I18n"; - -export default class RepoStatus extends Component { - @service router; - @service upgradeStore; - - get upgradeDisabled() { - // Allow to see the currently running update - if (this.args.upgradingRepo) { - return false; - } - - // Disable other buttons when an update is running - if (this.upgradeStore.running) { - return true; - } - - // docker_manager has to be updated before other plugins - return ( - !this.args.managerRepo.upToDate && - this.args.managerRepo !== this.args.repo - ); - } - - get officialRepoBadge() { - if (this.args.repo.fork) { - return "exclamation-circle"; - } else if (this.args.repo.official) { - return "check-circle"; - } - } - - get officialRepoBadgeTitle() { - if (this.args.repo.fork) { - return I18n.t("admin.docker.forked_plugin"); - } else if (this.args.repo.official) { - return I18n.t("admin.docker.official_plugin"); - } - } - - get upgradeButtonLabel() { - if (this.args.repo.upgrading) { - return I18n.t("admin.docker.updating"); - } else { - return I18n.t("admin.docker.update_action"); - } - } - - @action - upgrade() { - this.router.transitionTo("update.show", this.args.repo); - } -} diff --git a/admin/assets/javascripts/discourse/components/repo-status.gjs b/admin/assets/javascripts/discourse/components/repo-status.gjs new file mode 100644 index 0000000..b364d01 --- /dev/null +++ b/admin/assets/javascripts/discourse/components/repo-status.gjs @@ -0,0 +1,121 @@ +import Component from "@glimmer/component"; +import { action } from "@ember/object"; +import { inject as service } from "@ember/service"; +import DButton from "discourse/components/d-button"; +import FormatDate from "discourse/helpers/format-date"; +import icon from "discourse-common/helpers/d-icon"; +import i18n from "discourse-common/helpers/i18n"; +import I18n from "I18n"; +import CommitUrl from "../helpers/commit-url"; +import NewCommits from "../helpers/new-commits"; + +export default class RepoStatus extends Component { + @service router; + @service upgradeStore; + + get upgradeDisabled() { + // Allow to see the currently running update + if (this.args.upgradingRepo) { + return false; + } + + // Disable other buttons when an update is running + if (this.upgradeStore.running) { + return true; + } + + // docker_manager has to be updated before other plugins + return ( + !this.args.managerRepo.upToDate && + this.args.managerRepo !== this.args.repo + ); + } + + get upgradeButtonLabel() { + if (this.args.repo.upgrading) { + return I18n.t("admin.docker.updating"); + } else { + return I18n.t("admin.docker.update_action"); + } + } + + @action + upgrade() { + this.router.transitionTo("update.show", this.args.repo); + } + + +} diff --git a/admin/assets/javascripts/discourse/helpers/commit-url.js b/admin/assets/javascripts/discourse/helpers/commit-url.js new file mode 100644 index 0000000..802e213 --- /dev/null +++ b/admin/assets/javascripts/discourse/helpers/commit-url.js @@ -0,0 +1,16 @@ +import { htmlSafe } from "@ember/template"; + +export default function commitUrl(cssClass, version, prettyVersion, url) { + if (!prettyVersion) { + return ""; + } + + if (!url) { + return prettyVersion; + } + + const repoUrl = url.substr(0, url.search(/(\.git)?$/)); + const description = `${prettyVersion}`; + + return new htmlSafe(description); +} diff --git a/admin/assets/javascripts/discourse/models/repo.js b/admin/assets/javascripts/discourse/models/repo.js index c23d9bf..729d04e 100644 --- a/admin/assets/javascripts/discourse/models/repo.js +++ b/admin/assets/javascripts/discourse/models/repo.js @@ -1,6 +1,8 @@ -import { tracked } from "@glimmer/tracking"; +import { cached, tracked } from "@glimmer/tracking"; +import { capitalize } from "@ember/string"; import { TrackedObject } from "@ember-compat/tracked-built-ins"; import { ajax } from "discourse/lib/ajax"; +import AdminPlugin from "admin/models/admin-plugin"; let loaded = []; export let needsImageUpgrade = false; @@ -74,6 +76,7 @@ export default class Repo { @tracked checking = false; @tracked lastCheckedAt = null; @tracked latest = new TrackedObject({}); + @tracked plugin = null; // model attributes @tracked name = null; @@ -94,8 +97,12 @@ export default class Repo { } } + if (attributes.plugin) { + this.plugin = AdminPlugin.create(attributes.plugin); + } + for (const [key, value] of Object.entries(attributes)) { - if (key === "latest") { + if (["latest", "plugin"].includes(key)) { continue; } @@ -103,6 +110,31 @@ export default class Repo { } } + @cached + get nameTitleized() { + if (this.plugin) { + return this.plugin.nameTitleized; + } + + return capitalize(this.name); + } + + get linkUrl() { + if (this.plugin) { + return this.plugin.linkUrl; + } + + return this.url; + } + + get author() { + if (this.plugin) { + return this.plugin.author; + } + + return null; + } + get checkingStatus() { return this.unloaded || this.checking; } @@ -111,6 +143,10 @@ export default class Repo { return !this.upgrading && this.version === this.latest?.version; } + get hasNewVersion() { + return !this.checkingStatus && !this.upToDate; + } + get prettyVersion() { return this.pretty_version || this.version?.substring(0, 8); } diff --git a/admin/assets/javascripts/discourse/routes/update-show.js b/admin/assets/javascripts/discourse/routes/update-show.js index 7813ae7..a808069 100644 --- a/admin/assets/javascripts/discourse/routes/update-show.js +++ b/admin/assets/javascripts/discourse/routes/update-show.js @@ -4,6 +4,7 @@ import Repo from "../models/repo"; export default class UpgradeShow extends Route { @service upgradeStore; + @service router; model(params) { if (params.id === "all") { @@ -14,6 +15,11 @@ export default class UpgradeShow extends Route { } async afterModel(model) { + if (!model) { + this.router.replaceWith("/404"); + return; + } + if (Array.isArray(model)) { const repos = await Repo.findLatestAll(); diff --git a/admin/assets/javascripts/discourse/templates/update-index.hbs b/admin/assets/javascripts/discourse/templates/update-index.hbs index 3b0595a..30d14ea 100644 --- a/admin/assets/javascripts/discourse/templates/update-index.hbs +++ b/admin/assets/javascripts/discourse/templates/update-index.hbs @@ -1,4 +1,21 @@ -

{{i18n "admin.docker.update_title"}}

+
+

{{i18n "admin.docker.update_title"}}

+ {{#unless this.outdated}} + + {{#if this.allUpToDate}} + {{i18n "admin.docker.all_up_to_date"}} + {{else}} + {{i18n "admin.docker.update_all"}} + {{/if}} + + {{/unless}} +
{{#if this.outdated}}

{{i18n "admin.docker.outdated_image_header"}}

@@ -18,29 +35,18 @@

{{else}} - - +
- - - + + + + + {{#each this.model as |repo|}} - ` + hbs`` ); assert - .dom("span.current.commit-hash") + .dom("a.current.commit-hash") .hasText("v2.2.0.beta6 +98", "tag version is used when present"); assert - .dom("span.new.commit-hash") + .dom("a.new.commit-hash") .hasText("v2.2.0.beta6 +101", "tag version is used when present"); assert @@ -76,36 +76,32 @@ module("Integration | Component | RepoStatus", function (hooks) { await settled(); assert.strictEqual( - query("span.current.commit-hash").textContent.trim(), + query("a.current.commit-hash").textContent.trim(), "8f65e4f", "commit hash is used when tag version is absent" ); assert.strictEqual( - query("span.new.commit-hash").textContent.trim(), + query("a.new.commit-hash").textContent.trim(), "2b006c0", "commit hash is used when tag version is absent" ); }); - test("official check mark", async function (assert) { + test("official plugin", async function (assert) { const store = getOwner(this).lookup("service:store"); + repoProps.plugin = { name: "discourse", isOfficial: true }; this.set("repo", store.createRecord("repo", repoProps)); this.set("managerRepo", store.createRecord("repo", managerProps)); await render( - hbs`` + hbs`` ); - assert - .dom("svg.d-icon-check-circle") - .doesNotExist("green check is absent when not official"); - - this.repo.official = true; - await settled(); - - assert - .dom("svg.d-icon-check-circle") - .exists("green check is present when official"); + assert.strictEqual( + query("div.repo__author").textContent.trim(), + "By Discourse", + "shows plugin author" + ); }); test("update button", async function (assert) { @@ -114,7 +110,7 @@ module("Integration | Component | RepoStatus", function (hooks) { this.set("managerRepo", store.createRecord("repo", managerProps)); await render( - hbs`` + hbs`` ); assert
{{i18n "admin.docker.repository"}}{{i18n "admin.docker.status"}}{{i18n "admin.docker.repo.name"}}{{i18n "admin.docker.repo.commit_hash"}}{{i18n "admin.docker.repo.last_updated"}}{{i18n "admin.docker.repo.latest_version"}}{{i18n "admin.docker.repo.status"}}