Skip to content
Snippets Groups Projects
Commit 9da30769 authored by Adam Niedzielski's avatar Adam Niedzielski
Browse files

Improve support for external issue references

parent 81dba76b
No related branches found
No related tags found
No related merge requests found
Showing
with 83 additions and 102 deletions
Loading
Loading
@@ -14,7 +14,7 @@ module Mentionable
end
 
EXTERNAL_PATTERN = begin
issue_pattern = ExternalIssue.reference_pattern
issue_pattern = IssueTrackerService.reference_pattern
link_patterns = URI.regexp(%w(http https))
reference_pattern(link_patterns, issue_pattern)
end
Loading
Loading
Loading
Loading
@@ -38,11 +38,6 @@ class ExternalIssue
@project.id
end
 
# Pattern used to extract `JIRA-123` issue references from text
def self.reference_pattern
@reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
end
def to_reference(_from_project = nil, full: nil)
id
end
Loading
Loading
Loading
Loading
@@ -727,8 +727,8 @@ class Project < ActiveRecord::Base
end
end
 
def issue_reference_pattern
issues_tracker.reference_pattern
def external_issue_reference_pattern
external_issue_tracker.class.reference_pattern
end
 
def default_issues_tracker?
Loading
Loading
Loading
Loading
@@ -5,7 +5,10 @@ class IssueTrackerService < Service
 
# Pattern used to extract links from comments
# Override this method on services that uses different patterns
def reference_pattern
# 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+)}
end
 
Loading
Loading
Loading
Loading
@@ -18,7 +18,7 @@ class JiraService < IssueTrackerService
end
 
# {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1
def reference_pattern
def self.reference_pattern
@reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
end
 
Loading
Loading
---
title: Improve support for external issue references
merge_request: 12485
author:
Loading
Loading
@@ -8,6 +8,9 @@ you to do the following:
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
 
## Configuration
 
Loading
Loading
Loading
Loading
@@ -16,3 +16,14 @@ Once you have configured and enabled Bugzilla:
- the **Issues** link on the GitLab project pages takes you to the appropriate
Bugzilla product page
- clicking **New issue** on the project dashboard takes you to Bugzilla for entering a new issue
## Referencing issues in Bugzilla
Issues in Bugzilla can be referenced in two alternative ways:
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`).
Please note that `<PROJECT>` part is ignored and links always point to the
address specified in `issues_url`.
Loading
Loading
@@ -21,3 +21,14 @@ Once you have configured and enabled Redmine:
As an example, below is a configuration for a project named gitlab-ci.
 
![Redmine configuration](img/redmine_configuration.png)
## Referencing issues in Redmine
Issues in Redmine can be referenced in two alternative ways:
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`).
Please note that `<PROJECT>` part is ignored and links always point to the
address specified in `issues_url`.
Loading
Loading
@@ -216,12 +216,7 @@ module Banzai
@references_per_project ||= begin
refs = Hash.new { |hash, key| hash[key] = Set.new }
 
regex =
if uses_reference_pattern?
Regexp.union(object_class.reference_pattern, object_class.link_reference_pattern)
else
object_class.link_reference_pattern
end
regex = Regexp.union(object_class.reference_pattern, object_class.link_reference_pattern)
 
nodes.each do |node|
node.to_html.scan(regex) do
Loading
Loading
@@ -323,14 +318,6 @@ module Banzai
value
end
end
# There might be special cases like filters
# that should ignore reference pattern
# eg: IssueReferenceFilter when using a external issues tracker
# In those cases this method should be overridden on the filter subclass
def uses_reference_pattern?
true
end
end
end
end
Loading
Loading
@@ -3,6 +3,8 @@ module Banzai
# HTML filter that replaces external issue tracker references with links.
# References are ignored if the project doesn't use an external issue
# tracker.
#
# This filter does not support cross-project references.
class ExternalIssueReferenceFilter < ReferenceFilter
self.reference_type = :external_issue
 
Loading
Loading
@@ -87,7 +89,7 @@ module Banzai
end
 
def issue_reference_pattern
external_issues_cached(:issue_reference_pattern)
external_issues_cached(:external_issue_reference_pattern)
end
 
private
Loading
Loading
Loading
Loading
@@ -15,10 +15,6 @@ module Banzai
Issue
end
 
def uses_reference_pattern?
context[:project].default_issues_tracker?
end
def find_object(project, iid)
issues_per_project[project][iid]
end
Loading
Loading
@@ -38,13 +34,7 @@ module Banzai
 
projects_per_reference.each do |path, project|
issue_ids = references_per_project[path]
issues =
if project.default_issues_tracker?
project.issues.where(iid: issue_ids.to_a)
else
issue_ids.map { |id| ExternalIssue.new(id, project) }
end
issues = project.issues.where(iid: issue_ids.to_a)
 
issues.each do |issue|
hash[project][issue.iid.to_i] = issue
Loading
Loading
@@ -55,26 +45,6 @@ module Banzai
end
end
 
def object_link_title(object)
if object.is_a?(ExternalIssue)
"Issue in #{object.project.external_issue_tracker.title}"
else
super
end
end
def data_attributes_for(text, project, object, link: false)
if object.is_a?(ExternalIssue)
data_attribute(
project: project.id,
external_issue: object.id,
reference_type: ExternalIssueReferenceFilter.reference_type
)
else
super
end
end
def projects_relation_for_paths(paths)
super(paths).includes(:gitlab_issue_tracker_service)
end
Loading
Loading
Loading
Loading
@@ -4,9 +4,6 @@ module Banzai
self.reference_type = :issue
 
def nodes_visible_to_user(user, nodes)
# It is not possible to check access rights for external issue trackers
return nodes if project && project.external_issue_tracker
issues = issues_for_nodes(nodes)
 
readable_issues = Ability
Loading
Loading
Loading
Loading
@@ -220,7 +220,7 @@ FactoryGirl.define do
active: true,
properties: {
'project_url' => 'http://redmine/projects/project_name_in_redmine',
'issues_url' => "http://redmine/#{project.id}/project_name_in_redmine/:id",
'issues_url' => 'http://redmine/projects/project_name_in_redmine/issues/:id',
'new_issue_url' => 'http://redmine/projects/project_name_in_redmine/issues/new'
}
)
Loading
Loading
Loading
Loading
@@ -88,12 +88,12 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do
 
it 'queries the collection on the first call' do
expect_any_instance_of(Project).to receive(:default_issues_tracker?).once.and_call_original
expect_any_instance_of(Project).to receive(:issue_reference_pattern).once.and_call_original
expect_any_instance_of(Project).to receive(:external_issue_reference_pattern).once.and_call_original
 
not_cached = reference_filter.call("look for #{reference}", { project: project })
 
expect_any_instance_of(Project).not_to receive(:default_issues_tracker?)
expect_any_instance_of(Project).not_to receive(:issue_reference_pattern)
expect_any_instance_of(Project).not_to receive(:external_issue_reference_pattern)
 
cached = reference_filter.call("look for #{reference}", { project: project })
 
Loading
Loading
Loading
Loading
@@ -39,13 +39,6 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
 
let(:reference) { "##{issue.iid}" }
 
it 'ignores valid references when using non-default tracker' do
allow(project).to receive(:default_issues_tracker?).and_return(false)
exp = act = "Issue #{reference}"
expect(reference_filter(act).to_html).to eq exp
end
it 'links to a valid reference' do
doc = reference_filter("Fixed #{reference}")
 
Loading
Loading
@@ -340,24 +333,6 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
.to eq({ project => { issue.iid => issue } })
end
end
context 'using an external issue tracker' do
it 'returns a Hash containing the issues per project' do
doc = Nokogiri::HTML.fragment('')
filter = described_class.new(doc, project: project)
expect(project).to receive(:default_issues_tracker?).and_return(false)
expect(filter).to receive(:projects_per_reference)
.and_return({ project.path_with_namespace => project })
expect(filter).to receive(:references_per_project)
.and_return({ project.path_with_namespace => Set.new([1]) })
expect(filter.issues_per_project[project][1])
.to be_an_instance_of(ExternalIssue)
end
end
end
 
describe '.references_in' do
Loading
Loading
require 'rails_helper'
describe Banzai::Pipeline::GfmPipeline do
describe 'integration between parsing regular and external issue references' do
let(:project) { create(:redmine_project, :public) }
it 'allows to use shorthand external reference syntax for Redmine' do
markdown = '#12'
result = described_class.call(markdown, project: project)[:output]
link = result.css('a').first
expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
end
it 'parses cross-project references to regular issues' do
other_project = create(:empty_project, :public)
issue = create(:issue, project: other_project)
markdown = issue.to_reference(project, full: true)
result = described_class.call(markdown, project: project)[:output]
link = result.css('a').first
expect(link['href']).to eq(
Gitlab::Routing.url_helpers.namespace_project_issue_path(
other_project.namespace,
other_project,
issue
)
)
end
end
end
Loading
Loading
@@ -39,16 +39,6 @@ describe Banzai::ReferenceParser::IssueParser, lib: true do
expect(subject.nodes_visible_to_user(user, [link])).to eq([])
end
end
context 'when the project uses an external issue tracker' do
it 'returns all nodes' do
link = double(:link)
expect(project).to receive(:external_issue_tracker).and_return(true)
expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
end
end
end
 
describe '#referenced_by' do
Loading
Loading
Loading
Loading
@@ -64,12 +64,12 @@ describe JiraService, models: true do
end
end
 
describe '#reference_pattern' do
describe '.reference_pattern' do
it_behaves_like 'allows project key on reference pattern'
 
it 'does not allow # on the code' do
expect(subject.reference_pattern.match('#123')).to be_nil
expect(subject.reference_pattern.match('1#23#12')).to be_nil
expect(described_class.reference_pattern.match('#123')).to be_nil
expect(described_class.reference_pattern.match('1#23#12')).to be_nil
end
end
 
Loading
Loading
Loading
Loading
@@ -31,11 +31,11 @@ describe RedmineService, models: true do
end
end
 
describe '#reference_pattern' do
describe '.reference_pattern' do
it_behaves_like 'allows project key on reference pattern'
 
it 'does allow # on the reference' do
expect(subject.reference_pattern.match('#123')[:issue]).to eq('123')
expect(described_class.reference_pattern.match('#123')[:issue]).to eq('123')
end
end
end
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