Commit e1f77b9b authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'link-to-issue-tracker' of https://github.com/Undev/gitlabhq into...

Merge branch 'link-to-issue-tracker' of https://github.com/Undev/gitlabhq into Undev-link-to-issue-tracker

Conflicts:
	Gemfile.lock
parents d452ffc2 1dab19d0
...@@ -46,6 +46,9 @@ gem "grape-entity", "~> 0.2.0" ...@@ -46,6 +46,9 @@ gem "grape-entity", "~> 0.2.0"
# based on human-friendly examples # based on human-friendly examples
gem "stamp" gem "stamp"
# Enumeration fields
gem 'enumerize'
# Pagination # Pagination
gem "kaminari", "~> 0.14.1" gem "kaminari", "~> 0.14.1"
...@@ -113,6 +116,7 @@ group :assets do ...@@ -113,6 +116,7 @@ group :assets do
gem 'bootstrap-sass', "2.2.1.1" gem 'bootstrap-sass', "2.2.1.1"
gem "font-awesome-sass-rails", "~> 3.0.0" gem "font-awesome-sass-rails", "~> 3.0.0"
gem "gemoji", "~> 1.2.1", require: 'emoji/railtie' gem "gemoji", "~> 1.2.1", require: 'emoji/railtie'
gem "gon"
end end
group :development do group :development do
......
...@@ -146,6 +146,8 @@ GEM ...@@ -146,6 +146,8 @@ GEM
email_spec (1.4.0) email_spec (1.4.0)
launchy (~> 2.1) launchy (~> 2.1)
mail (~> 2.2) mail (~> 2.2)
enumerize (0.5.1)
activesupport (>= 3.2)
erubis (2.7.0) erubis (2.7.0)
escape_utils (0.2.4) escape_utils (0.2.4)
eventmachine (1.0.0) eventmachine (1.0.0)
...@@ -184,10 +186,13 @@ GEM ...@@ -184,10 +186,13 @@ GEM
pyu-ruby-sasl (~> 0.0.3.1) pyu-ruby-sasl (~> 0.0.3.1)
rubyntlm (~> 0.1.1) rubyntlm (~> 0.1.1)
gitlab_yaml_db (1.0.0) gitlab_yaml_db (1.0.0)
gon (4.0.2)
grape (0.3.1) grape (0.3.1)
actionpack (>= 2.3.0)
activesupport activesupport
grape-entity (~> 0.2.0) grape-entity (~> 0.2.0)
hashie (~> 1.2) hashie (~> 1.2)
json
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
multi_xml multi_xml
rack rack
...@@ -473,6 +478,7 @@ DEPENDENCIES ...@@ -473,6 +478,7 @@ DEPENDENCIES
devise (~> 2.1.0) devise (~> 2.1.0)
draper (~> 0.18.0) draper (~> 0.18.0)
email_spec email_spec
enumerize
factory_girl_rails factory_girl_rails
ffaker ffaker
font-awesome-sass-rails (~> 3.0.0) font-awesome-sass-rails (~> 3.0.0)
...@@ -484,6 +490,7 @@ DEPENDENCIES ...@@ -484,6 +490,7 @@ DEPENDENCIES
gitlab_meta (= 5.0) gitlab_meta (= 5.0)
gitlab_omniauth-ldap (= 1.0.2) gitlab_omniauth-ldap (= 1.0.2)
gitlab_yaml_db (= 1.0.0) gitlab_yaml_db (= 1.0.0)
gon
grack! grack!
grape (~> 0.3.1) grape (~> 0.3.1)
grape-entity (~> 0.2.0) grape-entity (~> 0.2.0)
......
...@@ -18,3 +18,18 @@ $ -> ...@@ -18,3 +18,18 @@ $ ->
# Ref switcher # Ref switcher
$('.project-refs-select').on 'change', -> $('.project-refs-select').on 'change', ->
$(@).parents('form').submit() $(@).parents('form').submit()
$('#project_issues_enabled').change ->
if ($(this).is(':checked') == true)
$('#project_issues_tracker').removeAttr('disabled')
else
$('#project_issues_tracker').attr('disabled', 'disabled')
$('#project_issues_tracker').change()
$('#project_issues_tracker').change ->
if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled'))
$('#project_issues_tracker_id').attr('disabled', 'disabled')
else
$('#project_issues_tracker_id').removeAttr('disabled')
...@@ -5,6 +5,7 @@ class ApplicationController < ActionController::Base ...@@ -5,6 +5,7 @@ class ApplicationController < ActionController::Base
before_filter :add_abilities before_filter :add_abilities
before_filter :dev_tools if Rails.env == 'development' before_filter :dev_tools if Rails.env == 'development'
before_filter :default_headers before_filter :default_headers
before_filter :add_gon_variables
protect_from_forgery protect_from_forgery
...@@ -148,4 +149,8 @@ class ApplicationController < ActionController::Base ...@@ -148,4 +149,8 @@ class ApplicationController < ActionController::Base
headers['X-Frame-Options'] = 'DENY' headers['X-Frame-Options'] = 'DENY'
headers['X-XSS-Protection'] = '1; mode=block' headers['X-XSS-Protection'] = '1; mode=block'
end end
def add_gon_variables
gon.default_issues_tracker = Project.issues_tracker.default_value
end
end end
...@@ -40,4 +40,39 @@ module IssuesHelper ...@@ -40,4 +40,39 @@ module IssuesHelper
def issues_active_milestones def issues_active_milestones
@project.milestones.active.order("id desc").all @project.milestones.active.order("id desc").all
end end
def url_for_project_issues
return "" if @project.nil?
if @project.used_default_issues_tracker?
project_issues_filter_path(@project)
else
url = Settings[:issues_tracker][@project.issues_tracker]["project_url"]
url.gsub(':project_id', @project.id.to_s)
.gsub(':issues_tracker_id', @project.issues_tracker_id.to_s)
end
end
def url_for_issue(issue_id)
return "" if @project.nil?
if @project.used_default_issues_tracker?
url = project_issue_url project_id: @project, id: issue_id
else
url = Settings[:issues_tracker][@project.issues_tracker]["issues_url"]
url.gsub(':id', issue_id.to_s)
.gsub(':project_id', @project.id.to_s)
.gsub(':issues_tracker_id', @project.issues_tracker_id.to_s)
end
end
def title_for_issue(issue_id)
return "" if @project.nil?
if @project.used_default_issues_tracker? && issue = @project.issues.where(id: issue_id).first
issue.title
else
""
end
end
end end
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
# creator_id :integer # creator_id :integer
# default_branch :string(255) # default_branch :string(255)
# issues_enabled :boolean default(TRUE), not null # issues_enabled :boolean default(TRUE), not null
# issues_tracker :string not null
# wall_enabled :boolean default(TRUE), not null # wall_enabled :boolean default(TRUE), not null
# merge_requests_enabled :boolean default(TRUE), not null # merge_requests_enabled :boolean default(TRUE), not null
# wiki_enabled :boolean default(TRUE), not null # wiki_enabled :boolean default(TRUE), not null
...@@ -22,11 +23,12 @@ require "grit" ...@@ -22,11 +23,12 @@ require "grit"
class Project < ActiveRecord::Base class Project < ActiveRecord::Base
include Gitolited include Gitolited
extend Enumerize
class TransferError < StandardError; end class TransferError < StandardError; end
attr_accessible :name, :path, :description, :default_branch, attr_accessible :name, :path, :description, :default_branch, :issues_tracker,
:issues_enabled, :wall_enabled, :merge_requests_enabled, :issues_enabled, :wall_enabled, :merge_requests_enabled, :issues_tracker_id,
:wiki_enabled, :public, :import_url, as: [:default, :admin] :wiki_enabled, :public, :import_url, as: [:default, :admin]
attr_accessible :namespace_id, :creator_id, as: :admin attr_accessible :namespace_id, :creator_id, as: :admin
...@@ -72,6 +74,7 @@ class Project < ActiveRecord::Base ...@@ -72,6 +74,7 @@ class Project < ActiveRecord::Base
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
validates :issues_enabled, :wall_enabled, :merge_requests_enabled, validates :issues_enabled, :wall_enabled, :merge_requests_enabled,
:wiki_enabled, inclusion: { in: [true, false] } :wiki_enabled, inclusion: { in: [true, false] }
validates :issues_tracker_id, length: { within: 0..255 }
validates_uniqueness_of :name, scope: :namespace_id validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id validates_uniqueness_of :path, scope: :namespace_id
...@@ -93,6 +96,8 @@ class Project < ActiveRecord::Base ...@@ -93,6 +96,8 @@ class Project < ActiveRecord::Base
scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) } scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) }
scope :public_only, -> { where(public: true) } scope :public_only, -> { where(public: true) }
enumerize :issues_tracker, :in => (Gitlab.config.issues_tracker.keys).append(:gitlab), :default => :gitlab
class << self class << self
def abandoned def abandoned
project_ids = Event.select('max(created_at) as latest_date, project_id'). project_ids = Event.select('max(created_at) as latest_date, project_id').
...@@ -201,6 +206,22 @@ class Project < ActiveRecord::Base ...@@ -201,6 +206,22 @@ class Project < ActiveRecord::Base
issues.tag_counts_on(:labels) issues.tag_counts_on(:labels)
end end
def issue_exists?(issue_id)
if used_default_issues_tracker?
self.issues.where(id: issue_id).first.present?
else
true
end
end
def used_default_issues_tracker?
self.issues_tracker == Project.issues_tracker.default_value
end
def can_have_issues_tracker_id?
self.issues_enabled && !self.used_default_issues_tracker?
end
def services def services
[gitlab_ci_service].compact [gitlab_ci_service].compact
end end
......
...@@ -31,6 +31,15 @@ ...@@ -31,6 +31,15 @@
= f.label :issues_enabled, "Issues" = f.label :issues_enabled, "Issues"
.input= f.check_box :issues_enabled .input= f.check_box :issues_enabled
- if Project.issues_tracker.values.count > 1
.clearfix
= f.label :issues_tracker, "Issues tracker", class: 'control-label'
.input= f.select(:issues_tracker, Project.issues_tracker.values, {}, { disabled: !@project.issues_enabled })
.clearfix
= f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label'
.input= f.text_field :issues_tracker_id, class: "xxlarge", disabled: !@project.can_have_issues_tracker_id?
.clearfix .clearfix
= f.label :merge_requests_enabled, "Merge Requests" = f.label :merge_requests_enabled, "Merge Requests"
.input= f.check_box :merge_requests_enabled .input= f.check_box :merge_requests_enabled
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
= stylesheet_link_tag "application" = stylesheet_link_tag "application"
= javascript_include_tag "application" = javascript_include_tag "application"
= csrf_meta_tags = csrf_meta_tags
= include_gon
-# Atom feed -# Atom feed
- if current_user - if current_user
......
...@@ -22,11 +22,12 @@ ...@@ -22,11 +22,12 @@
= nav_link(controller: %w(graph)) do = nav_link(controller: %w(graph)) do
= link_to "Network", project_graph_path(@project, @ref || @repository.root_ref) = link_to "Network", project_graph_path(@project, @ref || @repository.root_ref)
- if @project.issues_enabled - if @project.issues_enabled
= nav_link(controller: %w(issues milestones labels)) do = nav_link(controller: %w(issues milestones labels)) do
= link_to project_issues_filter_path(@project) do = link_to url_for_project_issues do
Issues Issues
%span.count.issue_counter= @project.issues.opened.count - if @project.used_default_issues_tracker?
%span.count.issue_counter= @project.issues.opened.count
- if @project.repo_exists? && @project.merge_requests_enabled - if @project.repo_exists? && @project.merge_requests_enabled
= nav_link(controller: :merge_requests) do = nav_link(controller: :merge_requests) do
......
...@@ -24,6 +24,15 @@ ...@@ -24,6 +24,15 @@
= f.check_box :issues_enabled = f.check_box :issues_enabled
%span.descr Lightweight issue tracking system for this project %span.descr Lightweight issue tracking system for this project
- if Project.issues_tracker.values.count > 1
.control-group
= f.label :issues_tracker, "Issues tracker", class: 'control-label'
.input= f.select(:issues_tracker, Project.issues_tracker.values, {}, { disabled: !@project.issues_enabled })
.clearfix
= f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label'
.input= f.text_field :issues_tracker_id, class: "xxlarge", disabled: !@project.can_have_issues_tracker_id?
.control-group .control-group
= f.label :merge_requests_enabled, "Merge Requests", class: 'control-label' = f.label :merge_requests_enabled, "Merge Requests", class: 'control-label'
.controls .controls
......
...@@ -37,6 +37,22 @@ production: &base ...@@ -37,6 +37,22 @@ production: &base
# signup_enabled: true # default: false - Account passwords are not sent via the email if signup is enabled. # signup_enabled: true # default: false - Account passwords are not sent via the email if signup is enabled.
# username_changing_enabled: false # default: true - User can change her username/namespace # username_changing_enabled: false # default: true - User can change her username/namespace
## External issues trackers
issues_tracker:
redmine:
## If not nil, link 'Issues' on project page will be replaced tp this
## Use placeholders:
## :project_id - Gitlab project identifier
## :issues_tracker_id - Project Name or Id in external issue tracker
project_url: "http://redmine.sample/projects/:issues_tracker_id"
## If not nil, links from /#\d/ entities from commit messages will replaced to this
## Use placeholders:
## :project_id - Gitlab project identifier
## :issues_tracker_id - Project Name or Id in external issue tracker
## :id - Issue id (from commit messages)
issues_url: "http://redmine.sample/issues/:id"
## Gravatar ## Gravatar
gravatar: gravatar:
enabled: true # Use user avatar images from Gravatar.com (default: true) enabled: true # Use user avatar images from Gravatar.com (default: true)
...@@ -133,6 +149,10 @@ development: ...@@ -133,6 +149,10 @@ development:
test: test:
<<: *base <<: *base
issues_tracker:
redmine:
project_url: "http://redmine/projects/:issues_tracker_id"
issues_url: "http://redmine/:project_id/:issues_tracker_id/:id"
staging: staging:
<<: *base <<: *base
...@@ -42,6 +42,8 @@ Settings['omniauth'] ||= Settingslogic.new({}) ...@@ -42,6 +42,8 @@ Settings['omniauth'] ||= Settingslogic.new({})
Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil? Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil?
Settings.omniauth['providers'] ||= [] Settings.omniauth['providers'] ||= []
Settings['issues_tracker'] ||= {}
# #
# GitLab # GitLab
# #
......
class AddIssuesTrackerToProject < ActiveRecord::Migration
def change
add_column :projects, :issues_tracker, :string, default: :gitlab, null: false
end
end
class AddIssuesTrackerIdToProject < ActiveRecord::Migration
def change
add_column :projects, :issues_tracker_id, :string
end
end
...@@ -106,11 +106,11 @@ ActiveRecord::Schema.define(:version => 20130220133245) do ...@@ -106,11 +106,11 @@ ActiveRecord::Schema.define(:version => 20130220133245) do
add_index "milestones", ["project_id"], :name => "index_milestones_on_project_id" add_index "milestones", ["project_id"], :name => "index_milestones_on_project_id"
create_table "namespaces", :force => true do |t| create_table "namespaces", :force => true do |t|
t.string "name", :null => false t.string "name", :null => false
t.string "path", :null => false t.string "path", :null => false
t.integer "owner_id", :null => false t.integer "owner_id", :null => false
t.datetime "created_at", :null => false t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false t.datetime "updated_at", :null => false
t.string "type" t.string "type"
end end
...@@ -152,6 +152,8 @@ ActiveRecord::Schema.define(:version => 20130220133245) do ...@@ -152,6 +152,8 @@ ActiveRecord::Schema.define(:version => 20130220133245) do
t.boolean "wiki_enabled", :default => true, :null => false t.boolean "wiki_enabled", :default => true, :null => false
t.integer "namespace_id" t.integer "namespace_id"
t.boolean "public", :default => false, :null => false t.boolean "public", :default => false, :null => false
t.string "issues_tracker", :default => "gitlab", :null => false
t.string "issues_tracker_id"
end end
add_index "projects", ["creator_id"], :name => "index_projects_on_owner_id" add_index "projects", ["creator_id"], :name => "index_projects_on_owner_id"
...@@ -230,8 +232,8 @@ ActiveRecord::Schema.define(:version => 20130220133245) do ...@@ -230,8 +232,8 @@ ActiveRecord::Schema.define(:version => 20130220133245) do
t.string "name" t.string "name"
t.string "path" t.string "path"
t.integer "owner_id" t.integer "owner_id"
t.datetime "created_at", :null => false t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false t.datetime "updated_at", :null => false
end end
create_table "users", :force => true do |t| create_table "users", :force => true do |t|
......
...@@ -25,6 +25,8 @@ module Gitlab ...@@ -25,6 +25,8 @@ module Gitlab
# >> gfm(":trollface:") # >> gfm(":trollface:")
# => "<img alt=\":trollface:\" class=\"emoji\" src=\"/images/trollface.png" title=\":trollface:\" /> # => "<img alt=\":trollface:\" class=\"emoji\" src=\"/images/trollface.png" title=\":trollface:\" />
module Markdown module Markdown
include IssuesHelper
attr_reader :html_options attr_reader :html_options
# Public: Parse the provided text with GitLab-Flavored Markdown # Public: Parse the provided text with GitLab-Flavored Markdown
...@@ -163,8 +165,11 @@ module Gitlab ...@@ -163,8 +165,11 @@ module Gitlab
end end
def reference_issue(identifier) def reference_issue(identifier)
if issue = @project.issues.where(id: identifier).first if @project.issue_exists? identifier
link_to("##{identifier}", project_issue_url(@project, issue), html_options.merge(title: "Issue: #{issue.title}", class: "gfm gfm-issue #{html_options[:class]}")) url = url_for_issue(identifier)
title = title_for_issue(identifier)
link_to("##{identifier}", url, html_options.merge(title: "Issue: #{title}", class: "gfm gfm-issue #{html_options[:class]}"))
end end
end end
......
...@@ -29,6 +29,11 @@ FactoryGirl.define do ...@@ -29,6 +29,11 @@ FactoryGirl.define do
creator creator
end end
factory :redmine_project, parent: :project do
issues_tracker { "redmine" }
issues_tracker_id { "project_name_in_redmine" }
end
factory :group do factory :group do
sequence(:name) { |n| "group#{n}" } sequence(:name) { |n| "group#{n}" }
path { name.downcase.gsub(/\s/, '_') } path { name.downcase.gsub(/\s/, '_') }
......
...@@ -2,6 +2,7 @@ require "spec_helper" ...@@ -2,6 +2,7 @@ require "spec_helper"
describe GitlabMarkdownHelper do describe GitlabMarkdownHelper do
include ApplicationHelper include ApplicationHelper
include IssuesHelper
let!(:project) { create(:project) } let!(:project) { create(:project) }
......
require "spec_helper"
describe IssuesHelper do
let(:project) { create :project }
let(:issue) { create :issue, project: project }
let(:ext_project) { create :redmine_project }
describe :title_for_issue do
it "should return issue title if used internal tracker" do
@project = project
title_for_issue(issue.id).should eq issue.title
end
it "should always return empty string if used external tracker" do
@project = ext_project
title_for_issue(rand(100)).should eq ""
end
it "should always return empty string if project nil" do
@project = nil
title_for_issue(rand(100)).should eq ""
end
end
describe :url_for_project_issues do
let(:project_url) { Gitlab.config.issues_tracker.redmine.project_url}
let(:ext_expected) do
project_url.gsub(':project_id', ext_project.id.to_s)
.gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
end
let(:int_expected) { polymorphic_path([project]) }
it "should return internal path if used internal tracker" do
@project = project
url_for_project_issues.should match(int_expected)
end
it "should return path to external tracker" do
@project = ext_project
url_for_project_issues.should match(ext_expected)
end
it "should return empty string if project nil" do
@project = nil
url_for_project_issues.should eq ""
end
end
describe :url_for_issue do
let(:issue_id) { 3 }
let(:issues_url) { Gitlab.config.issues_tracker.redmine.issues_url}
let(:ext_expected) do
issues_url.gsub(':id', issue_id.to_s)
.gsub(':project_id', ext_project.id.to_s)
.gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
end
let(:int_expected) { polymorphic_path([project, issue]) }
it "should return internal path if used internal tracker" do
@project = project
url_for_issue(issue.id).should match(int_expected)
end
it "should return path to external tracker" do
@project = ext_project
url_for_issue(issue_id).should match(ext_expected)
end
it "should return empty string if project nil" do
@project = nil
url_for_issue(issue.id).should eq ""
end
end
end
...@@ -60,6 +60,7 @@ describe Project do ...@@ -60,6 +60,7 @@ describe Project do
it { should ensure_inclusion_of(:wall_enabled).in_array([true, false]) } it { should ensure_inclusion_of(:wall_enabled).in_array([true, false]) }
it { should ensure_inclusion_of(:merge_requests_enabled).in_array([true, false]) } it { should ensure_inclusion_of(:merge_requests_enabled).in_array([true, false]) }
it { should ensure_inclusion_of(:wiki_enabled).in_array([true, false]) } it { should ensure_inclusion_of(:wiki_enabled).in_array([true, false]) }
it { should ensure_length_of(:issues_tracker_id).is_within(0..255) }
it "should not allow new projects beyond user limits" do it "should not allow new projects beyond user limits" do
project.stub(:creator).and_return(double(can_create_project?: false, projects_limit: 1)) project.stub(:creator).and_return(double(can_create_project?: false, projects_limit: 1))
...@@ -190,4 +191,57 @@ describe Project do ...@@ -190,4 +191,57 @@ describe Project do
Project.new(path: "empty").repository.should be_nil Project.new(path: "empty").repository.should be_nil
end end
end end
describe :issue_exists? do
let(:project) { create(:project) }
let(:existed_issue) { create(:issue, project: project) }
let(:not_existed_issue) { create(:issue) }
let(:ext_project) { create(:redmine_project) }
it "should be true or if used internal tracker and issue exists" do
project.issue_exists?(existed_issue.id).should be_true
end
it "should be false or if used internal tracker and issue not exists" do
project.issue_exists?(not_existed_issue.id).should be_false
end
it "should always be true if used other tracker" do
ext_project.issue_exists?(rand(100)).should be_true
end
end
describe :used_default_issues_tracker? do
let(:project) { create(:project) }
let(:ext_project) { create(:redmine_project) }
it "should be true if used internal tracker" do
project.used_default_issues_tracker?.should be_true
end
it "should be false if used other tracker" do
ext_project.used_default_issues_tracker?.should be_false
end
end
describe :can_have_issues_tracker_id? do
let(:project) { create(:project) }
let(:ext_project) { create(:redmine_project) }
it "should be true for projects with external issues tracker if issues enabled" do
ext_project.can_have_issues_tracker_id?.should be_true
end
it "should be false for projects with internal issue tracker if issues enabled" do
project.can_have_issues_tracker_id?.should be_false
end
it "should be always false if issues disbled" do
project.issues_enabled = false
ext_project.issues_enabled = false
project.can_have_issues_tracker_id?.should be_false
ext_project.can_have_issues_tracker_id?.should be_false
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