Commit 3faa14e7 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'reduce-observers' into 'master'

Move issue create/update code to services

Reduce observers role in GitLab code
parents a23d0ef9 49f977d6
...@@ -59,9 +59,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -59,9 +59,7 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def create def create
@issue = @project.issues.new(params[:issue]) @issue = Issues::CreateService.new(project, current_user, params[:issue]).execute
@issue.author = current_user
@issue.save
respond_to do |format| respond_to do |format|
format.html do format.html do
...@@ -76,8 +74,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -76,8 +74,7 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def update def update
@issue.update_attributes(params[:issue]) @issue = Issues::UpdateService.new(project, current_user, params[:issue]).execute(issue)
@issue.reset_events_cache
respond_to do |format| respond_to do |format|
format.js format.js
......
class IssueObserver < BaseObserver
def after_create(issue)
notification.new_issue(issue, current_user)
event_service.open_issue(issue, current_user)
issue.create_cross_references!(issue.project, current_user)
execute_hooks(issue)
end
def after_close(issue, transition)
notification.close_issue(issue, current_user)
event_service.close_issue(issue, current_user)
create_note(issue)
execute_hooks(issue)
end
def after_reopen(issue, transition)
event_service.reopen_issue(issue, current_user)
create_note(issue)
execute_hooks(issue)
end
def after_update(issue)
if issue.is_being_reassigned?
notification.reassigned_issue(issue, current_user)
create_assignee_note(issue)
end
issue.notice_added_references(issue.project, current_user)
execute_hooks(issue)
end
protected
# Create issue note with service comment like 'Status changed to closed'
def create_note(issue)
Note.create_status_change_note(issue, issue.project, current_user, issue.state, current_commit)
end
def create_assignee_note(issue)
Note.create_assignee_change_note(issue, issue.project, current_user, issue.assignee)
end
def execute_hooks(issue)
issue.project.execute_hooks(issue.to_hook_data, :issue_hooks)
end
end
...@@ -16,4 +16,16 @@ class BaseService ...@@ -16,4 +16,16 @@ class BaseService
def can?(object, action, subject) def can?(object, action, subject)
abilities.allowed?(object, action, subject) abilities.allowed?(object, action, subject)
end end
def notification_service
NotificationService.new
end
def event_service
EventCreateService.new
end
def log_info message
Gitlab::AppLogger.info message
end
end end
...@@ -86,10 +86,9 @@ class GitPushService ...@@ -86,10 +86,9 @@ class GitPushService
author = commit_user(commit) author = commit_user(commit)
if !issues_to_close.empty? && is_default_branch if !issues_to_close.empty? && is_default_branch
Thread.current[:current_user] = author issues_to_close.each do |issue|
Thread.current[:current_commit] = commit Issues::CloseService.new(project, author, {}).execute(issue, commit)
end
issues_to_close.each { |i| i.close && i.save }
end end
# Create cross-reference notes for any other references. Omit any issues that were referenced in an # Create cross-reference notes for any other references. Omit any issues that were referenced in an
......
module Issues
class BaseService < ::BaseService
private
def create_assignee_note(issue)
Note.create_assignee_change_note(issue, issue.project, current_user, issue.assignee)
end
def execute_hooks(issue)
issue.project.execute_hooks(issue.to_hook_data, :issue_hooks)
end
end
end
module Issues
class CloseService < Issues::BaseService
def execute(issue, commit = nil)
if issue.close
notification_service.close_issue(issue, current_user)
event_service.close_issue(issue, current_user)
create_note(issue, commit)
execute_hooks(issue)
end
issue
end
private
def create_note(issue, current_commit)
Note.create_status_change_note(issue, issue.project, current_user, issue.state, current_commit)
end
end
end
module Issues
class CreateService < Issues::BaseService
def execute
issue = project.issues.new(params)
issue.author = current_user
if issue.save
notification_service.new_issue(issue, current_user)
event_service.open_issue(issue, current_user)
issue.create_cross_references!(issue.project, current_user)
execute_hooks(issue)
end
issue
end
end
end
module Issues
class ReopenService < Issues::BaseService
def execute(issue)
if issue.reopen
event_service.reopen_issue(issue, current_user)
create_note(issue)
execute_hooks(issue)
end
issue
end
private
def create_note(issue)
Note.create_status_change_note(issue, issue.project, current_user, issue.state, nil)
end
end
end
module Issues
class UpdateService < Issues::BaseService
def execute(issue)
state = params.delete('state_event')
case state
when 'reopen'
Issues::ReopenService.new(project, current_user, {}).execute(issue)
when 'close'
Issues::CloseService.new(project, current_user, {}).execute(issue)
end
if params.present? && issue.update_attributes(params)
issue.reset_events_cache
if issue.previous_changes.include?('assignee_id')
notification_service.reassigned_issue(issue, current_user)
create_assignee_note(issue)
end
issue.notice_added_references(issue.project, current_user)
execute_hooks(issue)
end
issue
end
end
end
...@@ -21,7 +21,6 @@ module Gitlab ...@@ -21,7 +21,6 @@ module Gitlab
# Activate observers that should always be running. # Activate observers that should always be running.
config.active_record.observers = :milestone_observer, config.active_record.observers = :milestone_observer,
:project_activity_cache_observer, :project_activity_cache_observer,
:issue_observer,
:key_observer, :key_observer,
:merge_request_observer, :merge_request_observer,
:note_observer, :note_observer,
......
...@@ -48,19 +48,17 @@ module API ...@@ -48,19 +48,17 @@ module API
# Example Request: # Example Request:
# POST /projects/:id/issues # POST /projects/:id/issues
post ":id/issues" do post ":id/issues" do
set_current_user_for_thread do
required_attributes! [:title] required_attributes! [:title]
attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id] attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id]
attrs[:label_list] = params[:labels] if params[:labels].present? attrs[:label_list] = params[:labels] if params[:labels].present?
@issue = user_project.issues.new attrs issue = ::Issues::CreateService.new(user_project, current_user, attrs).execute
@issue.author = current_user
if @issue.save if issue.valid?
present @issue, with: Entities::Issue present issue, with: Entities::Issue
else else
not_found! not_found!
end end
end end
end
# Update an existing issue # Update an existing issue
# #
...@@ -76,20 +74,20 @@ module API ...@@ -76,20 +74,20 @@ module API
# Example Request: # Example Request:
# PUT /projects/:id/issues/:issue_id # PUT /projects/:id/issues/:issue_id
put ":id/issues/:issue_id" do put ":id/issues/:issue_id" do
set_current_user_for_thread do issue = user_project.issues.find(params[:issue_id])
@issue = user_project.issues.find(params[:issue_id]) authorize! :modify_issue, issue
authorize! :modify_issue, @issue
attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event] attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event]
attrs[:label_list] = params[:labels] if params[:labels].present? attrs[:label_list] = params[:labels] if params[:labels].present?
if @issue.update_attributes attrs issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue)
present @issue, with: Entities::Issue
if issue.valid?
present issue, with: Entities::Issue
else else
not_found! not_found!
end end
end end
end
# Delete a project issue (deprecated) # Delete a project issue (deprecated)
# #
......
require 'spec_helper'
describe IssueObserver do
let(:some_user) { create :user }
let(:assignee) { create :user }
let(:author) { create :user }
let(:mock_issue) { create(:issue, assignee: assignee, author: author) }
before { subject.stub(:current_user).and_return(some_user) }
before { subject.stub(:current_commit).and_return(nil) }
before { subject.stub(notification: double('NotificationService').as_null_object) }
before { mock_issue.project.stub_chain(:repository, :commit).and_return(nil) }
subject { IssueObserver.instance }
describe '#after_create' do
it 'trigger notification to send emails' do
subject.should_receive(:notification)
subject.after_create(mock_issue)
end
it 'should create cross-reference notes' do
other_issue = create(:issue)
mock_issue.stub(references: [other_issue])
Note.should_receive(:create_cross_reference_note).with(other_issue, mock_issue,
some_user, mock_issue.project)
subject.after_create(mock_issue)
end
end
context '#after_close' do
context 'a status "closed"' do
before { mock_issue.stub(state: 'closed') }
it 'note is created if the issue is being closed' do
Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'closed', nil)
subject.after_close(mock_issue, nil)
end
it 'trigger notification to send emails' do
subject.notification.should_receive(:close_issue).with(mock_issue, some_user)
subject.after_close(mock_issue, nil)
end
it 'appends a mention to the closing commit if one is present' do
commit = double('commit', gfm_reference: 'commit 123456')
subject.stub(current_commit: commit)
Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'closed', commit)
subject.after_close(mock_issue, nil)
end
end
context 'a status "reopened"' do
before { mock_issue.stub(state: 'reopened') }
it 'note is created if the issue is being reopened' do
Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'reopened', nil)
subject.after_reopen(mock_issue, nil)
end
end
end
context '#after_update' do
before(:each) do
mock_issue.stub(:is_being_reassigned?).and_return(false)
end
context 'notification' do
it 'triggered if the issue is being reassigned' do
mock_issue.should_receive(:is_being_reassigned?).and_return(true)
subject.should_receive(:notification)
subject.after_update(mock_issue)
end
it 'is not triggered if the issue is not being reassigned' do
mock_issue.should_receive(:is_being_reassigned?).and_return(false)
subject.should_not_receive(:notification)
subject.after_update(mock_issue)
end
end
context 'cross-references' do
it 'notices added references' do
mock_issue.should_receive(:notice_added_references)
subject.after_update(mock_issue)
end
end
end
end
...@@ -170,16 +170,10 @@ describe GitPushService do ...@@ -170,16 +170,10 @@ describe GitPushService do
Issue.find(issue.id).should be_closed Issue.find(issue.id).should be_closed
end end
it "passes the closing commit as a thread-local" do
service.execute(project, user, @oldrev, @newrev, @ref)
Thread.current[:current_commit].should == closing_commit
end
it "doesn't create cross-reference notes for a closing reference" do it "doesn't create cross-reference notes for a closing reference" do
expect { expect {
service.execute(project, user, @oldrev, @newrev, @ref) service.execute(project, user, @oldrev, @newrev, @ref)
}.not_to change { Note.where(project_id: project.id, system: true).count } }.not_to change { Note.where(project_id: project.id, system: true, commit_id: closing_commit.id).count }
end end
it "doesn't close issues when pushed to non-default branches" do it "doesn't close issues when pushed to non-default branches" do
......
require 'spec_helper'
describe Issues::CloseService do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:issue) { create(:issue, assignee: user2) }
before do
project.team << [user, :master]
project.team << [user2, :developer]
end
describe :execute do
context "valid params" do
before do
@issue = Issues::CloseService.new(project, user, {}).execute(issue)
end
it { @issue.should be_valid }
it { @issue.should be_closed }
it 'should send email to user2 about assign of new issue' do
email = ActionMailer::Base.deliveries.last
email.to.first.should == user2.email
email.subject.should include(issue.title)
end
it 'should create system note about issue reassign' do
note = @issue.notes.last
note.note.should include "Status changed to closed"
end
end
end
end
require 'spec_helper'
describe Issues::CreateService do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
describe :execute do
context "valid params" do
before do
project.team << [user, :master]
opts = {
title: 'Awesome issue',
description: 'please fix'
}
@issue = Issues::CreateService.new(project, user, opts).execute
end
it { @issue.should be_valid }
it { @issue.title.should == 'Awesome issue' }
end
end
end
require 'spec_helper'
describe Issues::UpdateService do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:issue) { create(:issue) }
before do
project.team << [user, :master]
project.team << [user2, :developer]
end
describe :execute do
context "valid params" do
before do
opts = {
title: 'New title',
description: 'Also please fix',
assignee_id: user2.id,
state_event: 'close'
}
@issue = Issues::UpdateService.new(project, user, opts).execute(issue)
end
it { @issue.should be_valid }
it { @issue.title.should == 'New title' }
it { @issue.assignee.should == user2 }
it { @issue.should be_closed }
it 'should send email to user2 about assign of new issue' do
email = ActionMailer::Base.deliveries.last
email.to.first.should == user2.email
email.subject.should include(issue.title)
end
it 'should create system note about issue reassign' do
note = @issue.notes.last
note.note.should include "Reassigned to \@#{user2.username}"
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment