Commit e950acfb authored by Mark Chao's avatar Mark Chao Committed by Sean McGivern

Resolve "Support GitLab subgroups in Jira development panel"

parent 05542079
...@@ -528,17 +528,36 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -528,17 +528,36 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end end
end end
end end
end
# EE-specific # EE-specific
scope path: '/-/jira', as: :jira do scope path: '/-/jira', as: :jira do
scope path: '*namespace_id', namespace_id: Gitlab::PathRegex.full_namespace_route_regex do scope path: '*namespace_id/:project_id',
resources :projects, path: '/', constraints: { id: Gitlab::PathRegex.project_route_regex }, only: :show namespace_id: Gitlab::Jira::Dvcs::ENCODED_ROUTE_REGEX,
project_id: Gitlab::Jira::Dvcs::ENCODED_ROUTE_REGEX do
scope path: ':project_id', constraints: { project_id: Gitlab::PathRegex.project_route_regex }, module: :projects do get '/', to: redirect { |params, req|
resources :commit, only: :show, constraints: { id: /\h{7,40}/ } ::Gitlab::Jira::Dvcs.restore_full_path(
namespace: params[:namespace_id],
get 'tree/*id', to: 'tree#show', as: nil project: params[:project_id]
end )
end }
get 'commit/:id', constraints: { id: /\h{7,40}/ }, to: redirect { |params, req|
project_full_path = ::Gitlab::Jira::Dvcs.restore_full_path(
namespace: params[:namespace_id],
project: params[:project_id]
)
"/#{project_full_path}/commit/#{params[:id]}"
}
get 'tree/*id', as: nil, to: redirect { |params, req|
project_full_path = ::Gitlab::Jira::Dvcs.restore_full_path(
namespace: params[:namespace_id],
project: params[:project_id]
)
"/#{project_full_path}/tree/#{params[:id]}"
}
end end
end end
...@@ -50,7 +50,7 @@ from the left navigation menu. Click `Link GitHub account` to start creating a n ...@@ -50,7 +50,7 @@ from the left navigation menu. Click `Link GitHub account` to start creating a n
Select GitHub Enterprise for the `Host` field. Select GitHub Enterprise for the `Host` field.
For the `Team or User Account` field, enter the group name of a GitLab group that you have access to. For the `Team or User Account` field, enter the group name of a GitLab group that you have access to. This must be a top-level group, but all its subgroups will be imported.
![Creation of Jira DVCS integration](img/jira_dev_panel_jira_setup_2.png) ![Creation of Jira DVCS integration](img/jira_dev_panel_jira_setup_2.png)
...@@ -97,7 +97,6 @@ Click the links to see your GitLab repository data. ...@@ -97,7 +97,6 @@ Click the links to see your GitLab repository data.
## Limitations ## Limitations
- This integration is currently not supported on GitLab instances under a [relative url][relative-url] (e.g. `http://example.com/gitlab`). - This integration is currently not supported on GitLab instances under a [relative url][relative-url] (e.g. `http://example.com/gitlab`).
- Projects under nested groups are not supported
[existing-jira]: ../user/project/integrations/jira.md [existing-jira]: ../user/project/integrations/jira.md
[jira-development-panel]: https://confluence.atlassian.com/adminjiraserver070/integrating-with-development-tools-776637096.html#Integratingwithdevelopmenttools-Developmentpanelonissues [jira-development-panel]: https://confluence.atlassian.com/adminjiraserver070/integrating-with-development-tools-776637096.html#Integratingwithdevelopmenttools-Developmentpanelonissues
......
---
title: Support GitLab subgroups in Jira development panel
merge_request: 6290
author:
type: added
...@@ -4,14 +4,14 @@ ...@@ -4,14 +4,14 @@
module API module API
module Github module Github
module Entities module Entities
class Namespace < Grape::Entity
expose :path, as: :login
end
class Repository < Grape::Entity class Repository < Grape::Entity
expose :id expose :id
expose :namespace, as: :owner, using: Namespace expose :owner do |project, options|
expose :path, as: :name { login: project.root_namespace.path }
end
expose :name do |project, options|
::Gitlab::Jira::Dvcs.encode_project_name(project)
end
end end
class BranchCommit < Grape::Entity class BranchCommit < Grape::Entity
......
...@@ -28,8 +28,10 @@ module API ...@@ -28,8 +28,10 @@ module API
not_found! unless Gitlab::Jira::Middleware.jira_dvcs_connector?(request.env) not_found! unless Gitlab::Jira::Middleware.jira_dvcs_connector?(request.env)
end end
def find_project_with_access(full_path) def find_project_with_access(params)
project = find_project!(full_path) project = find_project!(
::Gitlab::Jira::Dvcs.restore_full_path(params.slice(:namespace, :project).symbolize_keys)
)
not_found! unless licensed_project?(project) not_found! unless licensed_project?(project)
project project
end end
...@@ -79,7 +81,11 @@ module API ...@@ -79,7 +81,11 @@ module API
use :pagination use :pagination
end end
get ':namespace/repos', requirements: NAMESPACE_ENDPOINT_REQUIREMENTS do get ':namespace/repos', requirements: NAMESPACE_ENDPOINT_REQUIREMENTS do
projects = current_user.authorized_projects.select { |project| licensed_project?(project) } namespace = Namespace.find_by_full_path(params[:namespace])
not_found!('Namespace') unless namespace
projects = current_user.authorized_projects.where(namespace_id: namespace.self_and_descendants).to_a
projects.select! { |project| licensed_project?(project) }
projects = ::Kaminari.paginate_array(projects) projects = ::Kaminari.paginate_array(projects)
present paginate(projects), with: ::API::Github::Entities::Repository present paginate(projects), with: ::API::Github::Entities::Repository
end end
...@@ -124,9 +130,7 @@ module API ...@@ -124,9 +130,7 @@ module API
use :pagination use :pagination
end end
get ':namespace/:project/branches', requirements: PROJECT_ENDPOINT_REQUIREMENTS do get ':namespace/:project/branches', requirements: PROJECT_ENDPOINT_REQUIREMENTS do
namespace = params[:namespace] user_project = find_project_with_access(params)
project = params[:project]
user_project = find_project_with_access("#{namespace}/#{project}")
branches = ::Kaminari.paginate_array(user_project.repository.branches.sort_by(&:name)) branches = ::Kaminari.paginate_array(user_project.repository.branches.sort_by(&:name))
...@@ -137,9 +141,7 @@ module API ...@@ -137,9 +141,7 @@ module API
use :project_full_path use :project_full_path
end end
get ':namespace/:project/commits/:sha', requirements: PROJECT_ENDPOINT_REQUIREMENTS do get ':namespace/:project/commits/:sha', requirements: PROJECT_ENDPOINT_REQUIREMENTS do
namespace = params[:namespace] user_project = find_project_with_access(params)
project = params[:project]
user_project = find_project_with_access("#{namespace}/#{project}")
commit = user_project.commit(params[:sha]) commit = user_project.commit(params[:sha])
......
module Gitlab
module Jira
module Dvcs
ENCODED_SLASH = '@'.freeze
SLASH = '/'.freeze
ENCODED_ROUTE_REGEX = /[a-zA-Z0-9_\-\.#{ENCODED_SLASH}]+/
def self.encode_slash(path)
path.gsub(SLASH, ENCODED_SLASH)
end
def self.decode_slash(path)
path.gsub(ENCODED_SLASH, SLASH)
end
# To present two types of projects stored by Jira,
# Type 1 are projects imported prior to nested group support,
# those project names are not full_path, so they are presented differently
# to maintain backwards compatibility.
# Type 2 are projects imported after nested group support,
# those project names are encoded full path
#
# @param [Project] project
def self.encode_project_name(project)
if project.namespace.has_parent?
encode_slash(project.full_path)
else
project.path
end
end
# To interpret two types of project names stored by Jira (see `encode_project_name`)
#
# @param [String] project
# Either an encoded full path, or just project name
# @param [String] namespace
def self.restore_full_path(namespace:, project:)
if project.include?(ENCODED_SLASH)
project.gsub(ENCODED_SLASH, SLASH)
else
"#{namespace}/#{project}"
end
end
end
end
end
require 'spec_helper'
describe Gitlab::Jira::Dvcs do
describe '.encode_slash' do
it 'replaces slash character' do
expect(described_class.encode_slash('a/b/c')).to eq('a@b@c')
end
it 'ignores path without slash' do
expect(described_class.encode_slash('foo')).to eq('foo')
end
end
describe '.decode_slash' do
it 'replaces slash character' do
expect(described_class.decode_slash('a@b@c')).to eq('a/b/c')
end
it 'ignores path without slash' do
expect(described_class.decode_slash('foo')).to eq('foo')
end
end
describe '.encode_project_name' do
let(:group) { create(:group)}
let(:project) { create(:project, group: group)}
context 'root group' do
it 'returns project path' do
expect(described_class.encode_project_name(project)).to eq(project.path)
end
end
context 'nested group' do
let(:group) { create(:group, :nested)}
it 'returns encoded project full path' do
expect(described_class.encode_project_name(project)).to eq(described_class.encode_slash(project.full_path))
end
end
end
describe '.restore_full_path' do
context 'project name is an encoded full path' do
it 'returns decoded project path' do
expect(described_class.restore_full_path(namespace: 'group1', project: 'group1@group2@project1')).to eq('group1/group2/project1')
end
end
context 'project name is not an encoded full path' do
it 'assumes project belongs to root namespace and returns full project path based on passed in namespace' do
expect(described_class.restore_full_path(namespace: 'group1', project: 'project1')).to eq('group1/project1')
end
end
end
end
...@@ -117,40 +117,78 @@ describe API::V3::Github do ...@@ -117,40 +117,78 @@ describe API::V3::Github do
end end
describe 'GET /users/:namespace/repos' do describe 'GET /users/:namespace/repos' do
context 'authenticated' do let(:group) { create(:group, name: 'foo') }
let(:group) { create(:group) }
let!(:group_project) { create(:project, group: group) } def expect_project_under_namespace(projects, namespace, user)
get v3_api("/users/#{namespace.path}/repos", user)
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(response).to match_response_schema('entities/github/repositories', dir: 'ee')
projects.each do |project|
hash = json_response.find do |hash|
hash['name'] == ::Gitlab::Jira::Dvcs.encode_project_name(project)
end
raise "Project #{project.full_path} not present in response" if hash.nil?
expect(hash['owner']['login']).to eq(namespace.name)
end
expect(json_response.size).to eq(projects.size)
end
context 'group namespace' do
let(:project) { create(:project, group: group) }
before do before do
stub_licensed_features(jira_dev_panel_integration: true) stub_licensed_features(jira_dev_panel_integration: true)
group.add_master(user) group.add_master(user)
end end
it 'returns an array of projects with github format' do it 'returns an array of projects belonging to group with github format' do
get v3_api('/users/foo/repos', user) expect_project_under_namespace([project], group, user)
end
end
expect(response).to have_gitlab_http_status(200) context 'nested group namespace', :nested_groups do
expect(response).to include_pagination_headers let(:group) { create(:group, :nested) }
expect(json_response).to be_an(Array) let!(:parent_group_project) { create(:project, group: group.parent, name: 'parent_group_project') }
expect(json_response.size).to eq(2) let!(:child_group_project) { create(:project, group: group, name: 'child_group_project') }
expect(response).to match_response_schema('entities/github/repositories', dir: 'ee') before do
stub_licensed_features(jira_dev_panel_integration: true)
group.parent.add_master(user)
end end
it 'returns 200 when namespace path include a dot' do it 'returns an array of projects belonging to group with github format' do
group = create(:group, path: 'foo.bar') expect_project_under_namespace([parent_group_project, child_group_project], group.parent, user)
end
end
get v3_api("/users/#{group.path}/repos", user) context 'user namespace' do
let(:project) { create(:project, namespace: user.namespace) }
expect(response).to have_gitlab_http_status(200) before do
stub_licensed_features(jira_dev_panel_integration: true)
end
it 'returns an array of projects belonging to user namespace with github format' do
expect_project_under_namespace([project], user.namespace, user)
end end
end
it 'returns valid project path as name' do context 'namespace path includes a dot' do
get v3_api('/users/foo/repos', user) let(:project) { create(:project, group: group) }
let(:group) { create(:group, name: 'foo.bar') }
project_names = json_response.map { |r| r['name'] } before do
stub_licensed_features(jira_dev_panel_integration: true)
group.add_master(user)
end
expect(project_names).to include(project.path, group_project.path) it 'returns an array of projects belonging to group with github format' do
expect_project_under_namespace([project], group, user)
end end
end end
...@@ -164,7 +202,7 @@ describe API::V3::Github do ...@@ -164,7 +202,7 @@ describe API::V3::Github do
it 'filters unlicensed namespace projects' do it 'filters unlicensed namespace projects' do
silver_plan = create(:silver_plan) silver_plan = create(:silver_plan)
licensed_project = create(:project, :empty_repo) licensed_project = create(:project, :empty_repo, group: group)
licensed_project.add_reporter(user) licensed_project.add_reporter(user)
licensed_project.namespace.update!(plan_id: silver_plan.id) licensed_project.namespace.update!(plan_id: silver_plan.id)
...@@ -172,12 +210,17 @@ describe API::V3::Github do ...@@ -172,12 +210,17 @@ describe API::V3::Github do
stub_application_setting_on_object(project, should_check_namespace_plan: true) stub_application_setting_on_object(project, should_check_namespace_plan: true)
stub_application_setting_on_object(licensed_project, should_check_namespace_plan: true) stub_application_setting_on_object(licensed_project, should_check_namespace_plan: true)
get v3_api('/users/foo/repos', user) expect_project_under_namespace([licensed_project], group, user)
end
expect(response).to have_gitlab_http_status(200) context 'namespace does not exist' do
expect(response).to include_pagination_headers it 'responds with not found status' do
expect(json_response.size).to eq(1) stub_licensed_features(jira_dev_panel_integration: true)
expect(json_response.first['id']).to eq(licensed_project.id)
get v3_api("/users/noo/repos", user)
expect(response).to have_gitlab_http_status(404)
end
end end
end end
......
require 'rails_helper'
describe 'Jira referenced paths', type: :request do
let(:group) { create(:group, name: 'group') }
let(:sub_group) { create(:group, name: 'subgroup', parent: group) }
let(:group_project) { create(:project, name: 'group_project', namespace: group) }
let(:sub_group_project) { create(:project, name: 'sub_group_project', namespace: sub_group) }
before do
login_as user
end
describe 'redirects to projects#show' do
context 'without nested group' do
let(:user) { group_project.creator }
it 'redirects to project' do
get('/-/jira/group/group_project')
expect(response).to redirect_to('/group/group_project')
end
end
context 'with nested group' do
let(:user) { sub_group_project.creator }
it 'redirects to project for root group' do
get('/-/jira/group/group@sub_group@sub_group_project')
expect(response).to redirect_to('/group/sub_group/sub_group_project')
end
it 'redirects to project for nested group' do
get('/-/jira/group@sub_group/group@sub_group@sub_group_project')
expect(response).to redirect_to('/group/sub_group/sub_group_project')
end
end
end
describe 'redirects to projects/commit#show' do
context 'without nested group' do
let(:user) { group_project.creator }
it 'redirects to commits' do
get('/-/jira/group/group_project/commit/1234567')
expect(response).to redirect_to('/group/group_project/commit/1234567')
end
end
context 'with nested group' do
let(:user) { sub_group_project.creator }
it 'redirects to commits' do
get('/-/jira/group/group@sub_group@sub_group_project/commit/1234567')
expect(response).to redirect_to('/group/sub_group/sub_group_project/commit/1234567')
end
end
end
describe 'redirects to projects/tree#show' do
context 'without nested group' do
let(:user) { group_project.creator }
it 'redirects to tree' do
get('/-/jira/group/group_project/tree/1234567')
expect(response).to redirect_to('/group/group_project/tree/1234567')
end
end
context 'with nested group' do
let(:user) { sub_group_project.creator }
it 'redirects to tree' do
get('/-/jira/group/group@sub_group@sub_group_project/tree/1234567')
expect(response).to redirect_to('/group/sub_group/sub_group_project/tree/1234567')
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