Skip to content
Snippets Groups Projects
Commit 6b2b3d7f authored by Jan Provaznik's avatar Jan Provaznik Committed by Sean McGivern
Browse files

Resource event model

parent bd659f70
No related branches found
No related tags found
1 merge request!10495Merge Requests - Assignee
# frozen_string_literal: true
# == LabelEventable concern
#
# Contains functionality related to objects that support adding/removing labels.
#
# This concern is not used yet, it will be used for:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/48483
module LabelEventable
extend ActiveSupport::Concern
included do
has_many :resource_label_events
end
end
Loading
Loading
@@ -12,6 +12,7 @@ class Issue < ActiveRecord::Base
include TimeTrackable
include ThrottledTouch
include IgnorableColumn
include LabelEventable
 
ignore_column :assignee_id, :branch_name, :deleted_at
 
Loading
Loading
Loading
Loading
@@ -10,6 +10,7 @@ class MergeRequest < ActiveRecord::Base
include EachBatch
include ThrottledTouch
include Gitlab::Utils::StrongMemoize
include LabelEventable
 
ignore_column :locked_at,
:ref_fetched,
Loading
Loading
# frozen_string_literal: true
# This model is not used yet, it will be used for:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/48483
class ResourceLabelEvent < ActiveRecord::Base
belongs_to :user
belongs_to :issue
belongs_to :merge_request
belongs_to :label
validates :user, presence: true, on: :create
validates :label, presence: true, on: :create
validate :exactly_one_issuable
enum action: {
add: 1,
remove: 2
}
def self.issuable_columns
%i(issue_id merge_request_id).freeze
end
def issuable
issue || merge_request
end
private
def exactly_one_issuable
if self.class.issuable_columns.count { |attr| self[attr] } != 1
errors.add(:base, "Exactly one of #{self.class.issuable_columns.join(', ')} is required")
end
end
end
# frozen_string_literal: true
# This service is not used yet, it will be used for:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/48483
module ResourceEvents
class ChangeLabelsService
attr_reader :resource, :user
def initialize(resource, user)
@resource, @user = resource, user
end
def execute(added_labels: [], removed_labels: [])
label_hash = {
resource_column(resource) => resource.id,
user_id: user.id,
created_at: Time.now
}
labels = added_labels.map do |label|
label_hash.merge(label_id: label.id, action: ResourceLabelEvent.actions['add'])
end
labels += removed_labels.map do |label|
label_hash.merge(label_id: label.id, action: ResourceLabelEvent.actions['remove'])
end
Gitlab::Database.bulk_insert(ResourceLabelEvent.table_name, labels)
end
private
def resource_column(resource)
case resource
when Issue
:issue_id
when MergeRequest
:merge_request_id
else
raise ArgumentError, "Unknown resource type #{resource.class.name}"
end
end
end
end
---
title: Add new model for tracking label events.
merge_request:
author:
type: added
# frozen_string_literal: true
class CreateResourceLabelEvents < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table :resource_label_events, id: :bigserial do |t|
t.integer :action, null: false
t.references :issue, null: true, index: true, foreign_key: { on_delete: :cascade }
t.references :merge_request, null: true, index: true, foreign_key: { on_delete: :cascade }
t.references :label, index: true, foreign_key: { on_delete: :nullify }
t.references :user, index: true, foreign_key: { on_delete: :nullify }
t.datetime_with_timezone :created_at, null: false
end
end
end
Loading
Loading
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
 
ActiveRecord::Schema.define(version: 20180722103201) do
ActiveRecord::Schema.define(version: 20180726172057) do
 
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
Loading
Loading
@@ -1787,6 +1787,20 @@ ActiveRecord::Schema.define(version: 20180722103201) do
add_index "remote_mirrors", ["last_successful_update_at"], name: "index_remote_mirrors_on_last_successful_update_at", using: :btree
add_index "remote_mirrors", ["project_id"], name: "index_remote_mirrors_on_project_id", using: :btree
 
create_table "resource_label_events", id: :bigserial, force: :cascade do |t|
t.integer "action", null: false
t.integer "issue_id"
t.integer "merge_request_id"
t.integer "label_id"
t.integer "user_id"
t.datetime_with_timezone "created_at", null: false
end
add_index "resource_label_events", ["issue_id"], name: "index_resource_label_events_on_issue_id", using: :btree
add_index "resource_label_events", ["label_id"], name: "index_resource_label_events_on_label_id", using: :btree
add_index "resource_label_events", ["merge_request_id"], name: "index_resource_label_events_on_merge_request_id", using: :btree
add_index "resource_label_events", ["user_id"], name: "index_resource_label_events_on_user_id", using: :btree
create_table "routes", force: :cascade do |t|
t.integer "source_id", null: false
t.string "source_type", null: false
Loading
Loading
@@ -2337,6 +2351,10 @@ ActiveRecord::Schema.define(version: 20180722103201) do
add_foreign_key "push_event_payloads", "events", name: "fk_36c74129da", on_delete: :cascade
add_foreign_key "releases", "projects", name: "fk_47fe2a0596", on_delete: :cascade
add_foreign_key "remote_mirrors", "projects", on_delete: :cascade
add_foreign_key "resource_label_events", "issues", on_delete: :cascade
add_foreign_key "resource_label_events", "labels", on_delete: :nullify
add_foreign_key "resource_label_events", "merge_requests", on_delete: :cascade
add_foreign_key "resource_label_events", "users", on_delete: :nullify
add_foreign_key "services", "projects", name: "fk_71cce407f9", on_delete: :cascade
add_foreign_key "snippets", "projects", name: "fk_be41fd4bb7", on_delete: :cascade
add_foreign_key "subscriptions", "projects", on_delete: :cascade
Loading
Loading
# frozen_string_literal: true
FactoryBot.define do
factory :resource_label_event do
user { issue.project.creator }
action :add
label
issue
end
end
Loading
Loading
@@ -7,6 +7,7 @@ issues:
- updated_by
- milestone
- notes
- resource_label_events
- label_links
- labels
- last_edited_by
Loading
Loading
@@ -76,6 +77,7 @@ merge_requests:
- updated_by
- milestone
- notes
- resource_label_events
- label_links
- labels
- last_edited_by
Loading
Loading
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ResourceLabelEvent, type: :model do
subject { build(:resource_label_event) }
let(:issue) { create(:issue) }
let(:merge_request) { create(:merge_request) }
describe 'associations' do
it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:issue) }
it { is_expected.to belong_to(:merge_request) }
it { is_expected.to belong_to(:label) }
end
describe 'validations' do
it { is_expected.to be_valid }
it { is_expected.to validate_presence_of(:label) }
it { is_expected.to validate_presence_of(:user) }
describe 'Issuable validation' do
it 'is invalid if issue_id and merge_request_id are missing' do
subject.attributes = { issue: nil, merge_request: nil }
expect(subject).to be_invalid
end
it 'is invalid if issue_id and merge_request_id are set' do
subject.attributes = { issue: issue, merge_request: merge_request }
expect(subject).to be_invalid
end
it 'is valid if only issue_id is set' do
subject.attributes = { issue: issue, merge_request: nil }
expect(subject).to be_valid
end
it 'is valid if only merge_request_id is set' do
subject.attributes = { merge_request: merge_request, issue: nil }
expect(subject).to be_valid
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe ResourceEvents::ChangeLabelsService do
set(:project) { create(:project) }
set(:author) { create(:user) }
let(:resource) { create(:issue, project: project) }
describe '.change_labels' do
subject { described_class.new(resource, author).execute(added_labels: added, removed_labels: removed) }
let(:labels) { create_list(:label, 2, project: project) }
def expect_label_event(event, label, action)
expect(event.user).to eq(author)
expect(event.label).to eq(label)
expect(event.action).to eq(action)
end
context 'when adding a label' do
let(:added) { [labels[0]] }
let(:removed) { [] }
it 'creates new label event' do
expect { subject }.to change { resource.resource_label_events.count }.from(0).to(1)
expect_label_event(resource.resource_label_events.first, labels[0], 'add')
end
end
context 'when removing a label' do
let(:added) { [] }
let(:removed) { [labels[1]] }
it 'creates new label event' do
expect { subject }.to change { resource.resource_label_events.count }.from(0).to(1)
expect_label_event(resource.resource_label_events.first, labels[1], 'remove')
end
end
context 'when both adding and removing labels' do
let(:added) { [labels[0]] }
let(:removed) { [labels[1]] }
it 'creates all label events in a single query' do
expect(Gitlab::Database).to receive(:bulk_insert).once.and_call_original
expect { subject }.to change { resource.resource_label_events.count }.from(0).to(2)
end
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment