UX: Changes to new features section in admin dashboard (#12029)
This commit is contained in:
parent
43948f6a10
commit
544a4e4b48
|
@ -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));
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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"}}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -6,4 +6,7 @@
|
|||
.navigation a.navigation-link {
|
||||
padding: 0.5em;
|
||||
}
|
||||
.dashboard-new-features .section-body {
|
||||
grid-template-columns: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue