Commit f26cd63b authored by Sean McGivern's avatar Sean McGivern

Merge branch 'add_YouTrack_integration' into 'master'

Added YouTrack integration

Closes #42595

See merge request gitlab-org/gitlab-ce!25361
parents b770a70a 1d388205
......@@ -160,6 +160,7 @@ class Project < ActiveRecord::Base
has_one :pushover_service
has_one :jira_service
has_one :redmine_service
has_one :youtrack_service
has_one :custom_issue_tracker_service
has_one :bugzilla_service
has_one :gitlab_issue_tracker_service, inverse_of: :project
......
# frozen_string_literal: true
class YoutrackService < IssueTrackerService
validates :project_url, :issues_url, presence: true, public_url: true, if: :activated?
prop_accessor :description, :project_url, :issues_url
# {PROJECT-KEY}-{NUMBER} Examples: YT-1, PRJ-1
def self.reference_pattern(only_long: false)
if only_long
/(?<issue>\b[A-Z][A-Za-z0-9_]*-\d+)/
else
/(?<issue>\b[A-Z][A-Za-z0-9_]*-\d+)|(#{Issue.reference_prefix}(?<issue>\d+))/
end
end
def title
'YouTrack'
end
def description
if self.properties && self.properties['description'].present?
self.properties['description']
else
'YouTrack issue tracker'
end
end
def self.to_param
'youtrack'
end
def fields
[
{ type: 'text', name: 'description', placeholder: description },
{ type: 'text', name: 'project_url', placeholder: 'Project url', required: true },
{ type: 'text', name: 'issues_url', placeholder: 'Issue url', required: true }
]
end
end
......@@ -266,6 +266,7 @@ def self.available_services_names
prometheus
pushover
redmine
youtrack
slack_slash_commands
slack
teamcity
......
---
title: Add YouTrack integration service
merge_request: 25361
author: Yauhen Kotau @bessorion
type: added
......@@ -1102,3 +1102,39 @@ GET /projects/:id/services/mock-ci
```
[11435]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11435
## YouTrack
YouTrack issue tracker
### Create/Edit YouTrack service
Set YouTrack service for a project.
```
PUT /projects/:id/services/youtrack
```
Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `issues_url` | string | true | Issue url |
| `project_url` | string | true | Project url |
| `description` | string | false | Description |
### Delete YouTrack Service
Delete YouTrack service for a project.
```
DELETE /projects/:id/services/youtrack
```
### Get YouTrack Service Settings
Get YouTrack service settings for a project.
```
GET /projects/:id/services/youtrack
```
# External issue tracker
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:
Jira, Redmine, YouTrack, or Bugzilla. Issue trackers are configurable per GitLab project
and allow you to do the following:
- you can reference these external issues inside GitLab interface
(merge requests, commits, comments) and they will be automatically converted
......@@ -20,6 +20,7 @@ To enable an external issue tracker you must configure the appropriate **Service
Visit the links below for details:
- [Redmine](../user/project/integrations/redmine.md)
- [YouTrack](../user/project/integrations/youtrack.md)
- [Jira](../user/project/integrations/jira.md)
- [Bugzilla](../user/project/integrations/bugzilla.md)
- [Custom Issue Tracker](../user/project/integrations/custom_issue_tracker.md)
......
......@@ -50,6 +50,7 @@ Click on the service links to see further configuration instructions and details
| [Prometheus](prometheus.md) | Monitor the performance of your deployed apps |
| Pushover | Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop |
| [Redmine](redmine.md) | Redmine issue tracker |
| [YouTrack](youtrack.md) | YouTrack issue tracker |
## Services templates
......
# YouTrack Service
JetBrains YouTrack is a web-based issue tracking and project management platform.
Please refer official [documentation](https://www.jetbrains.com/help/youtrack/standalone/YouTrack-Documentation.html) for details about YouTrack itself.
1. To enable the YouTrack integration in a project, navigate to the
[Integrations page](project_services.md#accessing-the-project-services), click
the **YouTrack** service, and fill in the required details on the page as described
in the table below.
| Field | Description |
| ----- | ----------- |
| `description` | A name for the issue tracker (to differentiate between instances, for example) |
| `project_url` | The URL to the project in YouTrack which is being linked to this GitLab project |
| `issues_url` | The URL to the issue in YouTrack project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. |
Once you have configured and enabled YouTrack you'll see the YouTrack link on the GitLab project pages that takes you to the appropriate YouTrack project.
1. To disable the internal issue tracking system in a project, navigate to the General page, expand [Permissions](../settings/index.md#sharing-and-permissions), and slide the Issues switch invalid.
![Issue configuration](img/issue_configuration.png)
## Referencing issues in YouTrack
Issues in YouTrack can be referenced as `<PROJECT>-<ID>` where `<PROJECT>`
starts with a capital letter which is then followed by capital or lower case
letters, numbers or underscores, and `<ID>` is a number (example `Api_32-143`).
`<PROJECT>` part is included into issue_id and links can point any YouTrack
project (`issues_url` + issue_id)
......@@ -155,7 +155,7 @@ For further details, see [Importing issues from CSV](csv_import.md)
Alternatively to GitLab's built-in Issue Tracker, you can also use an [external
tracker](../../../integration/external-issue-tracker.md) such as Jira, Redmine,
or Bugzilla.
YouTrack, or Bugzilla.
### Issue API
......
......@@ -592,6 +592,26 @@ class Services < Grape::API
desc: 'The description of the tracker'
}
],
'youtrack' => [
{
required: true,
name: :project_url,
type: String,
desc: 'The project URL'
},
{
required: true,
name: :issues_url,
type: String,
desc: 'The issues URL'
},
{
required: false,
name: :description,
type: String,
desc: 'The description of the tracker'
}
],
'slack' => [
CHAT_NOTIFICATION_SETTINGS,
CHAT_NOTIFICATION_FLAGS,
......@@ -665,6 +685,7 @@ class Services < Grape::API
PrometheusService,
PushoverService,
RedmineService,
YoutrackService,
SlackService,
MattermostService,
MicrosoftTeamsService,
......
......@@ -313,6 +313,20 @@
end
end
factory :youtrack_project, parent: :project do
has_external_issue_tracker true
after :create do |project|
project.create_youtrack_service(
active: true,
properties: {
'project_url' => 'http://youtrack/projects/project_guid_in_youtrack',
'issues_url' => 'http://youtrack/issues/:id'
}
)
end
end
factory :jira_project, parent: :project do
has_external_issue_tracker true
jira_service
......
......@@ -6,11 +6,17 @@
let(:url) { 'http://tracker.example.com' }
def fill_form(active = true)
def fill_short_form(active = true)
check 'Active' if active
fill_in 'service_project_url', with: url
fill_in 'service_issues_url', with: "#{url}/:id"
end
def fill_full_form(active = true)
fill_short_form(active)
check 'Active' if active
fill_in 'service_new_issue_url', with: url
end
......@@ -21,14 +27,20 @@ def fill_form(active = true)
visit project_settings_integrations_path(project)
end
shared_examples 'external issue tracker activation' do |tracker:|
shared_examples 'external issue tracker activation' do |tracker:, skip_new_issue_url: false|
describe 'user sets and activates the Service' do
context 'when the connection test succeeds' do
before do
stub_request(:head, url).to_return(headers: { 'Content-Type' => 'application/json' })
click_link(tracker)
fill_form
if skip_new_issue_url
fill_short_form
else
fill_full_form
end
click_button('Test settings and save changes')
wait_for_requests
end
......@@ -50,7 +62,13 @@ def fill_form(active = true)
stub_request(:head, url).to_raise(HTTParty::Error)
click_link(tracker)
fill_form
if skip_new_issue_url
fill_short_form
else
fill_full_form
end
click_button('Test settings and save changes')
wait_for_requests
......@@ -69,7 +87,13 @@ def fill_form(active = true)
describe 'user sets the service but keeps it disabled' do
before do
click_link(tracker)
fill_form(false)
if skip_new_issue_url
fill_short_form(false)
else
fill_full_form(false)
end
click_button('Save changes')
end
......@@ -87,6 +111,7 @@ def fill_form(active = true)
end
it_behaves_like 'external issue tracker activation', tracker: 'Redmine'
it_behaves_like 'external issue tracker activation', tracker: 'YouTrack', skip_new_issue_url: true
it_behaves_like 'external issue tracker activation', tracker: 'Bugzilla'
it_behaves_like 'external issue tracker activation', tracker: 'Custom Issue Tracker'
end
require 'spec_helper'
describe 'User activates issue tracker', :js do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:url) { 'http://tracker.example.com' }
def fill_form(active = true)
check 'Active' if active
fill_in 'service_project_url', with: url
fill_in 'service_issues_url', with: "#{url}/:id"
end
before do
project.add_maintainer(user)
sign_in(user)
visit project_settings_integrations_path(project)
end
shared_examples 'external issue tracker activation' do |tracker:|
describe 'user sets and activates the Service' do
context 'when the connection test succeeds' do
before do
stub_request(:head, url).to_return(headers: { 'Content-Type' => 'application/json' })
click_link(tracker)
fill_form
click_button('Test settings and save changes')
wait_for_requests
end
it 'activates the service' do
expect(page).to have_content("#{tracker} activated.")
expect(current_path).to eq(project_settings_integrations_path(project))
end
it 'shows the link in the menu' do
page.within('.nav-sidebar') do
expect(page).to have_link(tracker, href: url)
end
end
end
context 'when the connection test fails' do
it 'activates the service' do
stub_request(:head, url).to_raise(HTTParty::Error)
click_link(tracker)
fill_form
click_button('Test settings and save changes')
wait_for_requests
expect(find('.flash-container-page')).to have_content 'Test failed.'
expect(find('.flash-container-page')).to have_content 'Save anyway'
find('.flash-alert .flash-action').click
wait_for_requests
expect(page).to have_content("#{tracker} activated.")
expect(current_path).to eq(project_settings_integrations_path(project))
end
end
end
describe 'user sets the service but keeps it disabled' do
before do
click_link(tracker)
fill_form(false)
click_button('Save changes')
end
it 'saves but does not activate the service' do
expect(page).to have_content("#{tracker} settings saved, but not activated.")
expect(current_path).to eq(project_settings_integrations_path(project))
end
it 'does not show the external tracker link in the menu' do
page.within('.nav-sidebar') do
expect(page).not_to have_link(tracker, href: url)
end
end
end
end
it_behaves_like 'external issue tracker activation', tracker: 'YouTrack'
end
......@@ -121,6 +121,42 @@ def helper
end
end
context "youtrack project" do
let(:project) { create(:youtrack_project) }
before do
project.update!(issues_enabled: false)
end
context "with right markdown" do
let(:issue) { ExternalIssue.new("YT-123", project) }
let(:reference) { issue.to_reference }
it_behaves_like "external issue tracker"
end
context "with underscores in the prefix" do
let(:issue) { ExternalIssue.new("PRJ_1-123", project) }
let(:reference) { issue.to_reference }
it_behaves_like "external issue tracker"
end
context "with lowercase letters in the prefix" do
let(:issue) { ExternalIssue.new("YTkPrj-123", project) }
let(:reference) { issue.to_reference }
it_behaves_like "external issue tracker"
end
context "with a single-letter prefix" do
let(:issue) { ExternalIssue.new("T-123", project) }
let(:reference) { issue.to_reference }
it_behaves_like "external issue tracker"
end
end
context "jira project" do
let(:project) { create(:jira_project) }
let(:reference) { issue.to_reference }
......
......@@ -233,6 +233,7 @@ project:
- pushover_service
- jira_service
- redmine_service
- youtrack_service
- custom_issue_tracker_service
- bugzilla_service
- gitlab_issue_tracker_service
......
......@@ -6629,6 +6629,26 @@
],
"deploy_keys": [],
"services": [
{
"id": 101,
"title": "YouTrack",
"project_id": 5,
"created_at": "2016-06-14T15:01:51.327Z",
"updated_at": "2016-06-14T15:01:51.327Z",
"active": false,
"properties": {},
"template": false,
"push_events": true,
"issues_events": true,
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
"job_events": true,
"type": "YoutrackService",
"category": "issue_tracker",
"default": false,
"wiki_page_events": true
},
{
"id": 100,
"title": "JetBrains TeamCity CI",
......
require 'spec_helper'
describe YoutrackService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when service is active' do
before do
subject.active = true
end
it { is_expected.to validate_presence_of(:project_url) }
it { is_expected.to validate_presence_of(:issues_url) }
it_behaves_like 'issue tracker service URL attribute', :project_url
it_behaves_like 'issue tracker service URL attribute', :issues_url
end
context 'when service is inactive' do
before do
subject.active = false
end
it { is_expected.not_to validate_presence_of(:project_url) }
it { is_expected.not_to validate_presence_of(:issues_url) }
end
end
describe '.reference_pattern' do
it_behaves_like 'allows project key on reference pattern'
it 'does allow project prefix on the reference' do
expect(described_class.reference_pattern.match('YT-123')[:issue]).to eq('YT-123')
end
end
end
......@@ -50,6 +50,7 @@
it { is_expected.to have_one(:teamcity_service) }
it { is_expected.to have_one(:jira_service) }
it { is_expected.to have_one(:redmine_service) }
it { is_expected.to have_one(:youtrack_service) }
it { is_expected.to have_one(:custom_issue_tracker_service) }
it { is_expected.to have_one(:bugzilla_service) }
it { is_expected.to have_one(:gitlab_issue_tracker_service) }
......
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