Commit ccff1051 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'jira_service' into 'master'

Close Jira issues with commit messages and merge requests
parents fcc32002 92917d89
v 6.9.0
- Support Jira ticket mentions in format JIRA-123
- Add support for closing Jira tickets with commits and MR
v 6.8.0
- Customise sign-in page with custom text and logo
......
......@@ -64,6 +64,7 @@ class Project < ActiveRecord::Base
has_one :assembla_service, dependent: :destroy
has_one :gemnasium_service, dependent: :destroy
has_one :slack_service, dependent: :destroy
has_one :jira_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link
# Merge Requests for target project should be removed with it
......@@ -316,13 +317,17 @@ class Project < ActiveRecord::Base
end
def available_services_names
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack)
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack jira)
end
def gitlab_ci?
gitlab_ci_service && gitlab_ci_service.active
end
def jira_tracker?
self.issues_tracker == "jira"
end
# For compatibility with old code
def code
path
......
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# token :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# username :string(255)
# password :string(255)
# api_version :string(255)
class JiraService < Service
include HTTParty
attr_accessible :project_url, :username, :password, :api_version
validates :username, :password, presence: true, if: :activated?
before_validation :set_api_version
def title
'JIRA'
end
def description
'Bug, issue tracking, and project management system'
end
def to_param
'jira'
end
def fields
[
{ type: 'text', name: 'project_url', placeholder: 'Url to JIRA, http://jira.example' },
{ type: 'text', name: 'username', placeholder: '' },
{ type: 'password', name: 'password', placeholder: '' },
{ type: 'text', name: 'api_version', placeholder: '2' }
]
end
def set_api_version
self.api_version = "2"
end
def execute(push, issue = nil)
close_issue(push, issue) if issue
end
private
def close_issue(push_data, issue_name)
url = close_issue_url(issue_name)
commit_url = push_data[:commits].first[:url]
message = {
'update' => {
'comment' => [{
'add' => {
'body' => "Issue solved with #{commit_url}"
}
}]
},
'transition' => {
'id' => '2'
}
}
JiraService.post(
url,
body: message.to_json,
headers: {
'Content-Type' => 'application/json',
'Authorization' => "Basic #{auth}"
}
)
end
def close_issue_url(issue_name)
"#{self.project_url.chomp("/")}/rest/api/#{self.api_version}/issue/#{issue_name}/transitions"
end
def auth
require 'base64'
Base64.urlsafe_encode64("#{self.username}:#{self.password}")
end
end
......@@ -88,9 +88,13 @@ class GitPushService
if !issues_to_close.empty? && is_default_branch
issues_to_close.each do |issue|
if project.jira_tracker? && project.jira_service.active
project.jira_service.execute(push_data, issue)
else
Issues::CloseService.new(project, author, {}).execute(issue, commit)
end
end
end
# Create cross-reference notes for any other references. Omit any issues that were referenced in an
# issue-closing phrase, or have already been mentioned from this commit (probably from this commit
......
......@@ -37,6 +37,8 @@
= f.text_area name, rows: 5, class: "form-control", placeholder: placeholder
- elsif type == 'checkbox'
= f.check_box name
- elsif type == 'password'
= f.password_field name, class: "form-control"
.form-actions
= f.submit 'Save', class: 'btn btn-save'
......
......@@ -74,7 +74,7 @@ production: &base
# If a commit message matches this regular expression, all issues referenced from the matched text will be closed.
# This happens when the commit is pushed or merged into the default branch of a project.
# When not specified the default issue_closing_pattern as specified below will be used.
# issue_closing_pattern: '([Cc]lose[sd]|[Ff]ixe[sd]) #(\d+)'
# issue_closing_pattern: '([Cc]lose[sd]|[Ff]ixe[sd]) (#\d+|([A-Z\-]+-)\d+)'
## Default project features settings
default_projects_features:
......
......@@ -91,7 +91,7 @@ Settings.gitlab['signup_enabled'] ||= false
Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
Settings.gitlab['issue_closing_pattern'] = '([Cc]lose[sd]|[Ff]ixe[sd]) #(\d+)' if Settings.gitlab['issue_closing_pattern'].nil?
Settings.gitlab['issue_closing_pattern'] = '([Cc]lose[sd]|[Ff]ixe[sd]) (#\d+|([A-Z\-]+-)\d+)' if Settings.gitlab['issue_closing_pattern'].nil?
Settings.gitlab['default_projects_features'] ||= {}
Settings.gitlab.default_projects_features['issues'] = true if Settings.gitlab.default_projects_features['issues'].nil?
Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.gitlab.default_projects_features['merge_requests'].nil?
......
class AddUsernamePasswordApiVersionToServices < ActiveRecord::Migration
def change
add_column :services, :username, :string
add_column :services, :password, :string
add_column :services, :api_version, :string
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20140502125220) do
ActiveRecord::Schema.define(version: 20140513095908) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -280,6 +280,9 @@ ActiveRecord::Schema.define(version: 20140502125220) do
t.string "room"
t.text "recipients"
t.string "api_key"
t.string "username"
t.string "password"
t.string "api_version"
end
add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
......
# GitLab JIRA integration
GitLab can be configured to interact with JIRA
*Note* Directions below are for JIRA v6.x and GitLab v6.x
## Configuring JIRA
We need to create a user in JIRA which will have access to all projects that need to integrate with GitLab.
Login to your JIRA instance as admin and under Administration -> User Management create a new user.
For example, let's create user `gitlab`. We've also added `gitlab` user to group `jira-developers` which grants it access to projects.
## Configuring GitLab
In `gitlab.yml` enable [JIRA issue tracker section by uncommenting the lines](https://gitlab.com/subscribers/gitlab-ee/blob/6-8-stable-ee/config/gitlab.yml.example#L111-115).
This will make sure that all issues within GitLab are pointing to the JIRA issue tracker.
We can also enable JIRA service that will allow us to interact with JIRA issues.
For example, we can close issues in JIRA by a commit in GitLab.
Go to project settings page and fill in the project name for the JIRA project, see [screenshot](jira_project_name.png).
Next, go to the services page and find JIRA.
1. Tick the active check box to enable the service.
1. Supply the url to JIRA server, for example http://jira.sample
1. Supply the username of a user we created under `Configuring JIRA` section, for example `gitlab`
1. Supply the password of the user
1. Optional: supply the JIRA api version, default is version 2
1. Save
Now we should be able to interact with JIRA issues, for example we can close a JIRA issue by commiting to our GitLab repository and referencing the JIRA issue( in the format of JIRAPROJECT-123).
For example, for project named NEW we commit with a commit message `Add new file fixes NEW-1` [screenshot](jira_service_commit.png) and that will close an issue NEW-1 in JIRA and add a comment with a link to the commit that closed the issue as shown on the [screenshot](jira_service_close_issue.png)
......@@ -48,3 +48,9 @@ Feature: Project Services
And I click email on push service link
And I fill email on push settings
Then I should see email on push service settings saved
Scenario: Activate JIRA service
When I visit project "Shop" services page
And I click jira service link
And I fill jira settings
Then I should see jira service settings saved
......@@ -118,4 +118,23 @@ class ProjectServices < Spinach::FeatureSteps
find_field('Room').value.should == '#gitlab'
find_field('Token').value.should == 'verySecret'
end
step 'I click jira service link' do
click_link 'JIRA'
end
step 'I fill jira settings' do
fill_in 'Project url', with: 'http://jira.example'
fill_in 'Username', with: 'gitlab'
fill_in 'Password', with: 'gitlab'
fill_in 'Api version', with: '2'
click_button 'Save'
end
step 'I should see jira service settings saved' do
find_field('Project url').value.should == 'http://jira.example'
find_field('Username').value.should == 'gitlab'
find_field('Password').value.should_not == 'gitlab'
find_field('Api version').value.should == '2'
end
end
......@@ -181,7 +181,7 @@ module Gitlab
link_to("##{identifier}", url, html_options.merge(title: "Issue: #{title}", class: "gfm gfm-issue #{html_options[:class]}"))
end
else
reference_jira_issue(identifier) if @project.issues_tracker == "jira"
reference_jira_issue(identifier) if @project.jira_tracker?
end
end
......
......@@ -23,10 +23,14 @@ module Gitlab
end
def issues_for project
if project.jira_tracker?
issues.uniq
else
issues.map do |identifier|
project.issues.where(iid: identifier).first
end.reject(&:nil?)
end
end
def merge_requests_for project
merge_requests.map do |identifier|
......
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# token :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# project_url :string(255)
# subdomain :string(255)
# room :string(255)
# recipients :text
# api_key :string(255)
# username :string(255)
# password :string(255)
# api_version :string(255)
require 'spec_helper'
describe JiraService, models: true do
describe "Associations" do
it { should belong_to :project }
it { should have_one :service_hook }
end
describe "Execute" do
let(:user) { create(:user) }
let(:project) { create(:project) }
before do
@jira_service = JiraService.new
@jira_service.stub(
project_id: project.id,
project: project,
service_hook: true,
project_url: 'http://jira.example.com',
username: 'gitlab_jira_username',
password: 'gitlab_jira_password',
api_version: '2'
)
@sample_data = GitPushService.new.sample_data(project, user)
# https://github.com/bblimke/webmock#request-with-basic-authentication
@api_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/transitions'
WebMock.stub_request(:post, @api_url)
end
it "should call JIRA API" do
@jira_service.execute(@sample_data, "JIRA-123")
WebMock.should have_requested(:post, @api_url).with(
body: /Issue solved with/
).once
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