UX: add gift emoji styling for new features (#24523)

When admin has unseen new feature, gift emoji is added to a link.

In addition, `/new-features` path was changed to `/whats-new`
This commit is contained in:
Krzysztof Kotlarek 2023-11-27 09:32:28 +11:00 committed by GitHub
parent 856ccb34e1
commit dc2a0854b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 64 additions and 62 deletions

View File

@ -13,7 +13,7 @@ export default class DashboardNewFeatures extends Component {
@bind @bind
loadNewFeatures() { loadNewFeatures() {
ajax("/admin/dashboard/new-features.json") ajax("/admin/dashboard/whats-new.json")
.then((json) => { .then((json) => {
this.newFeatures = json.new_features; this.newFeatures = json.new_features;
this.isLoaded = true; this.isLoaded = true;

View File

@ -91,6 +91,7 @@ export default class AdminDashboardController extends Controller {
if (versionChecks) { if (versionChecks) {
properties.versionCheck = VersionCheck.create(model.version_check); properties.versionCheck = VersionCheck.create(model.version_check);
} }
properties.hasUnseenFeatures = model.hasUnseenFeatures;
this.setProperties(properties); this.setProperties(properties);
}) })

View File

@ -11,7 +11,10 @@ export default class AdminDashboard extends EmberObject {
static fetch() { static fetch() {
return ajax("/admin/dashboard.json").then((json) => { return ajax("/admin/dashboard.json").then((json) => {
const model = AdminDashboard.create(); const model = AdminDashboard.create();
model.set("version_check", json.version_check); model.setProperties({
version_check: json.version_check,
hasUnseenFeatures: json.has_unseen_features,
});
return model; return model;
}); });

View File

@ -15,7 +15,7 @@ export default function () {
resetNamespace: true, resetNamespace: true,
}); });
this.route("admin.dashboardNewFeatures", { this.route("admin.dashboardNewFeatures", {
path: "/dashboard/new-features", path: "/dashboard/whats-new",
resetNamespace: true, resetNamespace: true,
}); });
}); });

View File

@ -53,6 +53,9 @@
{{#if this.isNewFeaturesTabVisible}} {{#if this.isNewFeaturesTabVisible}}
<li class="navigation-item new-features"> <li class="navigation-item new-features">
<LinkTo @route="admin.dashboardNewFeatures" class="navigation-link"> <LinkTo @route="admin.dashboardNewFeatures" class="navigation-link">
{{#if this.hasUnseenFeatures}}
{{replace-emoji ":gift:"}}
{{/if}}
{{i18n "admin.dashboard.new_features.title"}} {{i18n "admin.dashboard.new_features.title"}}
</LinkTo> </LinkTo>
</li> </li>

View File

@ -12,7 +12,7 @@ export default class extends NotificationTypeBase {
} }
get linkHref() { get linkHref() {
return getURL("/admin/dashboard/new-features"); return getURL("/admin/dashboard/whats-new");
} }
get icon() { get icon() {

View File

@ -142,6 +142,11 @@ acceptance("Dashboard", function (needs) {
await click(".dashboard .navigation-item.new-features .navigation-link"); await click(".dashboard .navigation-item.new-features .navigation-link");
assert.ok(
exists(
".dashboard .navigation-item.new-features .navigation-link .emoji[title='gift']"
)
);
assert.ok(exists(".dashboard-new-features")); assert.ok(exists(".dashboard-new-features"));
assert.ok(exists("img.admin-new-feature-item__screenshot")); assert.ok(exists("img.admin-new-feature-item__screenshot"));
}); });

View File

@ -1,5 +1,5 @@
export default { export default {
"/admin/dashboard/new-features.json": { "/admin/dashboard/whats-new.json": {
new_features: [ new_features: [
{ {
id: 1, id: 1,

View File

@ -1,5 +1,6 @@
export default { export default {
"/admin/dashboard.json": { "/admin/dashboard.json": {
updated_at: "2018-04-25T08:06:11.292Z", updated_at: "2018-04-25T08:06:11.292Z",
has_unseen_features: true
}, },
}; };

View File

@ -24,6 +24,10 @@
display: block; display: block;
font-weight: bold; font-weight: bold;
padding: 0.6em 1em 0.5em 1em; padding: 0.6em 1em 0.5em 1em;
.emoji {
margin-right: 0.5em;
}
} }
} }

View File

@ -4,8 +4,13 @@
font-size: var(--font-down-1); font-size: var(--font-down-1);
} }
.navigation a.navigation-link { .navigation a.navigation-link {
padding: 0.5em; padding: 0.4em;
font-size: var(--font-down-1); font-size: var(--font-down-1);
.emoji {
width: 1.3em;
height: 1.3em;
margin-right: 0;
}
} }
.dashboard-new-features .section-body { .dashboard-new-features .section-body {
grid-template-columns: none; grid-template-columns: none;

View File

@ -7,6 +7,7 @@ class Admin::DashboardController < Admin::StaffController
if SiteSetting.version_checks? if SiteSetting.version_checks?
data.merge!(version_check: DiscourseUpdates.check_version.as_json) data.merge!(version_check: DiscourseUpdates.check_version.as_json)
end end
data.merge!(has_unseen_features: DiscourseUpdates.has_unseen_features?(current_user.id))
render json: data render json: data
end end
@ -38,11 +39,15 @@ class Admin::DashboardController < Admin::StaffController
has_unseen_features: DiscourseUpdates.has_unseen_features?(current_user.id), has_unseen_features: DiscourseUpdates.has_unseen_features?(current_user.id),
release_notes_link: AdminDashboardGeneralData.fetch_cached_stats["release_notes_link"], release_notes_link: AdminDashboardGeneralData.fetch_cached_stats["release_notes_link"],
} }
mark_new_features_as_seen
render json: data render json: data
end end
private
def mark_new_features_as_seen def mark_new_features_as_seen
DiscourseUpdates.mark_new_features_as_seen(current_user.id) DiscourseUpdates.mark_new_features_as_seen(current_user.id)
render json: success_json
end end
end end

View File

@ -320,8 +320,7 @@ Discourse::Application.routes.draw do
get "dashboard/moderation" => "dashboard#moderation" get "dashboard/moderation" => "dashboard#moderation"
get "dashboard/security" => "dashboard#security" get "dashboard/security" => "dashboard#security"
get "dashboard/reports" => "dashboard#reports" get "dashboard/reports" => "dashboard#reports"
get "dashboard/new-features" => "dashboard#new_features" get "dashboard/whats-new" => "dashboard#new_features"
put "dashboard/mark-new-features-as-seen" => "dashboard#mark_new_features_as_seen"
resources :dashboard, only: [:index] do resources :dashboard, only: [:index] do
collection { get "problems" } collection { get "problems" }

View File

@ -164,7 +164,7 @@ RSpec.describe Admin::DashboardController do
before { sign_in(admin) } before { sign_in(admin) }
it "is empty by default" do it "is empty by default" do
get "/admin/dashboard/new-features.json" get "/admin/dashboard/whats-new.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
json = response.parsed_body json = response.parsed_body
expect(json["new_features"]).to eq(nil) expect(json["new_features"]).to eq(nil)
@ -172,7 +172,7 @@ RSpec.describe Admin::DashboardController do
it "fails gracefully for invalid JSON" do it "fails gracefully for invalid JSON" do
Discourse.redis.set("new_features", "INVALID JSON") Discourse.redis.set("new_features", "INVALID JSON")
get "/admin/dashboard/new-features.json" get "/admin/dashboard/whats-new.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
json = response.parsed_body json = response.parsed_body
expect(json["new_features"]).to eq(nil) expect(json["new_features"]).to eq(nil)
@ -181,7 +181,7 @@ RSpec.describe Admin::DashboardController do
it "includes new features when available" do it "includes new features when available" do
populate_new_features populate_new_features
get "/admin/dashboard/new-features.json" get "/admin/dashboard/whats-new.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
json = response.parsed_body json = response.parsed_body
@ -195,7 +195,7 @@ RSpec.describe Admin::DashboardController do
populate_new_features populate_new_features
DiscourseUpdates.mark_new_features_as_seen(admin.id) DiscourseUpdates.mark_new_features_as_seen(admin.id)
get "/admin/dashboard/new-features.json" get "/admin/dashboard/whats-new.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
json = response.parsed_body json = response.parsed_body
@ -209,7 +209,7 @@ RSpec.describe Admin::DashboardController do
expect(DiscourseUpdates.get_last_viewed_feature_date(admin.id)).to eq(nil) expect(DiscourseUpdates.get_last_viewed_feature_date(admin.id)).to eq(nil)
get "/admin/dashboard/new-features.json" get "/admin/dashboard/whats-new.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(DiscourseUpdates.get_last_viewed_feature_date(admin.id)).to be_within_one_second_of( expect(DiscourseUpdates.get_last_viewed_feature_date(admin.id)).to be_within_one_second_of(
date2, date2,
@ -218,15 +218,33 @@ RSpec.describe Admin::DashboardController do
date2 = 10.minutes.ago date2 = 10.minutes.ago
populate_new_features(date1, date2) populate_new_features(date1, date2)
get "/admin/dashboard/new-features.json" get "/admin/dashboard/whats-new.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(DiscourseUpdates.get_last_viewed_feature_date(admin.id)).to be_within_one_second_of( expect(DiscourseUpdates.get_last_viewed_feature_date(admin.id)).to be_within_one_second_of(
date2, date2,
) )
end end
it "marks new features as seen" do
date1 = 30.minutes.ago
date2 = 20.minutes.ago
populate_new_features(date1, date2)
expect(DiscourseUpdates.new_features_last_seen(admin.id)).to eq(nil)
expect(DiscourseUpdates.has_unseen_features?(admin.id)).to eq(true)
get "/admin/dashboard/whats-new.json"
expect(response.status).to eq(200)
expect(DiscourseUpdates.new_features_last_seen(admin.id)).not_to eq(nil)
expect(DiscourseUpdates.has_unseen_features?(admin.id)).to eq(false)
expect(DiscourseUpdates.new_features_last_seen(moderator.id)).to eq(nil)
expect(DiscourseUpdates.has_unseen_features?(moderator.id)).to eq(true)
end
it "doesn't error when there are no new features" do it "doesn't error when there are no new features" do
get "/admin/dashboard/new-features.json" get "/admin/dashboard/whats-new.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
end end
end end
@ -237,7 +255,7 @@ RSpec.describe Admin::DashboardController do
it "includes new features when available" do it "includes new features when available" do
populate_new_features populate_new_features
get "/admin/dashboard/new-features.json" get "/admin/dashboard/whats-new.json"
json = response.parsed_body json = response.parsed_body
@ -252,7 +270,7 @@ RSpec.describe Admin::DashboardController do
expect(DiscourseUpdates.get_last_viewed_feature_date(moderator.id)).to eq(nil) expect(DiscourseUpdates.get_last_viewed_feature_date(moderator.id)).to eq(nil)
get "/admin/dashboard/new-features.json" get "/admin/dashboard/whats-new.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(DiscourseUpdates.get_last_viewed_feature_date(moderator.id)).to eq(nil) expect(DiscourseUpdates.get_last_viewed_feature_date(moderator.id)).to eq(nil)
end end
@ -262,49 +280,7 @@ RSpec.describe Admin::DashboardController do
before { sign_in(user) } before { sign_in(user) }
it "denies access with a 404 response" do it "denies access with a 404 response" do
get "/admin/dashboard/new-features.json" get "/admin/dashboard/whats-new.json"
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
end
end
end
describe "#mark_new_features_as_seen" do
after { DiscourseUpdates.clean_state }
context "when logged in as an admin" do
before { sign_in(admin) }
it "resets last seen for a given user" do
populate_new_features
put "/admin/dashboard/mark-new-features-as-seen.json"
expect(response.status).to eq(200)
expect(DiscourseUpdates.new_features_last_seen(admin.id)).not_to eq(nil)
expect(DiscourseUpdates.has_unseen_features?(admin.id)).to eq(false)
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
it "resets last seen for moderator" do
populate_new_features
put "/admin/dashboard/mark-new-features-as-seen.json"
expect(response.status).to eq(200)
expect(DiscourseUpdates.new_features_last_seen(moderator.id)).not_to eq(nil)
expect(DiscourseUpdates.has_unseen_features?(moderator.id)).to eq(false)
end
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
it "prevents marking new feature as seen with a 404 response" do
put "/admin/dashboard/mark-new-features-as-seen.json"
expect(response.status).to eq(404) expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found")) expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))

View File

@ -4,7 +4,7 @@ module PageObjects
module Pages module Pages
class AdminDashboardNewFeatures < PageObjects::Pages::Base class AdminDashboardNewFeatures < PageObjects::Pages::Base
def visit def visit
page.visit("/admin/dashboard/new-features") page.visit("/admin/dashboard/whats-new")
self self
end end