diff options
author | Kamil Trzcinski <ayufan@ayufan.eu> | 2014-12-04 14:22:57 +0100 |
---|---|---|
committer | Kamil Trzcinski <ayufan@ayufan.eu> | 2015-01-12 19:48:45 +0100 |
commit | fc08a10dba559a93427b6ef31b33c45cd2194c40 (patch) | |
tree | ec9f2d2509162ca3b8c8007b3e656242c961f916 | |
parent | 31735615971642f74a9d84b153e5049782152b43 (diff) | |
download | gitlab-ci-fc08a10dba559a93427b6ef31b33c45cd2194c40.tar.gz |
Transplanted project notifications from GitLab
-rw-r--r-- | Gemfile | 3 | ||||
-rw-r--r-- | Gemfile.lock | 3 | ||||
-rw-r--r-- | app/controllers/services_controller.rb | 52 | ||||
-rw-r--r-- | app/helpers/icons_helper.rb | 9 | ||||
-rw-r--r-- | app/models/project.rb | 30 | ||||
-rw-r--r-- | app/models/project_services/slack_message.rb | 110 | ||||
-rw-r--r-- | app/models/project_services/slack_service.rb | 62 | ||||
-rw-r--r-- | app/models/service.rb | 85 | ||||
-rw-r--r-- | app/views/layouts/project.html.haml | 4 | ||||
-rw-r--r-- | app/views/services/_form.html.haml | 53 | ||||
-rw-r--r-- | app/views/services/edit.html.haml | 1 | ||||
-rw-r--r-- | app/views/services/index.html.haml | 13 | ||||
-rw-r--r-- | config/application.rb | 3 | ||||
-rw-r--r-- | config/routes.rb | 6 |
14 files changed, 433 insertions, 1 deletions
@@ -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 + ← 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' + + - 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 |