From 0a0710a548b0d1f722c0f58b0fef5bbfc71ef831 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 28 Feb 2012 20:09:23 +0200 Subject: [PATCH] Event entity created Observe issue, merge request, note creation - create event register push event --- app/models/activity_observer.rb | 12 ++ app/models/event.rb | 36 ++++++ app/models/project.rb | 13 ++ app/models/users_project.rb | 1 - app/models/wiki.rb | 14 +++ app/workers/post_receive.rb | 1 + config/application.rb | 2 +- db/migrate/20120228130210_create_events.rb | 14 +++ .../20120228134252_add_action_to_event.rb | 5 + db/schema.rb | 13 +- spec/factories.rb | 9 ++ spec/models/activity_observer_spec.rb | 44 +++++++ spec/models/event_spec.rb | 32 +++++ spec/models/project_hooks_spec.rb | 115 ++++++++++++++++++ spec/models/project_spec.rb | 102 +--------------- spec/models/users_project_spec.rb | 1 - 16 files changed, 310 insertions(+), 104 deletions(-) create mode 100644 app/models/activity_observer.rb create mode 100644 app/models/event.rb create mode 100644 db/migrate/20120228130210_create_events.rb create mode 100644 db/migrate/20120228134252_add_action_to_event.rb create mode 100644 spec/models/activity_observer_spec.rb create mode 100644 spec/models/event_spec.rb create mode 100644 spec/models/project_hooks_spec.rb diff --git a/app/models/activity_observer.rb b/app/models/activity_observer.rb new file mode 100644 index 00000000..46564161 --- /dev/null +++ b/app/models/activity_observer.rb @@ -0,0 +1,12 @@ +class ActivityObserver < ActiveRecord::Observer + observe :issue, :merge_request, :note + + def after_create(record) + Event.create( + :project => record.project, + :target_id => record.id, + :target_type => record.class.name, + :action => Event.determine_action(record) + ) + end +end diff --git a/app/models/event.rb b/app/models/event.rb new file mode 100644 index 00000000..5cb75247 --- /dev/null +++ b/app/models/event.rb @@ -0,0 +1,36 @@ +class Event < ActiveRecord::Base + Created = 1 + Updated = 2 + Closed = 3 + Reopened = 4 + Pushed = 5 + Commented = 6 + + belongs_to :project + belongs_to :target, :polymorphic => true + + serialize :data + + def self.determine_action(record) + if [Issue, MergeRequest].include? record.class + Event::Created + elsif record.kind_of? Note + Event::Commented + end + end +end +# == Schema Information +# +# Table name: events +# +# id :integer not null, primary key +# target_type :string(255) +# target_id :integer +# title :string(255) +# data :text +# project_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# action :integer +# + diff --git a/app/models/project.rb b/app/models/project.rb index b59dcd80..4d1d4e79 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -3,6 +3,7 @@ require "grit" class Project < ActiveRecord::Base belongs_to :owner, :class_name => "User" + has_many :events, :dependent => :destroy has_many :merge_requests, :dependent => :destroy has_many :issues, :dependent => :destroy, :order => "position" has_many :users_projects, :dependent => :destroy @@ -89,6 +90,16 @@ class Project < ActiveRecord::Base [GIT_HOST['host'], code].join("/") end + def observe_push(oldrev, newrev, ref) + data = web_hook_data(oldrev, newrev, ref) + + Event.create( + :project => self, + :action => Event::Pushed, + :data => data + ) + end + def execute_web_hooks(oldrev, newrev, ref) ref_parts = ref.split('/') @@ -96,6 +107,7 @@ class Project < ActiveRecord::Base return if ref_parts[1] !~ /heads/ || oldrev == "00000000000000000000000000000000" data = web_hook_data(oldrev, newrev, ref) + web_hooks.each { |web_hook| web_hook.execute(data) } end @@ -364,5 +376,6 @@ end # issues_enabled :boolean default(TRUE), not null # wall_enabled :boolean default(TRUE), not null # merge_requests_enabled :boolean default(TRUE), not null +# wiki_enabled :boolean default(TRUE), not null # diff --git a/app/models/users_project.rb b/app/models/users_project.rb index bc625225..726a85ae 100644 --- a/app/models/users_project.rb +++ b/app/models/users_project.rb @@ -80,7 +80,6 @@ end # project_id :integer not null # created_at :datetime # updated_at :datetime -# repo_access :integer default(0), not null # project_access :integer default(0), not null # diff --git a/app/models/wiki.rb b/app/models/wiki.rb index 62ac4cb8..ad3e4a38 100644 --- a/app/models/wiki.rb +++ b/app/models/wiki.rb @@ -31,3 +31,17 @@ class Wiki < ActiveRecord::Base end end +# == Schema Information +# +# Table name: wikis +# +# id :integer not null, primary key +# title :string(255) +# content :text +# project_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# slug :string(255) +# user_id :integer +# + diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index 922a66eb..81654dfa 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -5,6 +5,7 @@ class PostReceive project = Project.find_by_path(reponame) return false if project.nil? + project.observe_push(oldrev, newrev, ref) project.execute_web_hooks(oldrev, newrev, ref) end end diff --git a/config/application.rb b/config/application.rb index bdd5bbf3..a3ef29c0 100644 --- a/config/application.rb +++ b/config/application.rb @@ -23,7 +23,7 @@ module Gitlab # config.plugins = [ :exception_notification, :ssl_requirement, :all ] # Activate observers that should always be running. - config.active_record.observers = :mailer_observer + config.active_record.observers = :mailer_observer, :activity_observer # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. diff --git a/db/migrate/20120228130210_create_events.rb b/db/migrate/20120228130210_create_events.rb new file mode 100644 index 00000000..c01f557a --- /dev/null +++ b/db/migrate/20120228130210_create_events.rb @@ -0,0 +1,14 @@ +class CreateEvents < ActiveRecord::Migration + def change + create_table :events do |t| + t.string :target_type, :null => true + t.integer :target_id, :null => true + + t.string :title, :null => true + t.text :data, :null => true + t.integer :project_id, :null => true + + t.timestamps + end + end +end diff --git a/db/migrate/20120228134252_add_action_to_event.rb b/db/migrate/20120228134252_add_action_to_event.rb new file mode 100644 index 00000000..aade3d90 --- /dev/null +++ b/db/migrate/20120228134252_add_action_to_event.rb @@ -0,0 +1,5 @@ +class AddActionToEvent < ActiveRecord::Migration + def change + add_column :events, :action, :integer, :null => true + end +end diff --git a/db/schema.rb b/db/schema.rb index c32df7ea..a0baf8e2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,18 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20120219193300) do +ActiveRecord::Schema.define(:version => 20120228134252) do + + create_table "events", :force => true do |t| + t.string "target_type" + t.integer "target_id" + t.string "title" + t.text "data" + t.integer "project_id" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.integer "action" + end create_table "issues", :force => true do |t| t.string "title" diff --git a/spec/factories.rb b/spec/factories.rb index 6d7a4cb7..2ca8d764 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -32,10 +32,14 @@ end Factory.add(:issue, Issue) do |obj| obj.title = Faker::Lorem.sentence + obj.author = Factory :user + obj.assignee = Factory :user end Factory.add(:merge_request, MergeRequest) do |obj| obj.title = Faker::Lorem.sentence + obj.author = Factory :user + obj.assignee = Factory :user obj.source_branch = "master" obj.target_branch = "master" obj.closed = false @@ -64,3 +68,8 @@ Factory.add(:wikis, WebHook) do |obj| obj.title = Faker::Lorem.sentence obj.content = Faker::Lorem.sentence end + +Factory.add(:event, Event) do |obj| + obj.title = Faker::Lorem.sentence + obj.project = Factory(:project) +end diff --git a/spec/models/activity_observer_spec.rb b/spec/models/activity_observer_spec.rb new file mode 100644 index 00000000..9cd0dfb6 --- /dev/null +++ b/spec/models/activity_observer_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +describe ActivityObserver do + let(:project) { Factory :project } + + def self.it_should_be_valid_event + it { @event.should_not be_nil } + it { @event.project.should == project } + end + + describe "Merge Request created" do + before do + @merge_request = Factory :merge_request, :project => project + @event = Event.last + end + + it_should_be_valid_event + it { @event.action.should == Event::Created } + it { @event.target.should == @merge_request } + end + + describe "Issue created" do + before do + @issue = Factory :issue, :project => project + @event = Event.last + end + + it_should_be_valid_event + it { @event.action.should == Event::Created } + it { @event.target.should == @issue } + end + + describe "Issue commented" do + before do + @issue = Factory :issue, :project => project + @note = Factory :note, :noteable => @issue, :project => project + @event = Event.last + end + + it_should_be_valid_event + it { @event.action.should == Event::Commented } + it { @event.target.should == @note } + end +end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb new file mode 100644 index 00000000..50266fcf --- /dev/null +++ b/spec/models/event_spec.rb @@ -0,0 +1,32 @@ +# == Schema Information +# +# Table name: events +# +# id :integer not null, primary key +# target_type :string(255) +# target_id :integer +# title :string(255) +# data :text +# project_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# action :integer +# + +require 'spec_helper' + +describe Event do + describe "Associations" do + it { should belong_to(:project) } + end + + describe "Creation" do + before do + @event = Factory :event + end + + it "should create a valid event" do + @event.should be_valid + end + end +end diff --git a/spec/models/project_hooks_spec.rb b/spec/models/project_hooks_spec.rb new file mode 100644 index 00000000..841c85ba --- /dev/null +++ b/spec/models/project_hooks_spec.rb @@ -0,0 +1,115 @@ +require 'spec_helper' + +describe Project, "Hooks" do + let(:project) { Factory :project } + + describe "Post Receive Event" do + it "should create push event" do + oldrev, newrev, ref = '00000000000000000000000000000000', 'newrev', 'refs/heads/master' + project.observe_push(oldrev, newrev, ref) + event = Event.last + + event.should_not be_nil + event.project.should == project + event.action.should == Event::Pushed + event.data == project.web_hook_data(oldrev, newrev, ref) + end + end + + describe "Web hooks" do + context "with no web hooks" do + it "raises no errors" do + lambda { + project.execute_web_hooks('oldrev', 'newrev', 'ref') + }.should_not raise_error + end + end + + context "with web hooks" do + before do + @webhook = Factory(:web_hook) + @webhook_2 = Factory(:web_hook) + project.web_hooks << [@webhook, @webhook_2] + end + + it "executes multiple web hook" do + @webhook.should_receive(:execute).once + @webhook_2.should_receive(:execute).once + + project.execute_web_hooks('oldrev', 'newrev', 'refs/heads/master') + end + end + + context "does not execute web hooks" do + before do + @webhook = Factory(:web_hook) + project.web_hooks << [@webhook] + end + + it "when pushing a branch for the first time" do + @webhook.should_not_receive(:execute) + project.execute_web_hooks('00000000000000000000000000000000', 'newrev', 'refs/heads/master') + end + + it "when pushing tags" do + @webhook.should_not_receive(:execute) + project.execute_web_hooks('oldrev', 'newrev', 'refs/tags/v1.0.0') + end + end + + context "when pushing new branches" do + + end + + context "when gathering commit data" do + before do + @oldrev, @newrev, @ref = project.fresh_commits(2).last.sha, project.fresh_commits(2).first.sha, 'refs/heads/master' + @commit = project.fresh_commits(2).first + + # Fill nil/empty attributes + project.description = "This is a description" + + @data = project.web_hook_data(@oldrev, @newrev, @ref) + end + + subject { @data } + + it { should include(before: @oldrev) } + it { should include(after: @newrev) } + it { should include(ref: @ref) } + + context "with repository data" do + subject { @data[:repository] } + + it { should include(name: project.name) } + it { should include(url: project.web_url) } + it { should include(description: project.description) } + it { should include(homepage: project.web_url) } + it { should include(private: project.private?) } + end + + context "with commits" do + subject { @data[:commits] } + + it { should be_an(Array) } + it { should have(1).element } + + context "the commit" do + subject { @data[:commits].first } + + it { should include(id: @commit.id) } + it { should include(message: @commit.safe_message) } + it { should include(timestamp: @commit.date.xmlschema) } + it { should include(url: "http://localhost/#{project.code}/commits/#{@commit.id}") } + + context "with a author" do + subject { @data[:commits].first[:author] } + + it { should include(name: @commit.author_name) } + it { should include(email: @commit.author_email) } + end + end + end + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 437b1397..f53b833f 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe Project do describe "Associations" do + it { should have_many(:events) } it { should have_many(:users) } it { should have_many(:users_projects) } it { should have_many(:issues) } @@ -69,106 +70,6 @@ describe Project do end end - describe "web hooks" do - let(:project) { Factory :project } - - context "with no web hooks" do - it "raises no errors" do - lambda { - project.execute_web_hooks('oldrev', 'newrev', 'ref') - }.should_not raise_error - end - end - - context "with web hooks" do - before do - @webhook = Factory(:web_hook) - @webhook_2 = Factory(:web_hook) - project.web_hooks << [@webhook, @webhook_2] - end - - it "executes multiple web hook" do - @webhook.should_receive(:execute).once - @webhook_2.should_receive(:execute).once - - project.execute_web_hooks('oldrev', 'newrev', 'refs/heads/master') - end - end - - context "does not execute web hooks" do - before do - @webhook = Factory(:web_hook) - project.web_hooks << [@webhook] - end - - it "when pushing a branch for the first time" do - @webhook.should_not_receive(:execute) - project.execute_web_hooks('00000000000000000000000000000000', 'newrev', 'refs/heads/master') - end - - it "when pushing tags" do - @webhook.should_not_receive(:execute) - project.execute_web_hooks('oldrev', 'newrev', 'refs/tags/v1.0.0') - end - end - - context "when pushing new branches" do - - end - - context "when gathering commit data" do - before do - @oldrev, @newrev, @ref = project.fresh_commits(2).last.sha, project.fresh_commits(2).first.sha, 'refs/heads/master' - @commit = project.fresh_commits(2).first - - # Fill nil/empty attributes - project.description = "This is a description" - - @data = project.web_hook_data(@oldrev, @newrev, @ref) - end - - subject { @data } - - it { should include(before: @oldrev) } - it { should include(after: @newrev) } - it { should include(ref: @ref) } - - context "with repository data" do - subject { @data[:repository] } - - it { should include(name: project.name) } - it { should include(url: project.web_url) } - it { should include(description: project.description) } - it { should include(homepage: project.web_url) } - it { should include(private: project.private?) } - end - - context "with commits" do - subject { @data[:commits] } - - it { should be_an(Array) } - it { should have(1).element } - - context "the commit" do - subject { @data[:commits].first } - - it { should include(id: @commit.id) } - it { should include(message: @commit.safe_message) } - it { should include(timestamp: @commit.date.xmlschema) } - it { should include(url: "http://localhost/#{project.code}/commits/#{@commit.id}") } - - context "with a author" do - subject { @data[:commits].first[:author] } - - it { should include(name: @commit.author_name) } - it { should include(email: @commit.author_email) } - end - end - end - - end - end - describe "updates" do let(:project) { Factory :project } @@ -303,5 +204,6 @@ end # issues_enabled :boolean default(TRUE), not null # wall_enabled :boolean default(TRUE), not null # merge_requests_enabled :boolean default(TRUE), not null +# wiki_enabled :boolean default(TRUE), not null # diff --git a/spec/models/users_project_spec.rb b/spec/models/users_project_spec.rb index 41e36b57..85bc4d34 100644 --- a/spec/models/users_project_spec.rb +++ b/spec/models/users_project_spec.rb @@ -25,7 +25,6 @@ end # project_id :integer not null # created_at :datetime # updated_at :datetime -# repo_access :integer default(0), not null # project_access :integer default(0), not null #