UX: Changes to new features section in admin dashboard (#12029)

This commit is contained in:
Penar Musaraj 2021-02-10 13:12:04 -05:00 committed by GitHub
parent 43948f6a10
commit 544a4e4b48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 92 additions and 52 deletions

View File

@ -1,9 +1,11 @@
import Component from "@ember/component";
import { action } from "@ember/object";
import { action, computed } from "@ember/object";
import { ajax } from "discourse/lib/ajax";
export default Component.extend({
newFeatures: null,
classNames: ["section", "dashboard-new-features"],
classNameBindings: ["hasUnseenFeatures:ordered-first"],
releaseNotesLink: null,
init() {
@ -12,15 +14,20 @@ export default Component.extend({
ajax("/admin/dashboard/new-features.json").then((json) => {
this.setProperties({
newFeatures: json.new_features,
hasUnseenFeatures: json.has_unseen_features,
releaseNotesLink: json.release_notes_link,
});
});
},
columnCountClass: computed("newFeatures", function () {
return this.newFeatures.length > 2 ? "three-or-more-items" : "";
}),
@action
dismissNewFeatures() {
ajax("/admin/dashboard/mark-new-features-as-seen.json", {
type: "PUT",
}).then(() => this.set("newFeatures", null));
}).then(() => this.set("hasUnseenFeatures", false));
},
});

View File

@ -1,21 +1,19 @@
{{#if newFeatures}}
<div class="section dashboard-new-features">
<div class="section-title">
<h2>{{replace-emoji (i18n "admin.dashboard.new_features.title") }}</h2>
</div>
<div class="section-title">
<h2>{{replace-emoji (i18n "admin.dashboard.new_features.title") }}</h2>
</div>
<div class="section-body">
{{#each newFeatures as |feature|}}
{{dashboard-new-feature-item item=feature}}
{{/each}}
</div>
<div class="section-footer">
{{#if releaseNotesLink}}
<a rel="noopener noreferrer" target="_blank" href={{releaseNotesLink}} class="btn btn-primary new-features-release-notes">
{{i18n "admin.dashboard.new_features.learn_more"}}
</a>
{{/if}}
{{d-button label="admin.dashboard.new_features.dismiss" class="new-features-dismiss" action=dismissNewFeatures }}
</div>
<div class="section-body {{columnCountClass}}">
{{#each newFeatures as |feature|}}
{{dashboard-new-feature-item item=feature tagName=""}}
{{/each}}
</div>
<div class="section-footer">
{{#if releaseNotesLink}}
<a rel="noopener noreferrer" target="_blank" href={{releaseNotesLink}} class="btn btn-primary new-features-release-notes">
{{i18n "admin.dashboard.new_features.learn_more"}}
</a>
{{/if}}
{{d-button label="admin.dashboard.new_features.dismiss" class="new-features-dismiss" action=dismissNewFeatures }}
</div>
{{/if}}

View File

@ -1,5 +1,3 @@
{{dashboard-new-features}}
{{plugin-outlet name="admin-dashboard-top"}}
{{#if showVersionChecks}}
@ -52,4 +50,6 @@
{{outlet}}
{{dashboard-new-features tagName="div"}}
{{plugin-outlet name="admin-dashboard-bottom"}}

View File

@ -613,11 +613,32 @@
}
}
.dashboard-next.general {
display: flex;
flex-direction: column;
}
.dashboard-new-features {
&.ordered-first {
order: -1;
}
&:not(.ordered-first) {
.section-title {
margin-top: 1.5em;
}
.new-features-dismiss {
display: none;
}
}
.section-body {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
grid-gap: 1.5em;
&.three-or-more-items {
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
}
.section-footer {

View File

@ -6,4 +6,7 @@
.navigation a.navigation-link {
padding: 0.5em;
}
.dashboard-new-features .section-body {
grid-template-columns: none;
}
}

View File

@ -24,8 +24,11 @@ class Admin::DashboardController < Admin::AdminController
end
def new_features
data = { new_features: DiscourseUpdates.unseen_new_features(current_user.id) }
data.merge!(release_notes_link: AdminDashboardGeneralData.fetch_cached_stats["release_notes_link"])
data = {
new_features: DiscourseUpdates.new_features,
has_unseen_features: DiscourseUpdates.has_unseen_features?(current_user.id),
release_notes_link: AdminDashboardGeneralData.fetch_cached_stats["release_notes_link"]
}
render json: data
end

View File

@ -121,21 +121,28 @@ module DiscourseUpdates
Discourse.redis.set(new_features_key, response.body)
end
def unseen_new_features(user_id)
def new_features
entries = JSON.parse(Discourse.redis.get(new_features_key)) rescue nil
return nil if entries.nil?
entries.select! do |item|
item["discourse_version"].nil? || Discourse.has_needed_version?(Discourse::VERSION::STRING, item["discourse_version"]) rescue nil
end
entries.sort { |item| Time.zone.parse(item["created_at"]) }
end
def has_unseen_features?(user_id)
entries = new_features
return false if entries.nil?
last_seen = new_features_last_seen(user_id)
if last_seen.present?
entries.select! { |item| Time.zone.parse(item["created_at"]) > last_seen }
end
entries.select! do |item|
item["discourse_version"].nil? || Discourse.has_needed_version?(Discourse::VERSION::STRING, item["discourse_version"]) rescue nil
end
entries.sort { |item| Time.zone.parse(item["created_at"]) }
entries.size > 0
end
def new_features_last_seen(user_id)

View File

@ -158,46 +158,37 @@ describe DiscourseUpdates do
before(:each) do
Discourse.redis.del "new_features_last_seen_user_#{admin.id}"
Discourse.redis.del "new_features_last_seen_user_#{admin2.id}"
Discourse.redis.del "new_features"
Discourse.redis.set('new_features', MultiJson.dump(sample_features))
end
it 'returns all items on the first run' do
result = DiscourseUpdates.unseen_new_features(admin.id)
result = DiscourseUpdates.new_features
expect(result.length).to eq(3)
expect(result[2]["title"]).to eq("Super Fruits")
end
it 'returns only unseen items by user' do
it 'correctly marks unseen items by user' do
DiscourseUpdates.stubs(:new_features_last_seen).with(admin.id).returns(10.minutes.ago)
DiscourseUpdates.stubs(:new_features_last_seen).with(admin2.id).returns(30.minutes.ago)
result = DiscourseUpdates.unseen_new_features(admin.id)
expect(result.length).to eq(1)
expect(result[0]["title"]).to eq("Quality Veggies")
result2 = DiscourseUpdates.unseen_new_features(admin2.id)
expect(result2.length).to eq(2)
expect(result2[0]["title"]).to eq("Quality Veggies")
expect(result2[1]["title"]).to eq("Fancy Legumes")
expect(DiscourseUpdates.has_unseen_features?(admin.id)).to eq(true)
expect(DiscourseUpdates.has_unseen_features?(admin2.id)).to eq(true)
end
it 'can mark features as seen for a given user' do
expect(DiscourseUpdates.unseen_new_features(admin.id)).to be_present
expect(DiscourseUpdates.has_unseen_features?(admin.id)).to be_truthy
DiscourseUpdates.mark_new_features_as_seen(admin.id)
expect(DiscourseUpdates.unseen_new_features(admin.id)).to be_empty
expect(DiscourseUpdates.has_unseen_features?(admin.id)).to eq(false)
# doesn't affect another user
expect(DiscourseUpdates.unseen_new_features(admin2.id)).to be_present
expect(DiscourseUpdates.has_unseen_features?(admin2.id)).to eq(true)
end
it 'correctly sees newly added features as unseen' do
DiscourseUpdates.mark_new_features_as_seen(admin.id)
expect(DiscourseUpdates.unseen_new_features(admin.id)).to be_empty
expect(DiscourseUpdates.has_unseen_features?(admin.id)).to eq(false)
expect(DiscourseUpdates.new_features_last_seen(admin.id)).to be_within(1.second).of (last_item_date)
updated_features = [
@ -206,10 +197,7 @@ describe DiscourseUpdates do
updated_features += sample_features
Discourse.redis.set('new_features', MultiJson.dump(updated_features))
result = DiscourseUpdates.unseen_new_features(admin.id)
expect(result.length).to eq(1)
expect(result[0]["title"]).to eq("Brand New Item")
expect(DiscourseUpdates.has_unseen_features?(admin.id)).to eq(true)
end
it 'correctly shows features by Discourse version' do
@ -224,7 +212,7 @@ describe DiscourseUpdates do
Discourse.redis.set('new_features', MultiJson.dump(features_with_versions))
DiscourseUpdates.stubs(:last_installed_version).returns("2.7.0.beta2")
result = DiscourseUpdates.unseen_new_features(admin.id)
result = DiscourseUpdates.new_features
expect(result.length).to eq(3)
expect(result[0]["title"]).to eq("Confetti")

View File

@ -118,6 +118,18 @@ describe Admin::DashboardController do
expect(json['new_features'].length).to eq(2)
expect(json['new_features'][0]["emoji"]).to eq("🙈")
expect(json['new_features'][0]["title"]).to eq("Fancy Legumes")
expect(json['has_unseen_features']).to eq(true)
end
it 'passes unseen feature state' do
populate_new_features
DiscourseUpdates.mark_new_features_as_seen(admin.id)
get "/admin/dashboard/new-features.json"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json['has_unseen_features']).to eq(false)
end
end
@ -128,6 +140,7 @@ describe Admin::DashboardController do
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
end