Commit 714ca518 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'issues_tracker_template' into 'master'

Issues trackers template

If admin defines service template, service in project will be prefilled with the template data.

See merge request !1503
parents 30cf916b e8271226
class Admin::ServicesController < Admin::ApplicationController
before_filter :service, only: [:edit, :update]
def index
@services = services_templates
end
def edit
unless service.present?
redirect_to admin_application_settings_services_path,
alert: "Service is unknown or it doesn't exist"
end
end
def update
if service.update_attributes(application_services_params[:service])
redirect_to admin_application_settings_services_path,
notice: 'Application settings saved successfully'
else
render :edit
end
end
private
def services_templates
templates = []
Service.available_services_names.each do |service_name|
service_template = service_name.concat("_service").camelize.constantize
templates << service_template.where(template: true).first_or_create
end
templates
end
def service
@service ||= Service.where(id: params[:id], template: true).first
end
def application_services_params
params.permit(:id,
service: [
:title, :token, :type, :active, :api_key, :subdomain,
:room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
:build_key, :server, :teamcity_url, :build_type,
:description, :issues_url, :new_issue_url, :restrict_to_branch
])
end
end
...@@ -67,9 +67,10 @@ module Mentionable ...@@ -67,9 +67,10 @@ module Mentionable
return [] if text.blank? return [] if text.blank?
ext = Gitlab::ReferenceExtractor.new ext = Gitlab::ReferenceExtractor.new
ext.analyze(text, p) ext.analyze(text, p)
(ext.issues_for +
ext.merge_requests_for + (ext.issues_for(p) +
ext.commits_for).uniq - [local_reference] ext.merge_requests_for(p) +
ext.commits_for(p)).uniq - [local_reference]
end end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+. # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
......
class ExternalIssue
def initialize(issue_identifier, project)
@issue_identifier, @project = issue_identifier, project
end
def to_s
@issue_identifier.to_s
end
def id
@issue_identifier.to_s
end
def iid
@issue_identifier.to_s
end
def ==(other)
other.is_a?(self.class) && (to_s == other.to_s)
end
def project
@project
end
end
...@@ -353,18 +353,28 @@ class Project < ActiveRecord::Base ...@@ -353,18 +353,28 @@ class Project < ActiveRecord::Base
end end
def build_missing_services def build_missing_services
available_services_names.each do |service_name| services_templates = Service.where(template: true)
service = services.find { |service| service.to_param == service_name }
Service.available_services_names.each do |service_name|
service = find_service(services, service_name)
# If service is available but missing in db # If service is available but missing in db
# we should create an instance. Ex `create_gitlab_ci_service` if service.nil?
service = self.send :"create_#{service_name}_service" if service.nil? # We should check if template for the service exists
template = find_service(services_templates, service_name)
if template.nil?
# If no template, we should create an instance. Ex `create_gitlab_ci_service`
service = self.send :"create_#{service_name}_service"
else
Service.create_from_template(self.id, template)
end
end
end end
end end
def available_services_names def find_service(list, name)
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla asana list.find { |service| service.to_param == name }
emails_on_push gemnasium slack pushover buildbox bamboo teamcity jira redmine custom_issue_tracker)
end end
def gitlab_ci? def gitlab_ci?
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
require 'asana' require 'asana'
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
class AssemblaService < Service class AssemblaService < Service
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
class BambooService < CiService class BambooService < CiService
......
...@@ -5,13 +5,13 @@ ...@@ -5,13 +5,13 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
require "addressable/uri" require "addressable/uri"
class BuildboxService < CiService class BuildboxService < CiService
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
class CampfireService < Service class CampfireService < Service
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
# Base class for CI services # Base class for CI services
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
class CustomIssueTrackerService < IssueTrackerService class CustomIssueTrackerService < IssueTrackerService
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
class EmailsOnPushService < Service class EmailsOnPushService < Service
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
require "flowdock-git-hook" require "flowdock-git-hook"
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
require "gemnasium/gitlab_service" require "gemnasium/gitlab_service"
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
class GitlabCiService < CiService class GitlabCiService < CiService
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
class GitlabIssueTrackerService < IssueTrackerService class GitlabIssueTrackerService < IssueTrackerService
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
class HipchatService < Service class HipchatService < Service
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
class IssueTrackerService < Service class IssueTrackerService < Service
...@@ -77,12 +78,14 @@ class IssueTrackerService < Service ...@@ -77,12 +78,14 @@ class IssueTrackerService < Service
end end
def set_project_url def set_project_url
if self.project
id = self.project.issues_tracker_id id = self.project.issues_tracker_id
if id if id
issues_tracker['project_url'].gsub(":issues_tracker_id", id) issues_tracker['project_url'].gsub(":issues_tracker_id", id)
else
issues_tracker['project_url']
end end
end end
issues_tracker['project_url']
end
end end
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
class JiraService < IssueTrackerService class JiraService < IssueTrackerService
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
class PivotaltrackerService < Service class PivotaltrackerService < Service
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
class PushoverService < Service class PushoverService < Service
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
class RedmineService < IssueTrackerService class RedmineService < IssueTrackerService
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
class SlackService < Service class SlackService < Service
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
class TeamcityService < CiService class TeamcityService < CiService
......
...@@ -5,12 +5,12 @@ ...@@ -5,12 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# # template :boolean default(FALSE)
# To add new service you should build a class inherited from Service # To add new service you should build a class inherited from Service
# and implement a set of methods # and implement a set of methods
...@@ -25,7 +25,7 @@ class Service < ActiveRecord::Base ...@@ -25,7 +25,7 @@ class Service < ActiveRecord::Base
belongs_to :project belongs_to :project
has_one :service_hook has_one :service_hook
validates :project_id, presence: true validates :project_id, presence: true, unless: Proc.new { |service| service.template? }
scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') } scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') }
...@@ -33,6 +33,10 @@ class Service < ActiveRecord::Base ...@@ -33,6 +33,10 @@ class Service < ActiveRecord::Base
active active
end end
def template?
template
end
def category def category
:common :common
end end
...@@ -94,7 +98,15 @@ class Service < ActiveRecord::Base ...@@ -94,7 +98,15 @@ class Service < ActiveRecord::Base
self.category == :issue_tracker self.category == :issue_tracker
end end
def self.issue_tracker_service_list def self.available_services_names
Service.select(&:issue_tracker?).map{ |s| s.to_param } %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla asana
emails_on_push gemnasium slack pushover buildbox bamboo teamcity jira redmine custom_issue_tracker)
end
def self.create_from_template(project_id, template)
service = template.dup
service.template = false
service.project_id = project_id
service if service.save
end end
end end
%h3.page-title
= @service.title
%p #{@service.description} template
= form_for :service, url: admin_application_settings_service_path, method: :put, html: { class: 'form-horizontal fieldset-form' } do |f|
- if @service.errors.any?
#error_explanation
.alert.alert-danger
- @service.errors.full_messages.each do |msg|
%p= msg
- @service.fields.each do |field|
- name = field[:name]
- value = @service.send(name) unless field[:type] == 'password'
- 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" }
- elsif type == 'password'
= f.password_field name, class: 'form-control'
.form-actions
= f.submit 'Save', class: 'btn btn-save'
%h3.page-title Service templates
%p.light Service template allows you to set default values for project services
%table.table
%thead
%tr
%th
%th Service
%th Desription
%th Last edit
- @services.sort_by(&:title).each do |service|
%tr
%td
= icon("copy", class: 'clgray')
%td
= link_to edit_admin_application_settings_service_path(service.id) do
%strong= service.title
%td
= service.description
%td.light
= time_ago_in_words service.updated_at
ago
...@@ -46,6 +46,12 @@ ...@@ -46,6 +46,12 @@
%span %span
Applications Applications
= nav_link(controller: :application_settings) do
= link_to admin_application_settings_services_path, title: 'Service Templates' do
%i.fa.fa-copy
%span
Service Templates
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
= link_to admin_application_settings_path, title: 'Settings' do = link_to admin_application_settings_path, title: 'Settings' do
%i.fa.fa-cogs %i.fa.fa-cogs
......
...@@ -131,7 +131,9 @@ Gitlab::Application.routes.draw do ...@@ -131,7 +131,9 @@ Gitlab::Application.routes.draw do
end end
end end
resource :application_settings, only: [:show, :update] resource :application_settings, only: [:show, :update] do
resources :services
end
root to: 'dashboard#index' root to: 'dashboard#index'
end end
......
class AddTemplateToService < ActiveRecord::Migration
def change
add_column :services, :template, :boolean, default: false
end
end
class AllowNullInServicesProjectId < ActiveRecord::Migration
def change
change_column :services, :project_id, :integer, null: true
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150209222013) do ActiveRecord::Schema.define(version: 20150211174341) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -360,11 +360,12 @@ ActiveRecord::Schema.define(version: 20150209222013) do ...@@ -360,11 +360,12 @@ ActiveRecord::Schema.define(version: 20150209222013) do
create_table "services", force: true do |t| create_table "services", force: true do |t|
t.string "type" t.string "type"
t.string "title" t.string "title"
t.integer "project_id", null: false t.integer "project_id"
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.boolean "active", default: false, null: false t.boolean "active", default: false, null: false
t.text "properties" t.text "properties"
t.boolean "template", default: false
end end
add_index "services", ["created_at", "id"], name: "index_services_on_created_at_and_id", using: :btree add_index "services", ["created_at", "id"], name: "index_services_on_created_at_and_id", using: :btree
......
...@@ -71,7 +71,7 @@ module Gitlab ...@@ -71,7 +71,7 @@ module Gitlab
if entry_project.nil? if entry_project.nil?
false false
else else
project.nil? || project.id == entry_project.id project.nil? || entry_project.default_issues_tracker?
end end
end end
end end
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
# id :integer not null, primary key # id :integer not null, primary key
# type :string(255) # type :string(255)
# title :string(255) # title :string(255)
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# active :boolean default(FALSE), not null # active :boolean default(FALSE), not null
# properties :text # properties :text
# template :boolean default(FALSE)
# #
require 'spec_helper' require 'spec_helper'
...@@ -59,4 +60,29 @@ describe Service do ...@@ -59,4 +60,29 @@ describe Service do
end end
end end
end end
describe "Template" do
describe "for pushover service" do
let(:service_template) {
PushoverService.create(template: true, properties: {device: 'MyDevice', sound: 'mic', priority: 4, api_key: '123456789'})
}
let(:project) { create(:project) }
describe 'should be prefilled for projects pushover service' do
before do
service_template
project.build_missing_services
end
it "should have all fields prefilled" do
service = project.pushover_service
expect(service.template).to eq(false)
expect(service.device).to eq('MyDevice')
expect(service.sound).to eq('mic')
expect(service.priority).to eq(4)
expect(service.api_key).to eq('123456789')
end
end
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