diff --git a/app/assets/javascripts/admin/addon/components/dashboard-new-features.js b/app/assets/javascripts/admin/addon/components/dashboard-new-features.js
index 937340c1d36..059e138240a 100644
--- a/app/assets/javascripts/admin/addon/components/dashboard-new-features.js
+++ b/app/assets/javascripts/admin/addon/components/dashboard-new-features.js
@@ -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));
},
});
diff --git a/app/assets/javascripts/admin/addon/templates/components/dashboard-new-features.hbs b/app/assets/javascripts/admin/addon/templates/components/dashboard-new-features.hbs
index 190e53a4326..8d7e1c78f30 100644
--- a/app/assets/javascripts/admin/addon/templates/components/dashboard-new-features.hbs
+++ b/app/assets/javascripts/admin/addon/templates/components/dashboard-new-features.hbs
@@ -1,21 +1,19 @@
{{#if newFeatures}}
-
-
-
{{replace-emoji (i18n "admin.dashboard.new_features.title") }}
-
+
+
{{replace-emoji (i18n "admin.dashboard.new_features.title") }}
+
-
- {{#each newFeatures as |feature|}}
- {{dashboard-new-feature-item item=feature}}
- {{/each}}
-
-
+
+ {{#each newFeatures as |feature|}}
+ {{dashboard-new-feature-item item=feature tagName=""}}
+ {{/each}}
+
+
{{/if}}
diff --git a/app/assets/javascripts/admin/addon/templates/dashboard.hbs b/app/assets/javascripts/admin/addon/templates/dashboard.hbs
index cb87e84b471..95f26205e7c 100644
--- a/app/assets/javascripts/admin/addon/templates/dashboard.hbs
+++ b/app/assets/javascripts/admin/addon/templates/dashboard.hbs
@@ -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"}}
diff --git a/app/assets/stylesheets/common/admin/dashboard.scss b/app/assets/stylesheets/common/admin/dashboard.scss
index 0e553c4a9a8..0e49cb27a41 100644
--- a/app/assets/stylesheets/common/admin/dashboard.scss
+++ b/app/assets/stylesheets/common/admin/dashboard.scss
@@ -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 {
diff --git a/app/assets/stylesheets/mobile/dashboard.scss b/app/assets/stylesheets/mobile/dashboard.scss
index 71e8ef67610..3251b226690 100644
--- a/app/assets/stylesheets/mobile/dashboard.scss
+++ b/app/assets/stylesheets/mobile/dashboard.scss
@@ -6,4 +6,7 @@
.navigation a.navigation-link {
padding: 0.5em;
}
+ .dashboard-new-features .section-body {
+ grid-template-columns: none;
+ }
}
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index 1b75618f883..38e3fe0f074 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -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
diff --git a/lib/discourse_updates.rb b/lib/discourse_updates.rb
index a0274fe0929..91e5072bb78 100644
--- a/lib/discourse_updates.rb
+++ b/lib/discourse_updates.rb
@@ -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)
diff --git a/spec/components/discourse_updates_spec.rb b/spec/components/discourse_updates_spec.rb
index 41df2568636..3bdf7a683a3 100644
--- a/spec/components/discourse_updates_spec.rb
+++ b/spec/components/discourse_updates_spec.rb
@@ -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")
diff --git a/spec/requests/admin/dashboard_controller_spec.rb b/spec/requests/admin/dashboard_controller_spec.rb
index b838f169dc7..00ed3b3aaa5 100644
--- a/spec/requests/admin/dashboard_controller_spec.rb
+++ b/spec/requests/admin/dashboard_controller_spec.rb
@@ -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