Skip to content
Snippets Groups Projects
Commit 6d13bc58 authored by Sean McGivern's avatar Sean McGivern
Browse files

Merge branch '33097-issue-tracker' into 'master'

Associate Issues tab only with internal issues tracker

Closes #33097

See merge request !12130
parents 7eaef130 6774a037
No related branches found
No related tags found
No related merge requests found
Showing
with 79 additions and 48 deletions
Loading
Loading
@@ -8,7 +8,6 @@ class Projects::IssuesController < Projects::ApplicationController
 
prepend_before_action :authenticate_user!, only: [:new]
 
before_action :redirect_to_external_issue_tracker, only: [:index, :new]
before_action :check_issues_available!
before_action :issue, except: [:index, :new, :create, :bulk_update]
 
Loading
Loading
@@ -243,19 +242,19 @@ class Projects::IssuesController < Projects::ApplicationController
end
 
def authorize_update_issue!
return render_404 unless can?(current_user, :update_issue, @issue)
render_404 unless can?(current_user, :update_issue, @issue)
end
 
def authorize_admin_issues!
return render_404 unless can?(current_user, :admin_issue, @project)
render_404 unless can?(current_user, :admin_issue, @project)
end
 
def authorize_create_merge_request!
return render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user)
render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user)
end
 
def check_issues_available!
return render_404 unless @project.feature_available?(:issues, current_user) && @project.default_issues_tracker?
return render_404 unless @project.feature_available?(:issues, current_user)
end
 
def redirect_to_external_issue_tracker
Loading
Loading
Loading
Loading
@@ -17,10 +17,10 @@ module IssuesHelper
return '' if project.nil?
 
url =
if options[:only_path]
project.issues_tracker.issue_path(issue_iid)
if options[:internal]
url_for_internal_issue(issue_iid, project, options)
else
project.issues_tracker.issue_url(issue_iid)
url_for_tracker_issue(issue_iid, project, options)
end
 
# Ensure we return a valid URL to prevent possible XSS.
Loading
Loading
@@ -29,6 +29,24 @@ module IssuesHelper
''
end
 
def url_for_tracker_issue(issue_iid, project, options)
if options[:only_path]
project.issues_tracker.issue_path(issue_iid)
else
project.issues_tracker.issue_url(issue_iid)
end
end
def url_for_internal_issue(issue_iid, project = @project, options = {})
helpers = Gitlab::Routing.url_helpers
if options[:only_path]
helpers.namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: issue_iid)
else
helpers.namespace_project_issue_url(namespace_id: project.namespace, project_id: project, id: issue_iid)
end
end
def bulk_update_milestone_options
milestones = @project.milestones.active.reorder(due_date: :asc, title: :asc).to_a
milestones.unshift(Milestone::None)
Loading
Loading
@@ -158,4 +176,6 @@ module IssuesHelper
 
# Required for Banzai::Filter::IssueReferenceFilter
module_function :url_for_issue
module_function :url_for_internal_issue
module_function :url_for_tracker_issue
end
Loading
Loading
@@ -596,7 +596,7 @@ class MergeRequest < ActiveRecord::Base
# running `ReferenceExtractor` on each of them separately.
# This optimization does not apply to issues from external sources.
def cache_merge_request_closes_issues!(current_user)
return if project.has_external_issue_tracker?
return unless project.issues_enabled?
 
transaction do
self.merge_requests_closing_issues.delete_all
Loading
Loading
Loading
Loading
@@ -734,9 +734,11 @@ class Project < ActiveRecord::Base
end
 
def get_issue(issue_id, current_user)
if default_issues_tracker?
IssuesFinder.new(current_user, project_id: id).find_by(iid: issue_id)
else
issue = IssuesFinder.new(current_user, project_id: id).find_by(iid: issue_id) if issues_enabled?
if issue
issue
elsif external_issue_tracker
ExternalIssue.new(issue_id, self)
end
end
Loading
Loading
@@ -758,7 +760,7 @@ class Project < ActiveRecord::Base
end
 
def external_issue_reference_pattern
external_issue_tracker.class.reference_pattern
external_issue_tracker.class.reference_pattern(only_long: issues_enabled?)
end
 
def default_issues_tracker?
Loading
Loading
Loading
Loading
@@ -8,8 +8,12 @@ class IssueTrackerService < Service
# This pattern does not support cross-project references
# The other code assumes that this pattern is a superset of all
# overriden patterns. See ReferenceRegexes::EXTERNAL_PATTERN
def self.reference_pattern
@reference_pattern ||= %r{(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?<issue>\d+)}
def self.reference_pattern(only_long: false)
if only_long
%r{(\b[A-Z][A-Z0-9_]+-)(?<issue>\d+)}
else
%r{(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?<issue>\d+)}
end
end
 
def default?
Loading
Loading
Loading
Loading
@@ -18,7 +18,7 @@ class JiraService < IssueTrackerService
end
 
# {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1
def self.reference_pattern
def self.reference_pattern(only_long: true)
@reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
end
 
Loading
Loading
Loading
Loading
@@ -287,9 +287,6 @@ class ProjectPolicy < BasePolicy
prevent :create_issue
prevent :update_issue
prevent :admin_issue
end
rule { issues_disabled & default_issues_tracker }.policy do
prevent :read_issue
end
 
Loading
Loading
Loading
Loading
@@ -16,13 +16,13 @@ module Issues
# The code calling this method is responsible for ensuring that a user is
# allowed to close the given issue.
def close_issue(issue, commit: nil, notifications: true, system_note: true)
if project.jira_tracker? && project.jira_service.active
if project.jira_tracker? && project.jira_service.active && issue.is_a?(ExternalIssue)
project.jira_service.close_issue(commit, issue)
todo_service.close_issue(issue, current_user)
return issue
end
 
if project.default_issues_tracker? && issue.close
if project.issues_enabled? && issue.close
event_service.close_issue(issue, current_user)
create_note(issue, commit) if system_note
notification_service.close_issue(issue, current_user) if notifications
Loading
Loading
Loading
Loading
@@ -75,10 +75,10 @@
Registry
 
- if project_nav_tab? :issues
= nav_link(controller: @project.default_issues_tracker? ? [:issues, :labels, :milestones, :boards] : :issues) do
= nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do
= link_to project_issues_path(@project), title: 'Issues', class: 'shortcuts-issues' do
%span
- if @project.default_issues_tracker?
- if @project.issues_enabled?
%span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
Issues
 
Loading
Loading
@@ -113,7 +113,7 @@
Milestones
 
- if project_nav_tab? :merge_requests
= nav_link(controller: @project.default_issues_tracker? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
= nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
= link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
%span
%span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count)
Loading
Loading
Loading
Loading
@@ -23,16 +23,16 @@
Registry
 
- if project_nav_tab? :issues
= nav_link(controller: @project.default_issues_tracker? ? [:issues, :labels, :milestones, :boards] : :issues) do
= nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do
= link_to project_issues_path(@project), title: 'Issues', class: 'shortcuts-issues' do
%span
Issues
- if @project.default_issues_tracker?
- if @project.issues_enabled?
%span.badge.count.issue_counter= number_with_delimiter(issuables_count_for_state(:issues, :opened, finder: IssuesFinder.new(current_user, project_id: @project.id)))
 
- if project_nav_tab? :merge_requests
- controllers = [:merge_requests, 'projects/merge_requests/conflicts']
- controllers.push(:merge_requests, :labels, :milestones) unless @project.default_issues_tracker?
- controllers.push(:merge_requests, :labels, :milestones) unless @project.issues_enabled?
= nav_link(controller: controllers) do
= link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
%span
Loading
Loading
Loading
Loading
@@ -4,7 +4,7 @@
- new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project
 
- page_title "Merge Requests"
- unless @project.default_issues_tracker?
- unless @project.issues_enabled?
= content_for :sub_nav do
= render "projects/merge_requests/head"
 
Loading
Loading
- if @project.default_issues_tracker?
- if @project.issues_enabled?
= render "projects/issues/head"
- else
= render "projects/merge_requests/head"
---
title: Associate Issues tab only with internal issues tracker
merge_request:
author:
Loading
Loading
@@ -4,14 +4,12 @@ GitLab has a great issue tracker but you can also use an external one such as
Jira, Redmine, or Bugzilla. Issue trackers are configurable per GitLab project and allow
you to do the following:
 
- the **Issues** link on the GitLab project pages takes you to the appropriate
issue index of the external tracker
- clicking **New issue** on the project dashboard creates a new issue on the
external tracker
- you can reference these external issues inside GitLab interface
(merge requests, commits, comments) and they will be automatically converted
into links
 
You can have enabled both external and internal GitLab issue trackers in parallel. The **Issues** link always opens the internal issue tracker and in case the internal issue tracker is disabled the link is not visible in the menu.
## Configuration
 
The configuration is done via a project's **Services**.
Loading
Loading
Loading
Loading
@@ -20,10 +20,12 @@ Once you have configured and enabled Bugzilla:
## Referencing issues in Bugzilla
 
Issues in Bugzilla can be referenced in two alternative ways:
1. `#<ID>` where `<ID>` is a number (example `#143`)
1. `#<ID>` where `<ID>` is a number (example `#143`).
2. `<PROJECT>-<ID>` where `<PROJECT>` starts with a capital letter which is
then followed by capital letters, numbers or underscores, and `<ID>` is
a number (example `API_32-143`).
 
We suggest using the longer format if you have both internal and external issue trackers enabled. If you use the shorter format and an issue with the same ID exists in the internal issue tracker the internal issue will be linked.
Please note that `<PROJECT>` part is ignored and links always point to the
address specified in `issues_url`.
Loading
Loading
@@ -30,5 +30,7 @@ Issues in Redmine can be referenced in two alternative ways:
then followed by capital letters, numbers or underscores, and `<ID>` is
a number (example `API_32-143`).
 
We suggest using the longer format if you have both internal and external issue trackers enabled. If you use the shorter format and an issue with the same ID exists in the internal issue tracker the internal issue will be linked.
Please note that `<PROJECT>` part is ignored and links always point to the
address specified in `issues_url`.
Loading
Loading
@@ -109,7 +109,7 @@ module API
user.avatar_url(only_path: false)
end
expose :star_count, :forks_count
expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) && project.default_issues_tracker? }
expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) }
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :public_builds, as: :public_jobs
expose :ci_config_path
Loading
Loading
Loading
Loading
@@ -29,14 +29,6 @@ module API
render_api_error!(errors, 400)
end
 
def issue_entity(project)
if project.has_external_issue_tracker?
Entities::ExternalIssue
else
Entities::IssueBasic
end
end
def find_merge_requests(args = {})
args = params.merge(args)
 
Loading
Loading
@@ -278,7 +270,14 @@ module API
get ':id/merge_requests/:merge_request_iid/closes_issues' do
merge_request = find_merge_request_with_access(params[:merge_request_iid])
issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
present paginate(issues), with: issue_entity(user_project), current_user: current_user
issues = paginate(issues)
external_issues, internal_issues = issues.partition { |issue| issue.is_a?(ExternalIssue) }
data = Entities::IssueBasic.represent(internal_issues, current_user: current_user)
data += Entities::ExternalIssue.represent(external_issues, current_user: current_user)
data.as_json
end
end
end
Loading
Loading
Loading
Loading
@@ -20,7 +20,7 @@ module Banzai
end
 
def url_for_object(issue, project)
IssuesHelper.url_for_issue(issue.iid, project, only_path: context[:only_path])
IssuesHelper.url_for_issue(issue.iid, project, only_path: context[:only_path], internal: true)
end
 
def project_from_ref(ref)
Loading
Loading
Loading
Loading
@@ -21,10 +21,14 @@ module Banzai
gather_attributes_per_project(nodes, self.class.data_attribute)
end
 
private
# we extract only external issue trackers references here, we don't extract cross-project references,
# so we don't need to do anything here.
def can_read_reference?(user, ref_project, node)
can?(user, :read_issue, ref_project)
true
end
def nodes_visible_to_user(user, nodes)
nodes
end
end
end
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment