From e6dd2472980026114dae8143f790680a3a9a02c5 Mon Sep 17 00:00:00 2001
From: Eugie Limpin <elimpin@gitlab.com>
Date: Mon, 7 Oct 2024 15:10:49 +0800
Subject: [PATCH] Store abuse report labels separate from project/group labels

Changelog: changed
---
 .rubocop_todo/gitlab/bounded_contexts.yml     |  2 -
 .../admin/abuse_report_labels_finder.rb       |  4 +-
 app/models/abuse_report.rb                    |  5 +-
 app/models/admin/abuse_report_label.rb        |  7 --
 app/models/anti_abuse/reports/label.rb        | 18 +++++
 app/models/anti_abuse/reports/label_link.rb   | 15 +++++
 .../admin/abuse_report_label_policy.rb        |  9 ---
 .../anti_abuse/reports/label_policy.rb        | 11 +++
 .../abuse_report_labels/create_service.rb     |  2 +-
 .../admin/abuse_reports/update_service.rb     |  2 +-
 db/docs/abuse_report_label_links.yml          | 10 +++
 db/docs/abuse_report_labels.yml               | 10 +++
 db/docs/labels.yml                            |  1 -
 db/fixtures/development/18_abuse_reports.rb   | 17 ++++-
 ...241022011456_create_abuse_report_labels.rb | 16 +++++
 ...2012406_create_abuse_report_label_links.rb | 21 ++++++
 ...rt_labels_title_and_description_columns.rb | 22 ++++++
 db/schema_migrations/20241022011456           |  1 +
 db/schema_migrations/20241022012406           |  1 +
 db/schema_migrations/20241022012511           |  1 +
 db/structure.sql                              | 67 +++++++++++++++++++
 spec/factories/abuse_reports.rb               |  2 +-
 .../anti_abuse/abuse_report_label_links.rb    |  8 +++
 .../anti_abuse/abuse_report_labels.rb         |  7 ++
 spec/factories/labels.rb                      |  2 -
 spec/frontend/admin/abuse_report/mock_data.js |  6 +-
 spec/models/abuse_report_spec.rb              |  9 +++
 .../anti_abuse/reports/label_link_spec.rb     | 18 +++++
 spec/models/anti_abuse/reports/label_spec.rb  | 23 +++++++
 .../admin/abuse_report_labels/create_spec.rb  |  6 +-
 .../anti_abuse/reports/label_entity_spec.rb   |  2 +-
 .../create_service_spec.rb                    |  8 +--
 32 files changed, 293 insertions(+), 40 deletions(-)
 delete mode 100644 app/models/admin/abuse_report_label.rb
 create mode 100644 app/models/anti_abuse/reports/label.rb
 create mode 100644 app/models/anti_abuse/reports/label_link.rb
 delete mode 100644 app/policies/admin/abuse_report_label_policy.rb
 create mode 100644 app/policies/anti_abuse/reports/label_policy.rb
 create mode 100644 db/docs/abuse_report_label_links.yml
 create mode 100644 db/docs/abuse_report_labels.yml
 create mode 100644 db/migrate/20241022011456_create_abuse_report_labels.rb
 create mode 100644 db/migrate/20241022012406_create_abuse_report_label_links.rb
 create mode 100644 db/migrate/20241022012511_add_index_to_abuse_report_labels_title_and_description_columns.rb
 create mode 100644 db/schema_migrations/20241022011456
 create mode 100644 db/schema_migrations/20241022012406
 create mode 100644 db/schema_migrations/20241022012511
 create mode 100644 spec/factories/anti_abuse/abuse_report_label_links.rb
 create mode 100644 spec/factories/anti_abuse/abuse_report_labels.rb
 create mode 100644 spec/models/anti_abuse/reports/label_link_spec.rb
 create mode 100644 spec/models/anti_abuse/reports/label_spec.rb

diff --git a/.rubocop_todo/gitlab/bounded_contexts.yml b/.rubocop_todo/gitlab/bounded_contexts.yml
index d1b7aaee140bacb5..4bb41d54b7e40cdd 100644
--- a/.rubocop_todo/gitlab/bounded_contexts.yml
+++ b/.rubocop_todo/gitlab/bounded_contexts.yml
@@ -686,7 +686,6 @@ Gitlab/BoundedContexts:
     - 'app/models/activity_pub.rb'
     - 'app/models/activity_pub/releases_subscription.rb'
     - 'app/models/admin/abuse_report_assignee.rb'
-    - 'app/models/admin/abuse_report_label.rb'
     - 'app/models/alert_management.rb'
     - 'app/models/alert_management/alert.rb'
     - 'app/models/alert_management/alert_assignee.rb'
@@ -1288,7 +1287,6 @@ Gitlab/BoundedContexts:
     - 'app/policies/abuse_report_policy.rb'
     - 'app/policies/achievements/achievement_policy.rb'
     - 'app/policies/achievements/user_achievement_policy.rb'
-    - 'app/policies/admin/abuse_report_label_policy.rb'
     - 'app/policies/alert_management/alert_policy.rb'
     - 'app/policies/alert_management/http_integration_policy.rb'
     - 'app/policies/application_setting/term_policy.rb'
diff --git a/app/finders/admin/abuse_report_labels_finder.rb b/app/finders/admin/abuse_report_labels_finder.rb
index f8ca40f77b28792f..37ce37fd4a68b28d 100644
--- a/app/finders/admin/abuse_report_labels_finder.rb
+++ b/app/finders/admin/abuse_report_labels_finder.rb
@@ -8,9 +8,9 @@ def initialize(current_user, params = {})
     end
 
     def execute
-      return Admin::AbuseReportLabel.none unless current_user&.can_admin_all_resources?
+      return AntiAbuse::Reports::Label.none unless current_user&.can_admin_all_resources?
 
-      items = Admin::AbuseReportLabel.all
+      items = AntiAbuse::Reports::Label.all
       items = by_search(items)
 
       items.order(title: :asc) # rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb
index 4571602200612384..73d9396519c81d15 100644
--- a/app/models/abuse_report.rb
+++ b/app/models/abuse_report.rb
@@ -22,8 +22,9 @@ class AbuseReport < ApplicationRecord
   belongs_to :resolved_by, class_name: 'User', inverse_of: :resolved_abuse_reports
 
   has_many :events, class_name: 'ResourceEvents::AbuseReportEvent', inverse_of: :abuse_report
-  has_many :label_links, as: :target, inverse_of: :target
-  has_many :labels, through: :label_links
+  has_many :label_links, inverse_of: :abuse_report, class_name: 'AntiAbuse::Reports::LabelLink'
+  has_many :labels, through: :label_links, source: :abuse_report_label,
+    class_name: 'AntiAbuse::Reports::Label'
   has_many :admin_abuse_report_assignees, class_name: "Admin::AbuseReportAssignee"
   has_many :assignees, class_name: "User", through: :admin_abuse_report_assignees
 
diff --git a/app/models/admin/abuse_report_label.rb b/app/models/admin/abuse_report_label.rb
deleted file mode 100644
index 6f951b0293377737..0000000000000000
--- a/app/models/admin/abuse_report_label.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-module Admin
-  class AbuseReportLabel < Label
-    self.allow_legacy_sti_class = true
-  end
-end
diff --git a/app/models/anti_abuse/reports/label.rb b/app/models/anti_abuse/reports/label.rb
new file mode 100644
index 0000000000000000..0652fabf5f5c9e73
--- /dev/null
+++ b/app/models/anti_abuse/reports/label.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module AntiAbuse
+  module Reports
+    class Label < ApplicationRecord
+      include BaseLabel
+
+      self.table_name = 'abuse_report_labels'
+
+      has_many :label_links, foreign_key: :abuse_report_label_id, inverse_of: :abuse_report_label,
+        class_name: 'AntiAbuse::Reports::LabelLink'
+      has_many :abuse_reports, through: :label_links
+
+      validates :title, uniqueness: true
+      validates :description, length: { maximum: 500 }
+    end
+  end
+end
diff --git a/app/models/anti_abuse/reports/label_link.rb b/app/models/anti_abuse/reports/label_link.rb
new file mode 100644
index 0000000000000000..1479dd05f6caa49d
--- /dev/null
+++ b/app/models/anti_abuse/reports/label_link.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module AntiAbuse
+  module Reports
+    class LabelLink < ApplicationRecord
+      self.table_name = 'abuse_report_label_links'
+
+      belongs_to :abuse_report, inverse_of: :label_links
+      belongs_to :abuse_report_label, class_name: 'AntiAbuse::Reports::Label', inverse_of: :label_links
+
+      validates :abuse_report, presence: true
+      validates :abuse_report_label, presence: true, uniqueness: { scope: :abuse_report_id }
+    end
+  end
+end
diff --git a/app/policies/admin/abuse_report_label_policy.rb b/app/policies/admin/abuse_report_label_policy.rb
deleted file mode 100644
index 69c877c90b3e2476..0000000000000000
--- a/app/policies/admin/abuse_report_label_policy.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-module Admin
-  class AbuseReportLabelPolicy < ::BasePolicy
-    rule { admin }.policy do
-      enable :read_label
-    end
-  end
-end
diff --git a/app/policies/anti_abuse/reports/label_policy.rb b/app/policies/anti_abuse/reports/label_policy.rb
new file mode 100644
index 0000000000000000..d36e927498832694
--- /dev/null
+++ b/app/policies/anti_abuse/reports/label_policy.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module AntiAbuse
+  module Reports
+    class LabelPolicy < BasePolicy
+      rule { admin }.policy do
+        enable :read_label
+      end
+    end
+  end
+end
diff --git a/app/services/admin/abuse_report_labels/create_service.rb b/app/services/admin/abuse_report_labels/create_service.rb
index 937890a9f5175db0..a86eb497fc615bb7 100644
--- a/app/services/admin/abuse_report_labels/create_service.rb
+++ b/app/services/admin/abuse_report_labels/create_service.rb
@@ -10,7 +10,7 @@ def initialize(params = {})
       def execute
         params[:color] = convert_color_name_to_hex if params[:color].present?
 
-        ::Admin::AbuseReportLabel.create(params)
+        ::AntiAbuse::Reports::Label.create(params)
       end
     end
   end
diff --git a/app/services/admin/abuse_reports/update_service.rb b/app/services/admin/abuse_reports/update_service.rb
index 36992e1aa250a54b..5284ed000c66a628 100644
--- a/app/services/admin/abuse_reports/update_service.rb
+++ b/app/services/admin/abuse_reports/update_service.rb
@@ -23,7 +23,7 @@ def execute
 
       def label_ids
         params[:label_ids].filter_map do |id|
-          GitlabSchema.parse_gid(id, expected_type: ::Admin::AbuseReportLabel).model_id
+          GitlabSchema.parse_gid(id, expected_type: ::AntiAbuse::Reports::Label).model_id
         rescue Gitlab::Graphql::Errors::ArgumentError
         end
       end
diff --git a/db/docs/abuse_report_label_links.yml b/db/docs/abuse_report_label_links.yml
new file mode 100644
index 0000000000000000..ca02b3b0dbb8737c
--- /dev/null
+++ b/db/docs/abuse_report_label_links.yml
@@ -0,0 +1,10 @@
+---
+table_name: abuse_report_label_links
+classes:
+- AntiAbuse::Reports::LabelLink
+feature_categories:
+- insider_threat
+description: Links between abuse report labels and abuse reports
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/168186
+milestone: '17.6'
+gitlab_schema: gitlab_main_clusterwide
diff --git a/db/docs/abuse_report_labels.yml b/db/docs/abuse_report_labels.yml
new file mode 100644
index 0000000000000000..7fc9052ac464530d
--- /dev/null
+++ b/db/docs/abuse_report_labels.yml
@@ -0,0 +1,10 @@
+---
+table_name: abuse_report_labels
+classes:
+- AntiAbuse::Reports::Label
+feature_categories:
+- insider_threat
+description: Stores labels for abuse reports
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/168186
+milestone: '17.6'
+gitlab_schema: gitlab_main_clusterwide
diff --git a/db/docs/labels.yml b/db/docs/labels.yml
index 7cccf039993b6913..3744cf561e24b397 100644
--- a/db/docs/labels.yml
+++ b/db/docs/labels.yml
@@ -1,7 +1,6 @@
 ---
 table_name: labels
 classes:
-- Admin::AbuseReportLabel
 - GroupLabel
 - Label
 - ProjectLabel
diff --git a/db/fixtures/development/18_abuse_reports.rb b/db/fixtures/development/18_abuse_reports.rb
index 743810edad9b5667..65e86267f1e7ddd0 100644
--- a/db/fixtures/development/18_abuse_reports.rb
+++ b/db/fixtures/development/18_abuse_reports.rb
@@ -16,7 +16,22 @@ def self.seed
                   user.assign_personal_namespace(Organizations::Organization.default_organization)
                 end
 
-              ::AbuseReport.create(reporter: ::User.take, user: reported_user, message: 'User sends spam')
+              label_title = "abuse_report_label_#{i}"
+              ::AntiAbuse::Reports::Label.create(
+                title: label_title,
+                description: FFaker::Lorem.sentence,
+                color: "#{::Gitlab::Color.color_for(label_title)}"
+              )
+
+              label_ids = ::AntiAbuse::Reports::Label.pluck(:id).sample(rand(5))
+
+              ::AbuseReport.create(
+                reporter: ::User.take,
+                user: reported_user,
+                message: 'User sends spam',
+                label_ids: label_ids
+              )
+
               print '.'
             end
           end
diff --git a/db/migrate/20241022011456_create_abuse_report_labels.rb b/db/migrate/20241022011456_create_abuse_report_labels.rb
new file mode 100644
index 0000000000000000..efe143567555749d
--- /dev/null
+++ b/db/migrate/20241022011456_create_abuse_report_labels.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class CreateAbuseReportLabels < Gitlab::Database::Migration[2.2]
+  milestone '17.6'
+
+  def change
+    create_table :abuse_report_labels do |t|
+      t.timestamps_with_timezone null: false
+      t.integer :cached_markdown_version
+      t.text :title, limit: 255, index: { unique: true }, null: false
+      t.text :color, limit: 7
+      t.text :description, limit: 500
+      t.text :description_html, limit: 1000
+    end
+  end
+end
diff --git a/db/migrate/20241022012406_create_abuse_report_label_links.rb b/db/migrate/20241022012406_create_abuse_report_label_links.rb
new file mode 100644
index 0000000000000000..3484742768062e82
--- /dev/null
+++ b/db/migrate/20241022012406_create_abuse_report_label_links.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class CreateAbuseReportLabelLinks < Gitlab::Database::Migration[2.2]
+  milestone '17.6'
+
+  INDEX_NAME = 'index_abuse_report_label_links_on_report_id_and_label_id'
+
+  def up
+    create_table :abuse_report_label_links do |t|
+      t.references :abuse_report, index: false, null: false, foreign_key: { on_delete: :cascade }
+      t.references :abuse_report_label, null: false, foreign_key: { on_delete: :cascade }
+      t.timestamps_with_timezone null: false
+    end
+
+    add_index :abuse_report_label_links, [:abuse_report_id, :abuse_report_label_id], unique: true, name: INDEX_NAME
+  end
+
+  def down
+    drop_table :abuse_report_label_links
+  end
+end
diff --git a/db/migrate/20241022012511_add_index_to_abuse_report_labels_title_and_description_columns.rb b/db/migrate/20241022012511_add_index_to_abuse_report_labels_title_and_description_columns.rb
new file mode 100644
index 0000000000000000..eff936c748a0f724
--- /dev/null
+++ b/db/migrate/20241022012511_add_index_to_abuse_report_labels_title_and_description_columns.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class AddIndexToAbuseReportLabelsTitleAndDescriptionColumns < Gitlab::Database::Migration[2.2]
+  disable_ddl_transaction!
+
+  milestone '17.6'
+
+  TITLE_INDEX_NAME = 'index_abuse_report_labels_on_title_trigram'
+  DESCRIPTION_INDEX_NAME = 'index_abuse_report_labels_on_description_trigram'
+
+  def up
+    add_concurrent_index :abuse_report_labels, :title, name: TITLE_INDEX_NAME,
+      using: :gin, opclass: { name: :gin_trgm_ops }
+    add_concurrent_index :abuse_report_labels, :description, name: DESCRIPTION_INDEX_NAME,
+      using: :gin, opclass: { name: :gin_trgm_ops }
+  end
+
+  def down
+    remove_concurrent_index_by_name :abuse_report_labels, TITLE_INDEX_NAME
+    remove_concurrent_index_by_name :abuse_report_labels, DESCRIPTION_INDEX_NAME
+  end
+end
diff --git a/db/schema_migrations/20241022011456 b/db/schema_migrations/20241022011456
new file mode 100644
index 0000000000000000..95e899ba5794e9a6
--- /dev/null
+++ b/db/schema_migrations/20241022011456
@@ -0,0 +1 @@
+55f509efd9813f6f376e321a675ffe16aee2285af7ebde10d60e34a4aff246ee
\ No newline at end of file
diff --git a/db/schema_migrations/20241022012406 b/db/schema_migrations/20241022012406
new file mode 100644
index 0000000000000000..b7a28b30142e1072
--- /dev/null
+++ b/db/schema_migrations/20241022012406
@@ -0,0 +1 @@
+da833cf5ee9d9a3d3681376a78c42cb4122e6c1b365df8078123d8d6bb60ef4f
\ No newline at end of file
diff --git a/db/schema_migrations/20241022012511 b/db/schema_migrations/20241022012511
new file mode 100644
index 0000000000000000..ced75800bb230619
--- /dev/null
+++ b/db/schema_migrations/20241022012511
@@ -0,0 +1 @@
+5a044656695b3da3508523f1b87f10082d711f9a427bbd4226f64212cc0cb160
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 5b2ac770acc31c41..7f2b535a64d2d55b 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -5301,6 +5301,47 @@ CREATE SEQUENCE abuse_report_events_id_seq
 
 ALTER SEQUENCE abuse_report_events_id_seq OWNED BY abuse_report_events.id;
 
+CREATE TABLE abuse_report_label_links (
+    id bigint NOT NULL,
+    abuse_report_id bigint NOT NULL,
+    abuse_report_label_id bigint NOT NULL,
+    created_at timestamp with time zone NOT NULL,
+    updated_at timestamp with time zone NOT NULL
+);
+
+CREATE SEQUENCE abuse_report_label_links_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+ALTER SEQUENCE abuse_report_label_links_id_seq OWNED BY abuse_report_label_links.id;
+
+CREATE TABLE abuse_report_labels (
+    id bigint NOT NULL,
+    created_at timestamp with time zone NOT NULL,
+    updated_at timestamp with time zone NOT NULL,
+    cached_markdown_version integer,
+    title text NOT NULL,
+    color text,
+    description text,
+    description_html text,
+    CONSTRAINT check_034642a23f CHECK ((char_length(description) <= 500)),
+    CONSTRAINT check_7957e7e95f CHECK ((char_length(description_html) <= 1000)),
+    CONSTRAINT check_c7a15f74dc CHECK ((char_length(color) <= 7)),
+    CONSTRAINT check_e264245e2a CHECK ((char_length(title) <= 255))
+);
+
+CREATE SEQUENCE abuse_report_labels_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+ALTER SEQUENCE abuse_report_labels_id_seq OWNED BY abuse_report_labels.id;
+
 CREATE TABLE abuse_report_notes (
     id bigint NOT NULL,
     abuse_report_id bigint NOT NULL,
@@ -22023,6 +22064,10 @@ ALTER TABLE ONLY abuse_report_assignees ALTER COLUMN id SET DEFAULT nextval('abu
 
 ALTER TABLE ONLY abuse_report_events ALTER COLUMN id SET DEFAULT nextval('abuse_report_events_id_seq'::regclass);
 
+ALTER TABLE ONLY abuse_report_label_links ALTER COLUMN id SET DEFAULT nextval('abuse_report_label_links_id_seq'::regclass);
+
+ALTER TABLE ONLY abuse_report_labels ALTER COLUMN id SET DEFAULT nextval('abuse_report_labels_id_seq'::regclass);
+
 ALTER TABLE ONLY abuse_report_notes ALTER COLUMN id SET DEFAULT nextval('abuse_report_notes_id_seq'::regclass);
 
 ALTER TABLE ONLY abuse_report_user_mentions ALTER COLUMN id SET DEFAULT nextval('abuse_report_user_mentions_id_seq'::regclass);
@@ -23824,6 +23869,12 @@ ALTER TABLE ONLY abuse_report_assignees
 ALTER TABLE ONLY abuse_report_events
     ADD CONSTRAINT abuse_report_events_pkey PRIMARY KEY (id);
 
+ALTER TABLE ONLY abuse_report_label_links
+    ADD CONSTRAINT abuse_report_label_links_pkey PRIMARY KEY (id);
+
+ALTER TABLE ONLY abuse_report_labels
+    ADD CONSTRAINT abuse_report_labels_pkey PRIMARY KEY (id);
+
 ALTER TABLE ONLY abuse_report_notes
     ADD CONSTRAINT abuse_report_notes_pkey PRIMARY KEY (id);
 
@@ -27760,6 +27811,16 @@ CREATE INDEX index_abuse_report_events_on_abuse_report_id ON abuse_report_events
 
 CREATE INDEX index_abuse_report_events_on_user_id ON abuse_report_events USING btree (user_id);
 
+CREATE INDEX index_abuse_report_label_links_on_abuse_report_label_id ON abuse_report_label_links USING btree (abuse_report_label_id);
+
+CREATE UNIQUE INDEX index_abuse_report_label_links_on_report_id_and_label_id ON abuse_report_label_links USING btree (abuse_report_id, abuse_report_label_id);
+
+CREATE INDEX index_abuse_report_labels_on_description_trigram ON abuse_report_labels USING gin (description gin_trgm_ops);
+
+CREATE UNIQUE INDEX index_abuse_report_labels_on_title ON abuse_report_labels USING btree (title);
+
+CREATE INDEX index_abuse_report_labels_on_title_trigram ON abuse_report_labels USING gin (title gin_trgm_ops);
+
 CREATE INDEX index_abuse_report_notes_on_abuse_report_id ON abuse_report_notes USING btree (abuse_report_id);
 
 CREATE INDEX index_abuse_report_notes_on_author_id ON abuse_report_notes USING btree (author_id);
@@ -37399,9 +37460,15 @@ ALTER TABLE ONLY vs_code_settings
 ALTER TABLE ONLY audit_events_group_streaming_event_type_filters
     ADD CONSTRAINT fk_rails_e07e457a27 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
 
+ALTER TABLE ONLY abuse_report_label_links
+    ADD CONSTRAINT fk_rails_e15ea8b6bc FOREIGN KEY (abuse_report_id) REFERENCES abuse_reports(id) ON DELETE CASCADE;
+
 ALTER TABLE ONLY label_priorities
     ADD CONSTRAINT fk_rails_e161058b0f FOREIGN KEY (label_id) REFERENCES labels(id) ON DELETE CASCADE;
 
+ALTER TABLE ONLY abuse_report_label_links
+    ADD CONSTRAINT fk_rails_e1a10f7c4e FOREIGN KEY (abuse_report_label_id) REFERENCES abuse_report_labels(id) ON DELETE CASCADE;
+
 ALTER TABLE ONLY packages_packages
     ADD CONSTRAINT fk_rails_e1ac527425 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
 
diff --git a/spec/factories/abuse_reports.rb b/spec/factories/abuse_reports.rb
index f7e90fa95cca617e..bfad3661dceb9452 100644
--- a/spec/factories/abuse_reports.rb
+++ b/spec/factories/abuse_reports.rb
@@ -39,7 +39,7 @@
     end
 
     trait :with_labels do
-      labels { [association(:label)] }
+      labels { [association(:abuse_report_label)] }
     end
   end
 end
diff --git a/spec/factories/anti_abuse/abuse_report_label_links.rb b/spec/factories/anti_abuse/abuse_report_label_links.rb
new file mode 100644
index 0000000000000000..0a035824d4861fbe
--- /dev/null
+++ b/spec/factories/anti_abuse/abuse_report_label_links.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+  factory :abuse_report_label_link, class: 'AntiAbuse::Reports::LabelLink' do
+    abuse_report_label
+    abuse_report
+  end
+end
diff --git a/spec/factories/anti_abuse/abuse_report_labels.rb b/spec/factories/anti_abuse/abuse_report_labels.rb
new file mode 100644
index 0000000000000000..a1297e5a2742071f
--- /dev/null
+++ b/spec/factories/anti_abuse/abuse_report_labels.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+  factory :abuse_report_label, class: 'AntiAbuse::Reports::Label' do
+    title { generate(:label_title) }
+  end
+end
diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb
index e592565eef785cf4..250c92c0038c4997 100644
--- a/spec/factories/labels.rb
+++ b/spec/factories/labels.rb
@@ -37,6 +37,4 @@
   end
 
   factory :admin_label, traits: [:base_label], class: 'Label'
-
-  factory :abuse_report_label, traits: [:base_label], class: 'Admin::AbuseReportLabel'
 end
diff --git a/spec/frontend/admin/abuse_report/mock_data.js b/spec/frontend/admin/abuse_report/mock_data.js
index f276156c8f4f4b74..beb7ea8273d8694f 100644
--- a/spec/frontend/admin/abuse_report/mock_data.js
+++ b/spec/frontend/admin/abuse_report/mock_data.js
@@ -80,7 +80,7 @@ export const mockAbuseReport = {
 };
 
 export const mockLabel1 = {
-  id: 'gid://gitlab/Admin::AbuseReportLabel/1',
+  id: 'gid://gitlab/AntiAbuse::Reports::Label/1',
   title: 'Uno',
   color: '#F0AD4E',
   textColor: '#FFFFFF',
@@ -88,7 +88,7 @@ export const mockLabel1 = {
 };
 
 export const mockLabel2 = {
-  id: 'gid://gitlab/Admin::AbuseReportLabel/2',
+  id: 'gid://gitlab/AntiAbuse::Reports::Label/2',
   title: 'Dos',
   color: '#F0AD4E',
   textColor: '#FFFFFF',
@@ -124,7 +124,7 @@ export const mockCreateLabelResponse = {
   data: {
     labelCreate: {
       label: {
-        id: 'gid://gitlab/Admin::AbuseReportLabel/1',
+        id: 'gid://gitlab/AntiAbuse::Reports::Label/1',
         color: '#ed9121',
         description: null,
         title: 'abuse report label',
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index 6ba1843247a4cbaa..a9c71328cfac68a0 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -22,6 +22,15 @@
     it { is_expected.to have_many(:admin_abuse_report_assignees).class_name('Admin::AbuseReportAssignee') }
     it { is_expected.to have_many(:assignees).class_name('User').through(:admin_abuse_report_assignees) }
 
+    it do
+      is_expected.to have_many(:label_links).inverse_of(:abuse_report).class_name('AntiAbuse::Reports::LabelLink')
+    end
+
+    it do
+      is_expected.to have_many(:labels).through(:label_links).source(:abuse_report_label)
+        .class_name('AntiAbuse::Reports::Label')
+    end
+
     it "aliases reporter to author" do
       expect(subject.author).to be(subject.reporter)
     end
diff --git a/spec/models/anti_abuse/reports/label_link_spec.rb b/spec/models/anti_abuse/reports/label_link_spec.rb
new file mode 100644
index 0000000000000000..db7e90097bb8738e
--- /dev/null
+++ b/spec/models/anti_abuse/reports/label_link_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe AntiAbuse::Reports::LabelLink, feature_category: :insider_threat do
+  subject(:instance) { build(:abuse_report_label_link) }
+
+  it { is_expected.to be_valid }
+
+  it { is_expected.to belong_to(:abuse_report).inverse_of(:label_links) }
+  it { is_expected.to belong_to(:abuse_report_label).class_name('AntiAbuse::Reports::Label').inverse_of(:label_links) }
+
+  describe 'validations' do
+    it { is_expected.to validate_presence_of(:abuse_report) }
+    it { is_expected.to validate_presence_of(:abuse_report_label) }
+    it { is_expected.to validate_uniqueness_of(:abuse_report_label).scoped_to([:abuse_report_id]) }
+  end
+end
diff --git a/spec/models/anti_abuse/reports/label_spec.rb b/spec/models/anti_abuse/reports/label_spec.rb
new file mode 100644
index 0000000000000000..6a86a5d9ffb6b82c
--- /dev/null
+++ b/spec/models/anti_abuse/reports/label_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe AntiAbuse::Reports::Label, feature_category: :insider_threat do
+  subject(:record) { build(:abuse_report_label) }
+
+  it_behaves_like 'BaseLabel', factory_name: :abuse_report_label
+
+  describe 'associations' do
+    it 'has many label links' do
+      expect(record).to have_many(:label_links).with_foreign_key(:abuse_report_label_id).inverse_of(:abuse_report_label)
+        .class_name('AntiAbuse::Reports::LabelLink')
+    end
+
+    it { is_expected.to have_many(:abuse_reports).through(:label_links) }
+  end
+
+  describe 'validation' do
+    it { is_expected.to validate_uniqueness_of(:title) }
+    it { is_expected.to validate_length_of(:description).is_at_most(500) }
+  end
+end
diff --git a/spec/requests/api/graphql/mutations/admin/abuse_report_labels/create_spec.rb b/spec/requests/api/graphql/mutations/admin/abuse_report_labels/create_spec.rb
index 2a20d96d9c8c21fb..8aa422a724a5593a 100644
--- a/spec/requests/api/graphql/mutations/admin/abuse_report_labels/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/admin/abuse_report_labels/create_spec.rb
@@ -26,7 +26,7 @@ def mutation_response
     it_behaves_like 'a mutation that returns a top-level access error'
 
     it 'does not create the label' do
-      expect { subject }.not_to change { Admin::AbuseReportLabel.count }
+      expect { subject }.not_to change { AntiAbuse::Reports::Label.count }
     end
   end
 
@@ -34,7 +34,7 @@ def mutation_response
     let_it_be(:current_user) { create(:admin) }
 
     it 'creates the label' do
-      expect { subject }.to change { Admin::AbuseReportLabel.count }.to(1)
+      expect { subject }.to change { AntiAbuse::Reports::Label.count }.to(1)
 
       expect(mutation_response).to include('label' => a_hash_including(params))
     end
@@ -43,7 +43,7 @@ def mutation_response
       it 'does not create the label', :aggregate_failures do
         create(:abuse_report_label, title: params['title'])
 
-        expect { subject }.not_to change { Label.count }
+        expect { subject }.not_to change { AntiAbuse::Reports::Label.count }
 
         expect(mutation_response).to include({
           'label' => nil,
diff --git a/spec/serializers/anti_abuse/reports/label_entity_spec.rb b/spec/serializers/anti_abuse/reports/label_entity_spec.rb
index 2ee7a764d1a290d3..906e76539105e377 100644
--- a/spec/serializers/anti_abuse/reports/label_entity_spec.rb
+++ b/spec/serializers/anti_abuse/reports/label_entity_spec.rb
@@ -3,7 +3,7 @@
 require "spec_helper"
 
 RSpec.describe AntiAbuse::Reports::LabelEntity, feature_category: :insider_threat do
-  let_it_be(:abuse_report_label) { build_stubbed(:label) }
+  let_it_be(:abuse_report_label) { build_stubbed(:abuse_report_label) }
 
   let(:entity) do
     described_class.new(abuse_report_label)
diff --git a/spec/services/admin/abuse_report_labels/create_service_spec.rb b/spec/services/admin/abuse_report_labels/create_service_spec.rb
index 168229d6ed91d7d5..addb8536fbe6b6fd 100644
--- a/spec/services/admin/abuse_report_labels/create_service_spec.rb
+++ b/spec/services/admin/abuse_report_labels/create_service_spec.rb
@@ -12,16 +12,16 @@
 
     shared_examples 'creates a label with the correct values' do
       it 'creates a label with the correct values', :aggregate_failures do
-        expect { execute }.to change { Admin::AbuseReportLabel.count }.from(0).to(1)
+        expect { execute }.to change { AntiAbuse::Reports::Label.count }.from(0).to(1)
 
-        label = Admin::AbuseReportLabel.last
+        label = AntiAbuse::Reports::Label.last
         expect(label.title).to eq params[:title]
         expect(label.color).to eq color_in_hex
       end
 
       it 'returns the persisted label' do
         result = execute
-        expect(result).to be_an_instance_of(Admin::AbuseReportLabel)
+        expect(result).to be_an_instance_of(AntiAbuse::Reports::Label)
         expect(result.persisted?).to eq true
       end
     end
@@ -39,7 +39,7 @@
       let!(:existing_label) { create(:abuse_report_label, title: params[:title]) }
 
       it 'does not create the label' do
-        expect { execute }.not_to change { Admin::AbuseReportLabel.count }
+        expect { execute }.not_to change { AntiAbuse::Reports::Label.count }
       end
 
       it 'returns the label with errors' do
-- 
GitLab