diff --git a/app/assets/javascripts/discourse/app/controllers/user-activity.js b/app/assets/javascripts/discourse/app/controllers/user-activity.js index c62f5bc72ed..d3725a92597 100644 --- a/app/assets/javascripts/discourse/app/controllers/user-activity.js +++ b/app/assets/javascripts/discourse/app/controllers/user-activity.js @@ -3,7 +3,7 @@ import I18n from "I18n"; import { alias } from "@ember/object/computed"; import bootbox from "bootbox"; import { exportUserArchive } from "discourse/lib/export-csv"; -import { observes } from "discourse-common/utils/decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; export default Controller.extend({ application: controller(), @@ -28,6 +28,13 @@ export default Controller.extend({ this.set("application.showFooter", showFooter); }, + @discourseComputed("currentUser.draft_count") + draftLabel(count) { + return count > 0 + ? I18n.t("drafts.label_with_count", { count }) + : I18n.t("drafts.label"); + }, + actions: { exportUserArchive() { bootbox.confirm( diff --git a/app/assets/javascripts/discourse/app/initializers/subscribe-user-changes.js b/app/assets/javascripts/discourse/app/initializers/subscribe-user-changes.js new file mode 100644 index 00000000000..bc25dc22035 --- /dev/null +++ b/app/assets/javascripts/discourse/app/initializers/subscribe-user-changes.js @@ -0,0 +1,15 @@ +export default { + name: "subscribe-user-changes", + after: "message-bus", + + initialize(container) { + const user = container.lookup("current-user:main"); + + if (user) { + const bus = container.lookup("message-bus:main"); + bus.subscribe("/user", (data) => { + user.setProperties(data); + }); + } + }, +}; diff --git a/app/assets/javascripts/discourse/app/templates/user/activity.hbs b/app/assets/javascripts/discourse/app/templates/user/activity.hbs index 40b558bee14..a7fe65f3552 100644 --- a/app/assets/javascripts/discourse/app/templates/user/activity.hbs +++ b/app/assets/javascripts/discourse/app/templates/user/activity.hbs @@ -13,7 +13,7 @@ {{#if user.showDrafts}} {{#d-navigation-item route="userActivity.drafts"}} - {{i18n "user_action_groups.15"}} + {{draftLabel}} {{/d-navigation-item}} {{/if}} diff --git a/app/assets/javascripts/discourse/app/widgets/quick-access-profile.js b/app/assets/javascripts/discourse/app/widgets/quick-access-profile.js index 5ad69f6e9c1..9279b48bd4d 100644 --- a/app/assets/javascripts/discourse/app/widgets/quick-access-profile.js +++ b/app/assets/javascripts/discourse/app/widgets/quick-access-profile.js @@ -84,7 +84,12 @@ createWidgetFrom(QuickAccessPanel, "quick-access-profile", { { icon: "pencil-alt", href: `${this.attrs.path}/activity/drafts`, - content: I18n.t("user_action_groups.15"), + content: + this.currentUser.draft_count > 0 + ? I18n.t("drafts.label_with_count", { + count: this.currentUser.draft_count, + }) + : I18n.t("drafts.label"), className: "drafts", }, { diff --git a/app/models/draft.rb b/app/models/draft.rb index 13710661040..7b39d44e9a0 100644 --- a/app/models/draft.rb +++ b/app/models/draft.rb @@ -7,6 +7,8 @@ class Draft < ActiveRecord::Base belongs_to :user + after_commit :update_draft_count, on: [:create, :destroy] + class OutOfSequence < StandardError; end def self.set(user, key, sequence, data, owner = nil, force_save: false) @@ -92,6 +94,8 @@ class Draft < ActiveRecord::Base owner = :owner, updated_at = CURRENT_TIMESTAMP SQL + + UserStat.update_draft_count(user.id) end sequence @@ -338,6 +342,9 @@ class Draft < ActiveRecord::Base end + def update_draft_count + UserStat.update_draft_count(self.user_id) + end end # == Schema Information diff --git a/app/models/user_stat.rb b/app/models/user_stat.rb index d2963fba8d6..69bc63a5735 100644 --- a/app/models/user_stat.rb +++ b/app/models/user_stat.rb @@ -202,6 +202,21 @@ class UserStat < ActiveRecord::Base self.class.update_distinct_badge_count(self.user_id) end + def self.update_draft_count(user_id) + draft_count = DB.query_single <<~SQL, user_id: user_id + UPDATE user_stats + SET draft_count = (SELECT COUNT(*) FROM drafts WHERE user_id = :user_id) + WHERE user_id = :user_id + RETURNING draft_count + SQL + + MessageBus.publish( + '/user', + { draft_count: draft_count.first }, + user_ids: [user_id] + ) + end + # topic_reply_count is a count of posts in other users' topics def calc_topic_reply_count!(start_time = nil) sql = <<~SQL @@ -292,4 +307,5 @@ end # distinct_badge_count :integer default(0), not null # first_unread_pm_at :datetime not null # digest_attempted_at :datetime +# draft_count :integer default(0), not null # diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb index dc0637fe236..b0fd9fbe708 100644 --- a/app/serializers/current_user_serializer.rb +++ b/app/serializers/current_user_serializer.rb @@ -65,6 +65,7 @@ class CurrentUserSerializer < BasicUserSerializer :do_not_disturb_until, :has_topic_draft, :can_review, + :draft_count, def groups owned_group_ids = GroupUser.where(user_id: id, owner: true).pluck(:group_id).to_set @@ -315,4 +316,8 @@ class CurrentUserSerializer < BasicUserSerializer def include_has_topic_draft? Draft.has_topic_draft(object) end + + def draft_count + object.user_stat.draft_count + end end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 02265ea3ce5..ad3949804f6 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -332,6 +332,8 @@ en: copied: "copied!" drafts: + label: "Drafts" + label_with_count: "Drafts (%{count})" resume: "Resume" remove: "Remove" remove_confirmation: "Are you sure you want to delete this draft?" diff --git a/db/migrate/20210720221817_add_draft_count_to_user_stat.rb b/db/migrate/20210720221817_add_draft_count_to_user_stat.rb new file mode 100644 index 00000000000..f496609da82 --- /dev/null +++ b/db/migrate/20210720221817_add_draft_count_to_user_stat.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class AddDraftCountToUserStat < ActiveRecord::Migration[6.1] + def change + add_column :user_stats, :draft_count, :integer, default: 0, null: false + + execute <<~SQL + UPDATE user_stats + SET draft_count = new_user_stats.draft_count + FROM (SELECT user_stats.user_id, COUNT(drafts.id) draft_count + FROM user_stats + LEFT JOIN drafts ON user_stats.user_id = drafts.user_id + GROUP BY user_stats.user_id) new_user_stats + WHERE user_stats.user_id = new_user_stats.user_id + AND user_stats.draft_count <> new_user_stats.draft_count + SQL + end +end diff --git a/spec/models/draft_spec.rb b/spec/models/draft_spec.rb index df1c3dff41e..54218c5e9eb 100644 --- a/spec/models/draft_spec.rb +++ b/spec/models/draft_spec.rb @@ -176,6 +176,20 @@ describe Draft do expect(Draft.count).to eq 0 end + it 'updates draft count when a draft is created or destroyed' do + messages = MessageBus.track_publish("/user") do + Draft.set(user, "test", 0, "data") + end + + expect(messages.first.data[:draft_count]).to eq(1) + + messages = MessageBus.track_publish("/user") do + Draft.where(user: user).destroy_all + end + + expect(messages.first.data[:draft_count]).to eq(0) + end + describe '#stream' do fab!(:public_post) { Fabricate(:post) } let(:public_topic) { public_post.topic } diff --git a/spec/models/user_stat_spec.rb b/spec/models/user_stat_spec.rb index ef0ddc3f62e..3a3a0a56fd3 100644 --- a/spec/models/user_stat_spec.rb +++ b/spec/models/user_stat_spec.rb @@ -222,4 +222,18 @@ describe UserStat do end end + + describe '.update_draft_count' do + fab!(:user) { Fabricate(:user) } + + it 'updates draft_count' do + Draft.create!(user: user, draft_key: "topic_1", data: {}) + Draft.create!(user: user, draft_key: "new_topic", data: {}) + Draft.create!(user: user, draft_key: "topic_2", data: {}) + UserStat.update_all(draft_count: 0) + + UserStat.update_draft_count(user.id) + expect(user.user_stat.draft_count).to eq(3) + end + end end