summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKamil Trzcinski <ayufan@ayufan.eu>2014-12-04 14:22:57 +0100
committerKamil Trzcinski <ayufan@ayufan.eu>2015-01-12 19:48:45 +0100
commitfc08a10dba559a93427b6ef31b33c45cd2194c40 (patch)
treeec9f2d2509162ca3b8c8007b3e656242c961f916
parent31735615971642f74a9d84b153e5049782152b43 (diff)
downloadgitlab-ci-fc08a10dba559a93427b6ef31b33c45cd2194c40.tar.gz
Transplanted project notifications from GitLab
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock3
-rw-r--r--app/controllers/services_controller.rb52
-rw-r--r--app/helpers/icons_helper.rb9
-rw-r--r--app/models/project.rb30
-rw-r--r--app/models/project_services/slack_message.rb110
-rw-r--r--app/models/project_services/slack_service.rb62
-rw-r--r--app/models/service.rb85
-rw-r--r--app/views/layouts/project.html.haml4
-rw-r--r--app/views/services/_form.html.haml53
-rw-r--r--app/views/services/edit.html.haml1
-rw-r--r--app/views/services/index.html.haml13
-rw-r--r--config/application.rb3
-rw-r--r--config/routes.rb6
14 files changed, 433 insertions, 1 deletions
diff --git a/Gemfile b/Gemfile
index bc5dff2..9f113c3 100644
--- a/Gemfile
+++ b/Gemfile
@@ -52,6 +52,9 @@ gem 'grape'
gem 'grape-entity'
gem 'virtus', '1.0.1'
+# Default values for AR models
+gem "default_value_for", "~> 3.0.0"
+
# Other
gem 'rake'
gem 'foreman'
diff --git a/Gemfile.lock b/Gemfile.lock
index ff43d0b..cacebfe 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -69,6 +69,8 @@ GEM
thor
crack (0.4.1)
safe_yaml (~> 0.9.0)
+ default_value_for (3.0.0.1)
+ activerecord (>= 3.2.0, < 5.0)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
diff-lcs (1.2.5)
@@ -316,6 +318,7 @@ DEPENDENCIES
capybara
coffee-rails (~> 4.0.0)
coveralls
+ default_value_for (~> 3.0.0)
email_spec
factory_girl_rails
ffaker
diff --git a/app/controllers/services_controller.rb b/app/controllers/services_controller.rb
new file mode 100644
index 0000000..10a2d3e
--- /dev/null
+++ b/app/controllers/services_controller.rb
@@ -0,0 +1,52 @@
+class ServicesController < ApplicationController
+ before_filter :authenticate_user!
+ before_filter :project
+ before_filter :authorize_access_project!
+ before_filter :service, only: [:edit, :update, :test]
+
+ respond_to :html
+
+ layout 'project'
+
+ def index
+ @project.build_missing_services
+ @services = @project.services.reload
+ end
+
+ def edit
+ end
+
+ def update
+ if @service.update_attributes(service_params)
+ redirect_to edit_project_service_path(@project, @service.to_param)
+ else
+ render 'edit'
+ end
+ end
+
+ def test
+ # data = GitPushService.new.sample_data(project, current_user)
+ #
+ # @service.execute(data)
+ #
+ # redirect_to :back
+ end
+
+ private
+
+ def project
+ @project = Project.find(params[:project_id])
+ end
+
+ def service
+ @service ||= @project.services.find { |service| service.to_param == params[:id] }
+ end
+
+ def service_params
+ params.require(:service).permit(
+ :title, :token, :type, :active, :api_key, :subdomain,
+ :room, :recipients, :project_url, :webhook,
+ :user_key, :device, :priority, :sound
+ )
+ end
+end
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
new file mode 100644
index 0000000..4e77d2b
--- /dev/null
+++ b/app/helpers/icons_helper.rb
@@ -0,0 +1,9 @@
+module IconsHelper
+ def boolean_to_icon(value)
+ if value.to_s == "true"
+ content_tag :i, nil, class: 'icon-circle cgreen'
+ else
+ content_tag :i, nil, class: 'icon-power-off clgray'
+ end
+ end
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index 75be51c..9c93d08 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -39,6 +39,10 @@ class Project < ActiveRecord::Base
has_many :web_hooks, dependent: :destroy
has_many :jobs, dependent: :destroy
+ # Project services
+ has_many :services, dependent: :destroy
+ has_one :slack_service, dependent: :destroy
+
accepts_nested_attributes_for :jobs, allow_destroy: true
#
@@ -178,4 +182,30 @@ ls -la
prefix + auth
end
end
+
+ def available_services_names
+ %w(slack)
+ end
+
+ def build_missing_services
+ available_services_names.each do |service_name|
+ service = services.find { |service| service.to_param == service_name }
+
+ # If service is available but missing in db
+ # we should create an instance. Ex `create_gitlab_ci_service`
+ service = self.send :"create_#{service_name}_service" if service.nil?
+ end
+ end
+
+ def execute_services(data)
+ services.each do |service|
+
+ # Call service hook only if it is active
+ begin
+ service.execute(data) if service.active
+ rescue => e
+ logger.error(e)
+ end
+ end
+ end
end
diff --git a/app/models/project_services/slack_message.rb b/app/models/project_services/slack_message.rb
new file mode 100644
index 0000000..28204e5
--- /dev/null
+++ b/app/models/project_services/slack_message.rb
@@ -0,0 +1,110 @@
+require 'slack-notifier'
+
+class SlackMessage
+ def initialize(params)
+ @after = params.fetch(:after)
+ @before = params.fetch(:before)
+ @commits = params.fetch(:commits, [])
+ @project_name = params.fetch(:project_name)
+ @project_url = params.fetch(:project_url)
+ @ref = params.fetch(:ref).gsub('refs/heads/', '')
+ @username = params.fetch(:user_name)
+ end
+
+ def pretext
+ format(message)
+ end
+
+ def attachments
+ return [] if new_branch? || removed_branch?
+
+ commit_message_attachments
+ end
+
+ private
+
+ attr_reader :after
+ attr_reader :before
+ attr_reader :commits
+ attr_reader :project_name
+ attr_reader :project_url
+ attr_reader :ref
+ attr_reader :username
+
+ def message
+ if new_branch?
+ new_branch_message
+ elsif removed_branch?
+ removed_branch_message
+ else
+ push_message
+ end
+ end
+
+ def format(string)
+ Slack::Notifier::LinkFormatter.format(string)
+ end
+
+ def new_branch_message
+ "#{username} pushed new branch #{branch_link} to #{project_link}"
+ end
+
+ def removed_branch_message
+ "#{username} removed branch #{ref} from #{project_link}"
+ end
+
+ def push_message
+ "#{username} pushed to branch #{branch_link} of #{project_link} (#{compare_link})"
+ end
+
+ def commit_messages
+ commits.each_with_object('') do |commit, str|
+ str << compose_commit_message(commit)
+ end.chomp
+ end
+
+ def commit_message_attachments
+ [{ text: format(commit_messages), color: attachment_color }]
+ end
+
+ def compose_commit_message(commit)
+ author = commit.fetch(:author).fetch(:name)
+ id = commit.fetch(:id)[0..8]
+ message = commit.fetch(:message)
+ url = commit.fetch(:url)
+
+ "[#{id}](#{url}): #{message} - #{author}\n"
+ end
+
+ def new_branch?
+ before =~ /000000/
+ end
+
+ def removed_branch?
+ after =~ /000000/
+ end
+
+ def branch_url
+ "#{project_url}/commits/#{ref}"
+ end
+
+ def compare_url
+ "#{project_url}/compare/#{before}...#{after}"
+ end
+
+ def branch_link
+ "[#{ref}](#{branch_url})"
+ end
+
+ def project_link
+ "[#{project_name}](#{project_url})"
+ end
+
+ def compare_link
+ "[Compare changes](#{compare_url})"
+ end
+
+ def attachment_color
+ '#345'
+ end
+end
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
new file mode 100644
index 0000000..837002e
--- /dev/null
+++ b/app/models/project_services/slack_service.rb
@@ -0,0 +1,62 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+#
+
+class SlackService < Service
+ prop_accessor :webhook
+ validates :webhook, presence: true, if: :activated?
+
+ def title
+ 'Slack'
+ end
+
+ def description
+ 'A team communication tool for the 21st century'
+ end
+
+ def to_param
+ 'slack'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'webhook', placeholder: '' }
+ ]
+ end
+
+ def execute(push_data)
+ message = SlackMessage.new(push_data.merge(
+ project_url: project_url,
+ project_name: project_name
+ ))
+
+ credentials = webhook.match(/([\w-]*).slack.com.*services\/(.*)/)
+
+ if credentials.present?
+ subdomain = credentials[1]
+ token = credentials[2].split("token=").last
+ notifier = Slack::Notifier.new(subdomain, token)
+ notifier.ping(message.pretext, attachments: message.attachments)
+ end
+ end
+
+ private
+
+ def project_name
+ project.name_with_namespace.gsub(/\s/, '')
+ end
+
+ def project_url
+ project.web_url
+ end
+end
diff --git a/app/models/service.rb b/app/models/service.rb
new file mode 100644
index 0000000..f05f503
--- /dev/null
+++ b/app/models/service.rb
@@ -0,0 +1,85 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+#
+
+# To add new service you should build a class inherited from Service
+# and implement a set of methods
+class Service < ActiveRecord::Base
+ serialize :properties, JSON
+
+ default_value_for :active, false
+
+ after_initialize :initialize_properties
+
+ belongs_to :project
+ has_one :service_hook
+
+ validates :project_id, presence: true
+
+ def activated?
+ active
+ end
+
+ def category
+ :common
+ end
+
+ def initialize_properties
+ self.properties = {} if properties.nil?
+ end
+
+ def title
+ # implement inside child
+ end
+
+ def description
+ # implement inside child
+ end
+
+ def help
+ # implement inside child
+ end
+
+ def to_param
+ # implement inside child
+ end
+
+ def fields
+ # implement inside child
+ []
+ end
+
+ def execute
+ # implement inside child
+ end
+
+ def can_test?
+ !project.commits.empty?
+ end
+
+ # Provide convenient accessor methods
+ # for each serialized property.
+ def self.prop_accessor(*args)
+ args.each do |arg|
+ class_eval %{
+ def #{arg}
+ properties['#{arg}']
+ end
+
+ def #{arg}=(value)
+ self.properties['#{arg}'] = value
+ end
+ }
+ end
+ end
+end
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 243bd27..e0c1557 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -46,6 +46,10 @@
= link_to project_jobs_path(@project) do
%i.icon-code
Jobs
+ = nav_link path: 'services#index' do
+ = link_to project_services_path(@project) do
+ %i.icon-gear
+ Services
= nav_link path: 'projects#edit' do
= link_to edit_project_path(@project) do
%i.icon-cogs
diff --git a/app/views/services/_form.html.haml b/app/views/services/_form.html.haml
new file mode 100644
index 0000000..16d59d1
--- /dev/null
+++ b/app/views/services/_form.html.haml
@@ -0,0 +1,53 @@
+%h3.page-title
+ = @service.title
+ = boolean_to_icon @service.activated?
+
+%p= @service.description
+
+.back-link
+ = link_to project_services_path(@project) do
+ &larr; to services
+
+%hr
+
+= form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |f|
+ - if @service.errors.any?
+ .alert.alert-danger
+ %ul
+ - @service.errors.full_messages.each do |msg|
+ %li= msg
+
+ - if @service.help.present?
+ .bs-callout
+ = @service.help
+
+ .form-group
+ = f.label :active, "Active", class: "control-label"
+ .col-sm-10
+ = f.check_box :active
+
+ - @service.fields.each do |field|
+ - name = field[:name]
+ - value = @service.send(name)
+ - type = field[:type]
+ - placeholder = field[:placeholder]
+ - choices = field[:choices]
+ - default_choice = field[:default_choice]
+
+ .form-group
+ = f.label name, class: "control-label"
+ .col-sm-10
+ - if type == 'text'
+ = f.text_field name, class: "form-control", placeholder: placeholder
+ - elsif type == 'textarea'
+ = f.text_area name, rows: 5, class: "form-control", placeholder: placeholder
+ - elsif type == 'checkbox'
+ = f.check_box name
+ - elsif type == 'select'
+ = f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
+
+ .form-actions
+ = f.submit 'Save', class: 'btn btn-save'
+ &nbsp;
+ - if @service.valid? && @service.activated? && @service.can_test?
+ = link_to 'Test settings', test_project_service_path(@project, @service.to_param), class: 'btn'
diff --git a/app/views/services/edit.html.haml b/app/views/services/edit.html.haml
new file mode 100644
index 0000000..bcc5832
--- /dev/null
+++ b/app/views/services/edit.html.haml
@@ -0,0 +1 @@
+= render 'form'
diff --git a/app/views/services/index.html.haml b/app/views/services/index.html.haml
new file mode 100644
index 0000000..634b681
--- /dev/null
+++ b/app/views/services/index.html.haml
@@ -0,0 +1,13 @@
+%h3.page-title Project services
+%p.light Project services allow you to integrate GitLab CI with other applications
+%hr
+
+%ul.bordered-list
+ - @services.sort_by(&:title).each do |service|
+ %li
+ %h4
+ = link_to edit_project_service_path(@project, service.to_param) do
+ = service.title
+ .pull-right
+ = boolean_to_icon service.activated?
+ %p= service.description
diff --git a/config/application.rb b/config/application.rb
index 3a57d7c..ba0da4b 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -11,7 +11,8 @@ module GitlabCi
# -- all .rb files in that directory are automatically loaded.
# Custom directories with classes and modules you want to be autoloadable.
- config.autoload_paths += %W(#{config.root}/lib)
+ config.autoload_paths += %W(#{config.root}/lib
+ #{config.root}/app/models/project_services)
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named.
diff --git a/config/routes.rb b/config/routes.rb
index 234dd0a..da0b453 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -19,6 +19,12 @@ GitlabCi::Application.routes.draw do
post :build
end
+ resources :services, only: [:index, :edit, :update] do
+ member do
+ get :test
+ end
+ end
+
resource :charts, only: [:show]
resources :commits, only: [:show] do
member do