diff --git a/app/controllers/users/settings/tags_controller.rb b/app/controllers/users/settings/tags_controller.rb index 4d3a8939d..e034bf44b 100644 --- a/app/controllers/users/settings/tags_controller.rb +++ b/app/controllers/users/settings/tags_controller.rb @@ -5,7 +5,7 @@ module Users class TagsController < ApplicationController before_action :load_team before_action :check_team_permissions - before_action :load_vars, only: %i(update destroy) + before_action :load_vars, only: %i(update destroy merge) before_action :set_breadcrumbs_items, only: %i(index) def index @@ -20,6 +20,10 @@ module Users end end + def list + @tags = @team.tags.order(:name) + end + def actions_toolbar render json: { actions: @@ -63,6 +67,23 @@ module Users end end + def merge + ActiveRecord::Base.transaction do + 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)) + + taggings_to_update.update!(tag_id: @tag.id) + tags_to_merge.each(&:destroy!) + + render json: { message: :ok }, status: :ok + rescue ActiveRecord::RecordInvalid => e + render json: { error: e.message }, status: :unprocessable_entity + raise ActiveRecord::Rollback + end + end + private def load_team diff --git a/app/javascript/vue/tags/index.vue b/app/javascript/vue/tags/index.vue index 1e0ef0ecf..0b7deda01 100644 --- a/app/javascript/vue/tags/index.vue +++ b/app/javascript/vue/tags/index.vue @@ -14,8 +14,14 @@ @changeColor="changeColor" @changeName="changeName" @createRow="createTag" + @merge="openMergeModal" @delete="deleteTag" /> + @@ -27,6 +33,7 @@ import axios from '../../packs/custom_axios.js'; import DataTable from '../shared/datatable/table.vue'; import colorRenderer from './renderers/color.vue'; import nameRenderer from './renderers/name.vue'; +import mergeModal from './modals/merge.vue'; import { users_settings_team_tag_path, @@ -38,6 +45,8 @@ export default { components: { DataTable, colorRenderer, + nameRenderer, + mergeModal }, props: { dataSource: { @@ -54,12 +63,20 @@ export default { tagsColors: { type: Object, required: true + }, + teamId: { + required: true + }, + listUrl: { + type: String, + required: true } }, data() { return { reloadingTable: false, addingNewRow: false, + mergeIds: null, newRowTemplate: { name: { value: '', @@ -144,6 +161,9 @@ export default { this.reloadingTable = true; }) }, + openMergeModal(event, rows) { + this.mergeIds = rows.map(row => row.id); + }, createTag(newTag) { this.addingNewRow = false; axios.post(this.createUrl, { diff --git a/app/javascript/vue/tags/modals/merge.vue b/app/javascript/vue/tags/modals/merge.vue new file mode 100644 index 000000000..4fa2ae58b --- /dev/null +++ b/app/javascript/vue/tags/modals/merge.vue @@ -0,0 +1,87 @@ + + + diff --git a/app/javascript/vue/user_groups/modal/add_member.vue b/app/javascript/vue/user_groups/modal/add_member.vue index d80399375..758761026 100644 --- a/app/javascript/vue/user_groups/modal/add_member.vue +++ b/app/javascript/vue/user_groups/modal/add_member.vue @@ -53,7 +53,7 @@ import SelectDropdown from '../../shared/select_dropdown.vue'; import axios from '../../../packs/custom_axios.js'; import modalMixin from '../../shared/modal_mixin'; -import escapeHtml from '../../../shared/escape_html.js'; +import escapeHtml from '../../shared/escape_html.js'; export default { name: 'AddMembersModal', diff --git a/app/services/lists/tags_service.rb b/app/services/lists/tags_service.rb index ee94155d2..b9eb4e57c 100644 --- a/app/services/lists/tags_service.rb +++ b/app/services/lists/tags_service.rb @@ -5,7 +5,7 @@ module Lists private def fetch_records - @records = @raw_data.left_joins(:created_by, :taggings) + @records = @raw_data.left_joins(:created_by, :taggings).includes(:created_by, :last_modified_by) .select('tags.*') .select('array_agg(users.full_name) AS created_by_user') .select('COUNT(taggings.id) AS taggings_count') diff --git a/app/services/toolbars/tags_service.rb b/app/services/toolbars/tags_service.rb index 43cf1e392..3748d90a9 100644 --- a/app/services/toolbars/tags_service.rb +++ b/app/services/toolbars/tags_service.rb @@ -19,6 +19,7 @@ module Toolbars return [] if @tags.none? [ + merge_action, delete_action ].compact end @@ -38,5 +39,16 @@ module Toolbars type: :emit } end + + def merge_action + # return unless can_manage_team?(@team) + + { + name: 'merge', + label: I18n.t('tags.index.toolbar.merge'), + icon: 'sn-icon sn-icon-merge', + type: :emit + } + end end end diff --git a/app/views/users/settings/tags/index.html.erb b/app/views/users/settings/tags/index.html.erb index 7da212391..bb01f6694 100644 --- a/app/views/users/settings/tags/index.html.erb +++ b/app/views/users/settings/tags/index.html.erb @@ -9,6 +9,8 @@ <%= render partial: 'users/settings/teams/header' %>
- - + + @@ -235,4 +235,7 @@ + + + \ No newline at end of file diff --git a/vendor/assets/stylesheets/fonts/SN-icon-font.ttf b/vendor/assets/stylesheets/fonts/SN-icon-font.ttf index 0003aaa90..6ae1b4a8f 100644 Binary files a/vendor/assets/stylesheets/fonts/SN-icon-font.ttf and b/vendor/assets/stylesheets/fonts/SN-icon-font.ttf differ diff --git a/vendor/assets/stylesheets/fonts/SN-icon-font.woff b/vendor/assets/stylesheets/fonts/SN-icon-font.woff index 38bbeeca4..c26c365ac 100644 Binary files a/vendor/assets/stylesheets/fonts/SN-icon-font.woff and b/vendor/assets/stylesheets/fonts/SN-icon-font.woff differ diff --git a/vendor/assets/stylesheets/fonts/SN-icon-font.woff2 b/vendor/assets/stylesheets/fonts/SN-icon-font.woff2 index 1dd83c105..c741707d7 100644 Binary files a/vendor/assets/stylesheets/fonts/SN-icon-font.woff2 and b/vendor/assets/stylesheets/fonts/SN-icon-font.woff2 differ diff --git a/vendor/assets/stylesheets/sn-icon-font.css b/vendor/assets/stylesheets/sn-icon-font.css index 09741a70e..9b6ce1131 100644 --- a/vendor/assets/stylesheets/sn-icon-font.css +++ b/vendor/assets/stylesheets/sn-icon-font.css @@ -1,11 +1,11 @@ @font-face { font-family: 'SN-icon-font'; - src: url('fonts/SN-icon-font.eot?gqphqg'); - src: url('fonts/SN-icon-font.eot?gqphqg#iefix') format('embedded-opentype'), - url('fonts/SN-icon-font.woff2?gqphqg') format('woff2'), - url('fonts/SN-icon-font.ttf?gqphqg') format('truetype'), - url('fonts/SN-icon-font.woff?gqphqg') format('woff'), - url('fonts/SN-icon-font.svg?gqphqg#SN-icon-font') format('svg'); + src: url('fonts/SN-icon-font.eot?uepbw'); + src: url('fonts/SN-icon-font.eot?uepbw#iefix') format('embedded-opentype'), + url('fonts/SN-icon-font.woff2?uepbw') format('woff2'), + url('fonts/SN-icon-font.ttf?uepbw') format('truetype'), + url('fonts/SN-icon-font.woff?uepbw') format('woff'), + url('fonts/SN-icon-font.svg?uepbw#SN-icon-font') format('svg'); font-weight: normal; font-style: normal; font-display: block; @@ -653,3 +653,12 @@ .sn-icon-cloud-storage:before { content: "\e9d0"; } +.sn-icon-merge:before { + content: "\e9d1"; +} +.sn-icon-ai:before { + content: "\e9d2"; +} +.sn-icon-close-solid:before { + content: "\e9d3"; +}