From 8a8b11da6a7b936cf5b8aedd74b72bea1b49d69f Mon Sep 17 00:00:00 2001 From: Anton Date: Wed, 3 Sep 2025 14:30:56 +0200 Subject: [PATCH] Add new tags managment modal [SCI-12241] --- app/controllers/concerns/taggable_actions.rb | 27 ++++- .../users/settings/tags_controller.rb | 6 +- app/javascript/vue/shared/datatable/table.vue | 1 + .../vue/shared/mixins/tags_mixin.js | 91 ++++++++++++++ app/javascript/vue/shared/tags_input.vue | 112 ++++++++---------- app/javascript/vue/shared/tags_modal.vue | 71 +++++++++++ app/serializers/my_module_serializer.rb | 7 +- config/locales/en.yml | 1 + config/routes.rb | 1 + 9 files changed, 251 insertions(+), 66 deletions(-) create mode 100644 app/javascript/vue/shared/mixins/tags_mixin.js create mode 100644 app/javascript/vue/shared/tags_modal.vue diff --git a/app/controllers/concerns/taggable_actions.rb b/app/controllers/concerns/taggable_actions.rb index 8ddf83cd5..a6817fdd3 100644 --- a/app/controllers/concerns/taggable_actions.rb +++ b/app/controllers/concerns/taggable_actions.rb @@ -4,9 +4,10 @@ module TaggableActions extend ActiveSupport::Concern included do - before_action :load_taggable_item, only: %i(tag_resource untag_resource) + before_action :load_taggable_item, only: %i(tag_resource untag_resource tag_resource_with_new_tag) before_action :load_tag, only: %i(tag_resource untag_resource) - before_action :check_tag_manage_permissions, only: %i(tag_resource untag_resource) + before_action :check_tag_manage_permissions, only: %i(tag_resource untag_resource tag_resource_with_new_tag) + before_action :check_tag_create_permissions, only: %i(tag_resource_with_new_tag) end def tag_resource @@ -18,6 +19,20 @@ module TaggableActions end end + def tag_resource_with_new_tag + ActiveRecord::Base.transaction do + params[:tag][:color] = Constants::TAG_COLORS.sample.to_s if params[:tag][:color].blank? + + @tag = current_team.tags.create!(tag_params.merge(created_by: current_user, last_modified_by: current_user)) + tagging = @taggable_item.taggings.new(tag: @tag, created_by: current_user) + tagging.save! + render json: { tag: [@tag.id, @tag.name, @tag.color] } + rescue ActiveRecord::RecordInvalid => e + render json: { status: :error, error: e.message }, status: :unprocessable_entity + raise ActiveRecord::Rollback + end + end + def untag_resource tagging = @taggable_item.taggings.find_by(tag_id: @tag.id) if tagging&.destroy @@ -29,6 +44,10 @@ module TaggableActions private + def tag_params + params.require(:tag).permit(:name, :color) + end + def load_taggable_item @taggable_item = controller_name.singularize.camelize.constantize.find(params[:id]) end @@ -41,4 +60,8 @@ module TaggableActions def check_tag_manage_permissions raise NotImplementedError end + + def check_tag_create_permissions + true # TODO: implement + end end diff --git a/app/controllers/users/settings/tags_controller.rb b/app/controllers/users/settings/tags_controller.rb index e034bf44b..4ec857016 100644 --- a/app/controllers/users/settings/tags_controller.rb +++ b/app/controllers/users/settings/tags_controller.rb @@ -72,7 +72,11 @@ module Users tags_to_merge = @team.tags.where(id: params[:merge_ids]).where.not(id: @tag.id) taggings_to_update = Tagging.where(tag_id: tags_to_merge.select(:id)) - .where.not(id: Tagging.where(tag_id: @tag.id).select(:id)) + .where.not( + Tagging.where(tag_id: @tag.id).map{|i| + Arel.sql("(taggable_type = '#{i.taggable_type}' AND taggable_id = #{i.taggable_id})") + }.join(" OR ") + ) taggings_to_update.update!(tag_id: @tag.id) tags_to_merge.each(&:destroy!) diff --git a/app/javascript/vue/shared/datatable/table.vue b/app/javascript/vue/shared/datatable/table.vue index 883cf13ae..15c307628 100644 --- a/app/javascript/vue/shared/datatable/table.vue +++ b/app/javascript/vue/shared/datatable/table.vue @@ -695,6 +695,7 @@ export default { }, applyFilters(filters) { this.activeFilters = filters; + console.log(this.activeFilters); this.reloadTable(); }, switchViewRender(view) { diff --git a/app/javascript/vue/shared/mixins/tags_mixin.js b/app/javascript/vue/shared/mixins/tags_mixin.js new file mode 100644 index 000000000..b99ebd592 --- /dev/null +++ b/app/javascript/vue/shared/mixins/tags_mixin.js @@ -0,0 +1,91 @@ +import axios from '../../../packs/custom_axios.js'; +import { + tags_path +} from '../../../routes.js'; + +export default { + props: { + subject: { + type: Object, + required: true + }, + }, + computed: { + canManage() { + return this.subject.attributes.permissions.manage_tags; + }, + canAssign() { + return this.subject.attributes.permissions.assign_tags; + }, + tagResourceUrl() { + return this.subject.attributes.urls.tag_resource; + }, + untagResourceUrl() { + return this.subject.attributes.urls.untag_resource; + }, + createTagUrl() { + return this.subject.attributes.urls.tag_resource_with_new_tag; + } + }, + created() { + this.loadAllTags(); + this.tags = this.subject.attributes.tags || []; + }, + data() { + return { + tags: [], + allTags: [], + linkingTag: false, + searchQuery: '' + }; + }, + methods: { + loadAllTags() { + axios.get(tags_path()).then((response) => { + this.allTags = response.data.data; + }); + }, + linkTag(tag) { + if (this.tags.map(t => t[0]).includes(tag[0])) { + this.unlinkTag(tag); + return; + } + + if (this.linkingTag) { + return; + } + + this.linkingTag = true; + + axios.post(this.tagResourceUrl, { + tag_id: tag[0], + }).then((response) => { + this.tags.push(response.data.tag); + this.linkingTag = false; + this.searchQuery = ''; + }).catch(() => { + this.linkingTag = false; + HelperModule.flashAlertMsg(I18n.t('errors.general'), 'danger'); + }); + }, + + unlinkTag(tag) { + if (this.linkingTag) { + return; + } + + this.linkingTag = true; + + axios.post(this.untagResourceUrl, { + tag_id: tag[0], + }).then((response) => { + this.tags = this.tags.filter(t => t[0] !== tag[0]); + this.linkingTag = false; + this.searchQuery = ''; + }).catch(() => { + this.linkingTag = false; + HelperModule.flashAlertMsg(I18n.t('errors.general'), 'danger'); + }); + } + } +}; \ No newline at end of file diff --git a/app/javascript/vue/shared/tags_input.vue b/app/javascript/vue/shared/tags_input.vue index a25a5301a..331dd8a42 100644 --- a/app/javascript/vue/shared/tags_input.vue +++ b/app/javascript/vue/shared/tags_input.vue @@ -1,26 +1,32 @@