From 9cabd3721b13a37c63464043dbae2d41dd908349 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotlarek Date: Wed, 1 Dec 2021 09:18:56 +1100 Subject: [PATCH] FEATURE: ability to add description to tags (#15125) Ability to add description to tags, which will be displayed on hover. --- .../discourse/app/components/tag-info.js | 38 +++++++++++++++++-- .../discourse/app/controllers/rename-tag.js | 33 ---------------- .../discourse/app/lib/render-tag.js | 1 + .../discourse/app/lib/render-tags.js | 8 +++- .../app/templates/components/tag-info.hbs | 37 +++++++++++++++--- .../app/templates/components/tag-list.hbs | 2 +- .../app/templates/modal/rename-tag.hbs | 21 ---------- .../discourse/tests/acceptance/tags-test.js | 27 ++++++++++++- .../tests/fixtures/discovery-fixtures.js | 1 + .../tests/fixtures/search-fixtures.js | 4 ++ .../addon/components/mini-tag-chooser.js | 6 ++- .../stylesheets/common/base/tagging.scss | 25 +++++++++++- app/assets/stylesheets/mobile/_index.scss | 1 + app/assets/stylesheets/mobile/tagging.scss | 9 +++++ app/controllers/tags_controller.rb | 11 ++++-- app/models/tag.rb | 2 + app/serializers/concerns/topic_tags_mixin.rb | 25 ++++++++---- app/serializers/tag_serializer.rb | 2 +- config/locales/client.en.yml | 5 ++- .../20211116225901_add_description_to_tags.rb | 7 ++++ lib/discourse_tagging.rb | 2 +- .../serializers/topic_view_serializer_spec.rb | 7 ++-- .../web_hook_topic_view_serializer_spec.rb | 1 + 23 files changed, 190 insertions(+), 85 deletions(-) delete mode 100644 app/assets/javascripts/discourse/app/controllers/rename-tag.js delete mode 100644 app/assets/javascripts/discourse/app/templates/modal/rename-tag.hbs create mode 100644 app/assets/stylesheets/mobile/tagging.scss create mode 100644 db/migrate/20211116225901_add_description_to_tags.rb diff --git a/app/assets/javascripts/discourse/app/components/tag-info.js b/app/assets/javascripts/discourse/app/components/tag-info.js index 5749eb283ef..60ddb3ba604 100644 --- a/app/assets/javascripts/discourse/app/components/tag-info.js +++ b/app/assets/javascripts/discourse/app/components/tag-info.js @@ -6,7 +6,7 @@ import bootbox from "bootbox"; import discourseComputed from "discourse-common/utils/decorators"; import { isEmpty } from "@ember/utils"; import { popupAjaxError } from "discourse/lib/ajax-error"; -import showModal from "discourse/lib/show-modal"; +import { inject as service } from "@ember/service"; export default Component.extend({ tagName: "", @@ -16,6 +16,10 @@ export default Component.extend({ showEditControls: false, canAdminTag: reads("currentUser.staff"), editSynonymsMode: and("canAdminTag", "showEditControls"), + editing: false, + newTagName: null, + newTagDescription: null, + router: service(), @discourseComputed("tagInfo.tag_group_names") tagGroupsInfo(tagGroupNames) { @@ -41,6 +45,13 @@ export default Component.extend({ return isEmpty(tagGroupNames) && isEmpty(categories) && isEmpty(synonyms); }, + @discourseComputed("newTagName") + updateDisabled(newTagName) { + const filterRegexp = new RegExp(this.site.tags_filter_regexp, "g"); + newTagName = newTagName ? newTagName.replace(filterRegexp, "").trim() : ""; + return newTagName.length === 0; + }, + didInsertElement() { this._super(...arguments); this.loadTagInfo(); @@ -69,8 +80,29 @@ export default Component.extend({ this.toggleProperty("showEditControls"); }, - renameTag() { - showModal("rename-tag", { model: this.tag }); + edit() { + this.setProperties({ + editing: true, + newTagName: this.tag.id, + newTagDescription: this.tagInfo.description, + }); + }, + + cancelEditing() { + this.set("editing", false); + }, + + finishedEditing() { + this.tag + .update({ id: this.newTagName, description: this.newTagDescription }) + .then((result) => { + this.set("editing", false); + this.tagInfo.set("description", this.newTagDescription); + if (result.payload) { + this.router.transitionTo("tag.show", result.payload.id); + } + }) + .catch(popupAjaxError); }, deleteTag() { diff --git a/app/assets/javascripts/discourse/app/controllers/rename-tag.js b/app/assets/javascripts/discourse/app/controllers/rename-tag.js deleted file mode 100644 index 1c5f1d618f0..00000000000 --- a/app/assets/javascripts/discourse/app/controllers/rename-tag.js +++ /dev/null @@ -1,33 +0,0 @@ -import { action } from "@ember/object"; -import BufferedContent from "discourse/mixins/buffered-content"; -import Controller from "@ember/controller"; -import ModalFunctionality from "discourse/mixins/modal-functionality"; -import discourseComputed from "discourse-common/utils/decorators"; -import { extractError } from "discourse/lib/ajax-error"; - -export default Controller.extend(ModalFunctionality, BufferedContent, { - newTag: null, - - @discourseComputed("newTag", "model.id") - renameDisabled(newTag, currentTag) { - const filterRegexp = new RegExp(this.site.tags_filter_regexp, "g"); - newTag = newTag ? newTag.replace(filterRegexp, "").trim() : ""; - return newTag.length === 0 || newTag === currentTag; - }, - - @action - performRename() { - this.model - .update({ id: this.newTag }) - .then((result) => { - this.send("closeModal"); - - if (result.responseJson.tag) { - this.transitionToRoute("tag.show", result.responseJson.tag.id); - } else { - this.flash(extractError(result.responseJson.errors[0]), "error"); - } - }) - .catch((error) => this.flash(extractError(error), "error")); - }, -}); diff --git a/app/assets/javascripts/discourse/app/lib/render-tag.js b/app/assets/javascripts/discourse/app/lib/render-tag.js index d545ad10bc7..418b37f1b4a 100644 --- a/app/assets/javascripts/discourse/app/lib/render-tag.js +++ b/app/assets/javascripts/discourse/app/lib/render-tag.js @@ -44,6 +44,7 @@ export function defaultRenderTag(tag, params) { href + " data-tag-name=" + tag + + (params.description ? ' title="' + params.description + '" ' : "") + " class='" + classes.join(" ") + "'>" + diff --git a/app/assets/javascripts/discourse/app/lib/render-tags.js b/app/assets/javascripts/discourse/app/lib/render-tags.js index 14647d562c4..d4aebe57edc 100644 --- a/app/assets/javascripts/discourse/app/lib/render-tags.js +++ b/app/assets/javascripts/discourse/app/lib/render-tags.js @@ -55,7 +55,13 @@ export default function (topic, params) { if (tags) { for (let i = 0; i < tags.length; i++) { buffer += - renderTag(tags[i], { isPrivateMessage, tagsForUser, tagName }) + " "; + renderTag(tags[i], { + description: + topic.tags_descriptions && topic.tags_descriptions[tags[i]], + isPrivateMessage, + tagsForUser, + tagName, + }) + " "; } } diff --git a/app/assets/javascripts/discourse/app/templates/components/tag-info.hbs b/app/assets/javascripts/discourse/app/templates/components/tag-info.hbs index 7862457f4b3..92aa9d2e9c9 100644 --- a/app/assets/javascripts/discourse/app/templates/components/tag-info.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/tag-info.hbs @@ -1,16 +1,41 @@
{{#if tagInfo}}
- {{discourse-tag tagInfo.name tagName="div" size="large"}} + {{#if editing}} +
+ {{text-field id="edit-name" value=(readonly tagInfo.name) maxlength=siteSettings.max_tag_length input=(action (mut newTagName) value="target.value") autofocus="true"}} + {{text-field id="edit-description" value=(readonly tagInfo.description) placeholder=(i18n "tagging.description") maxlength=280 input=(action (mut newTagDescription) value="target.value") autofocus="true"}} + +
+ {{#unless updateDisabled}} + {{d-button action=(action "finishedEditing") class="btn-primary submit-edit" icon="check" ariaLabel="tagging.save"}} + {{/unless}} + {{d-button action=(action "cancelEditing") class="btn-default cancel-edit" icon="times" ariaLabel="cancel"}} +
+
+ {{else}} +
+ {{discourse-tag tagInfo.name tagName="div" size="large"}} + {{#if canAdminTag}} + {{d-icon "pencil-alt"}} + {{/if}} +
+ {{#if canAdminTag}} +
+ {{tagInfo.description}} +
+ {{/if}} + {{/if}} {{#if canAdminTag}} {{plugin-outlet name="tag-custom-settings" args=(hash tag=tagInfo) connectorTagName="" tagName="section"}} - {{d-button class="btn-default" action=(action "renameTag") icon="pencil-alt" label="tagging.rename_tag" id="rename-tag"}} - {{d-button class="btn-default" action=(action "toggleEditControls") icon="cog" label="tagging.edit_synonyms" id="edit-synonyms"}} - {{#if deleteAction}} - {{d-button class="btn-danger delete-tag" action=(action "deleteTag") icon="far-trash-alt" label="tagging.delete_tag" id="delete-tag"}} - {{/if}} +
+ {{d-button class="btn-default" action=(action "toggleEditControls") icon="cog" label="tagging.edit_synonyms" id="edit-synonyms"}} + {{#if deleteAction}} + {{d-button class="btn-danger delete-tag" action=(action "deleteTag") icon="far-trash-alt" label="tagging.delete_tag" id="delete-tag"}} + {{/if}} +
{{/if}}
diff --git a/app/assets/javascripts/discourse/app/templates/components/tag-list.hbs b/app/assets/javascripts/discourse/app/templates/components/tag-list.hbs index 6f5f0da581e..e02568a225b 100644 --- a/app/assets/javascripts/discourse/app/templates/components/tag-list.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/tag-list.hbs @@ -9,7 +9,7 @@ {{/if}} {{#each sortedTags as |tag|}}
- {{discourse-tag tag.id isPrivateMessage=isPrivateMessage pmOnly=tag.pmOnly tagsForUser=tagsForUser}} {{#if tag.pmOnly}}{{d-icon "far-envelope"}}{{/if}}{{#if tag.totalCount}} x {{tag.totalCount}}{{/if}} + {{discourse-tag tag.id description=tag.description isPrivateMessage=isPrivateMessage pmOnly=tag.pmOnly tagsForUser=tagsForUser}} {{#if tag.pmOnly}}{{d-icon "far-envelope"}}{{/if}}{{#if tag.totalCount}} x {{tag.totalCount}}{{/if}}
{{/each}}
diff --git a/app/assets/javascripts/discourse/app/templates/modal/rename-tag.hbs b/app/assets/javascripts/discourse/app/templates/modal/rename-tag.hbs deleted file mode 100644 index 0ce71cba7d5..00000000000 --- a/app/assets/javascripts/discourse/app/templates/modal/rename-tag.hbs +++ /dev/null @@ -1,21 +0,0 @@ -{{#d-modal-body title="tagging.rename_tag"}} - -
- {{input - value=(readonly model.id) - maxlength=siteSettings.max_tag_length - input=(action (mut newTag) value="target.value") - }} -
-{{/d-modal-body}} - - diff --git a/app/assets/javascripts/discourse/tests/acceptance/tags-test.js b/app/assets/javascripts/discourse/tests/acceptance/tags-test.js index 3f8e95df2cf..42dfe9e3d0f 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/tags-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/tags-test.js @@ -4,6 +4,7 @@ import { count, exists, invisible, + query, queryAll, updateCurrentUser, } from "discourse/tests/helpers/qunit-helpers"; @@ -64,6 +65,7 @@ acceptance("Tags", function (needs) { bookmarked: false, liked: true, tags: ["test"], + tags_descriptions: { test: "test description" }, views: 42, like_count: 42, has_summary: false, @@ -355,6 +357,7 @@ acceptance("Tag info", function (needs) { tag_info: { id: 13, name: "happy-monkey", + description: "happy monkey description", topic_count: 1, staff: false, synonyms: [], @@ -429,6 +432,28 @@ acceptance("Tag info", function (needs) { ); }); + test("edit tag is showing input for name and description", async function (assert) { + updateCurrentUser({ moderator: false, admin: true }); + + await visit("/tag/happy-monkey"); + assert.strictEqual(count("#show-tag-info"), 1); + + await click("#show-tag-info"); + assert.ok(exists(".tag-info .tag-name"), "show tag"); + + await click("#edit-tag"); + assert.strictEqual( + query("#edit-name").value, + "happy-monkey", + "it displays original tag name" + ); + assert.strictEqual( + query("#edit-description").value, + "happy monkey description", + "it displays original tag description" + ); + }); + test("can filter tags page by category", async function (assert) { await visit("/tag/planters"); @@ -445,7 +470,7 @@ acceptance("Tag info", function (needs) { assert.strictEqual(count("#show-tag-info"), 1); await click("#show-tag-info"); - assert.ok(exists("#rename-tag"), "can rename tag"); + assert.ok(exists("#edit-tag"), "can rename tag"); assert.ok(exists("#edit-synonyms"), "can edit synonyms"); assert.ok(exists("#delete-tag"), "can delete tag"); diff --git a/app/assets/javascripts/discourse/tests/fixtures/discovery-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/discovery-fixtures.js index c981d7e3642..65f3c515e09 100644 --- a/app/assets/javascripts/discourse/tests/fixtures/discovery-fixtures.js +++ b/app/assets/javascripts/discourse/tests/fixtures/discovery-fixtures.js @@ -6405,6 +6405,7 @@ export default { bookmarked: false, liked: false, tags: ["test", "test-tag"], + tags_description: { test: "test description", "test-tag": "test tag description" }, views: 6, like_count: 0, has_summary: false, diff --git a/app/assets/javascripts/discourse/tests/fixtures/search-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/search-fixtures.js index 84e74240464..bfb8a449991 100644 --- a/app/assets/javascripts/discourse/tests/fixtures/search-fixtures.js +++ b/app/assets/javascripts/discourse/tests/fixtures/search-fixtures.js @@ -540,6 +540,10 @@ export default { pinned_globally: false, posters: [], tags: ["dev", "slow"], + tags_descriptions: { + "dev": "dev description", + "slow": "slow description", + } }, { id: 14727, diff --git a/app/assets/javascripts/select-kit/addon/components/mini-tag-chooser.js b/app/assets/javascripts/select-kit/addon/components/mini-tag-chooser.js index 0025efd2064..e119c7adcc8 100644 --- a/app/assets/javascripts/select-kit/addon/components/mini-tag-chooser.js +++ b/app/assets/javascripts/select-kit/addon/components/mini-tag-chooser.js @@ -99,7 +99,11 @@ export default MultiSelectComponent.extend(TagsMixin, { return results .filter((r) => !makeArray(context.tags).includes(r.id)) .map((result) => { - return { id: result.text, name: result.text, count: result.count }; + return { + id: result.text, + name: result.description, + count: result.count, + }; }); }, }); diff --git a/app/assets/stylesheets/common/base/tagging.scss b/app/assets/stylesheets/common/base/tagging.scss index 11ad84d5256..9d8a9223fbd 100644 --- a/app/assets/stylesheets/common/base/tagging.scss +++ b/app/assets/stylesheets/common/base/tagging.scss @@ -338,9 +338,32 @@ section.tag-info { margin-right: 0.5em; } + .edit-tag-wrapper { + display: flex; + + input { + margin-right: 0.5em; + } + } + .tag-name-wrapper, + .tag-description-wrapper { + display: flex; + } + .tag-name-wrapper a { + color: var(--primary-high); + margin-left: 0.5em; + } + + .tag-name-wrapper a { + font-size: var(--font-up-3); + } + .tag-name .discourse-tag { display: block; - margin-bottom: 0.75em; + } + + .tag-description-wrapper { + margin-bottom: 1em; } .synonyms-list, diff --git a/app/assets/stylesheets/mobile/_index.scss b/app/assets/stylesheets/mobile/_index.scss index b1cca1189df..8f7319408dd 100644 --- a/app/assets/stylesheets/mobile/_index.scss +++ b/app/assets/stylesheets/mobile/_index.scss @@ -24,6 +24,7 @@ @import "reviewables"; @import "ring"; @import "search"; +@import "tagging"; @import "topic-list"; @import "topic-post"; @import "topic"; diff --git a/app/assets/stylesheets/mobile/tagging.scss b/app/assets/stylesheets/mobile/tagging.scss new file mode 100644 index 00000000000..d18d3cf6529 --- /dev/null +++ b/app/assets/stylesheets/mobile/tagging.scss @@ -0,0 +1,9 @@ +.edit-tag-wrapper { + flex-direction: column; + .edit-controls { + margin-bottom: 0.5em; + } +} +.tag-info .tag-actions { + display: flex; +} diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 912a60c9c77..05e0f9b809a 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -135,11 +135,14 @@ class TagsController < ::ApplicationController tag = Tag.find_by_name(params[:tag_id]) raise Discourse::NotFound if tag.nil? - new_tag_name = DiscourseTagging.clean_tag(params[:tag][:id]) - tag.name = new_tag_name + if (params[:tag][:id].present?) + new_tag_name = DiscourseTagging.clean_tag(params[:tag][:id]) + tag.name = new_tag_name + end + tag.description = params[:tag][:description] if params[:tag]&.has_key?(:description) if tag.save StaffActionLogger.new(current_user).log_custom('renamed_tag', previous_value: params[:tag_id], new_value: new_tag_name) - render json: { tag: { id: new_tag_name } } + render json: { tag: { id: tag.name, description: tag.description } } else render_json_error tag.errors.full_messages end @@ -353,6 +356,8 @@ class TagsController < ::ApplicationController { id: t.name, text: t.name, + name: t.name, + description: t.description, count: t.topic_count, pm_count: show_pm_tags ? t.pm_topic_count : 0, target_tag: t.target_tag_id ? target_tags.find { |x| x.id == t.target_tag_id }&.name : nil diff --git a/app/models/tag.rb b/app/models/tag.rb index f489747138d..7a98de7cdcb 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -15,6 +15,7 @@ class Tag < ActiveRecord::Base validate :target_tag_validator, if: Proc.new { |t| t.new_record? || t.will_save_change_to_target_tag_id? } validate :name_validator + validates :description, length: { maximum: 280 } scope :where_name, ->(name) do name = Array(name).map(&:downcase) @@ -215,6 +216,7 @@ end # updated_at :datetime not null # pm_topic_count :integer default(0), not null # target_tag_id :integer +# description :string # # Indexes # diff --git a/app/serializers/concerns/topic_tags_mixin.rb b/app/serializers/concerns/topic_tags_mixin.rb index e5552afe53a..5c39cd5a5d4 100644 --- a/app/serializers/concerns/topic_tags_mixin.rb +++ b/app/serializers/concerns/topic_tags_mixin.rb @@ -3,6 +3,7 @@ module TopicTagsMixin def self.included(klass) klass.attributes :tags + klass.attributes :tags_descriptions end def include_tags? @@ -10,16 +11,26 @@ module TopicTagsMixin end def tags - # Calling method `pluck` or `order` along with `includes` causing N+1 queries - tags = (SiteSetting.tags_sort_alphabetically ? topic.tags.sort_by(&:name) : topic.tags.sort_by(&:topic_count).reverse).map(&:name) - if scope.is_staff? - tags - else - tags - scope.hidden_tag_names - end + all_tags.map(&:name) + end + + def tags_descriptions + all_tags.each.with_object({}) { |tag, acc| acc[tag.name] = tag.description }.compact end def topic object.is_a?(Topic) ? object : object.topic end + + private + + def all_tags + return @tags if defined?(@tags) + # Calling method `pluck` or `order` along with `includes` causing N+1 queries + tags = (SiteSetting.tags_sort_alphabetically ? topic.tags.sort_by(&:name) : topic.tags.sort_by(&:topic_count).reverse) + if !scope.is_staff? + tags = tags.reject { |tag| scope.hidden_tag_names.include?(tag[:name]) } + end + @tags = tags + end end diff --git a/app/serializers/tag_serializer.rb b/app/serializers/tag_serializer.rb index 696eb3c0378..6fa46a80bdf 100644 --- a/app/serializers/tag_serializer.rb +++ b/app/serializers/tag_serializer.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class TagSerializer < ApplicationSerializer - attributes :id, :name, :topic_count, :staff + attributes :id, :name, :topic_count, :staff, :description def staff DiscourseTagging.staff_tag_names.include?(name) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 9ee9bf3b73c..4895e58255c 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -3796,6 +3796,7 @@ en: category_restricted: "This tag is restricted to categories you don't have permission to access." synonyms: "Synonyms" synonyms_description: "When the following tags are used, they will be replaced with %{base_tag_name}." + save: "Save name and description of the tag" tag_groups_info: one: 'This tag belongs to the group "%{tag_groups}".' other: "This tag belongs to these groups: %{tag_groups}." @@ -3819,8 +3820,8 @@ en: delete_confirm_synonyms: one: "Its synonym will also be deleted." other: "Its %{count} synonyms will also be deleted." - rename_tag: "Rename Tag" - rename_instructions: "Choose a new name for the tag:" + edit_tag: "Edit tag name and description" + description: "Description" sort_by: "Sort by:" sort_by_count: "count" sort_by_name: "name" diff --git a/db/migrate/20211116225901_add_description_to_tags.rb b/db/migrate/20211116225901_add_description_to_tags.rb new file mode 100644 index 00000000000..275109ce11a --- /dev/null +++ b/db/migrate/20211116225901_add_description_to_tags.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddDescriptionToTags < ActiveRecord::Migration[6.1] + def change + add_column :tags, :description, :string + end +end diff --git a/lib/discourse_tagging.rb b/lib/discourse_tagging.rb index 16e240ade53..fd740bfc31c 100644 --- a/lib/discourse_tagging.rb +++ b/lib/discourse_tagging.rb @@ -256,7 +256,7 @@ module DiscourseTagging end sql << <<~SQL - SELECT #{distinct_clause} t.id, t.name, t.topic_count, t.pm_topic_count, + SELECT #{distinct_clause} t.id, t.name, t.topic_count, t.pm_topic_count, t.description, tgr.tgm_id as tgm_id, tgr.tag_group_id as tag_group_id, tgr.parent_tag_id as parent_tag_id, tgr.one_per_topic as one_per_topic, t.target_tag_id FROM tags t diff --git a/spec/serializers/topic_view_serializer_spec.rb b/spec/serializers/topic_view_serializer_spec.rb index 02a126060a9..5861d9b3e6f 100644 --- a/spec/serializers/topic_view_serializer_spec.rb +++ b/spec/serializers/topic_view_serializer_spec.rb @@ -236,9 +236,9 @@ describe TopicViewSerializer do end describe 'tags order' do - fab!(:tag1) { Fabricate(:tag, name: 'ctag', topic_count: 5) } - fab!(:tag2) { Fabricate(:tag, name: 'btag', topic_count: 9) } - fab!(:tag3) { Fabricate(:tag, name: 'atag', topic_count: 3) } + fab!(:tag1) { Fabricate(:tag, name: 'ctag', description: "c description", topic_count: 5) } + fab!(:tag2) { Fabricate(:tag, name: 'btag', description: "b description", topic_count: 9) } + fab!(:tag3) { Fabricate(:tag, name: 'atag', description: "a description", topic_count: 3) } before do topic.tags << tag1 @@ -249,6 +249,7 @@ describe TopicViewSerializer do it 'tags are automatically sorted by tag popularity' do json = serialize_topic(topic, user) expect(json[:tags]).to eq(%w(btag ctag atag)) + expect(json[:tags_descriptions]).to eq({ btag: "b description", ctag: "c description", atag: "a description" }) end it 'tags can be sorted alphabetically' do diff --git a/spec/serializers/web_hook_topic_view_serializer_spec.rb b/spec/serializers/web_hook_topic_view_serializer_spec.rb index df878fb7cba..c3e3451e69c 100644 --- a/spec/serializers/web_hook_topic_view_serializer_spec.rb +++ b/spec/serializers/web_hook_topic_view_serializer_spec.rb @@ -50,6 +50,7 @@ RSpec.describe WebHookTopicViewSerializer do created_by last_poster tags + tags_descriptions thumbnails }