mirror of
https://github.com/wahyd4/gitlabhq.git
synced 2026-08-09 04:36:11 +10:00
Backport JIRA service
This commit is contained in:
@@ -19,6 +19,7 @@ v 8.3.0 (unreleased)
|
||||
- Fix 500 error when update group member permission
|
||||
- Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera)
|
||||
- Recognize issue/MR/snippet/commit links as references
|
||||
- Backport JIRA features from EE to CE
|
||||
- Add ignore whitespace change option to commit view
|
||||
- Fire update hook from GitLab
|
||||
- Style warning about mentioning many people in a comment
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Projects::ServicesController < Projects::ApplicationController
|
||||
ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_version, :subdomain,
|
||||
ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_url, :api_version, :subdomain,
|
||||
:room, :recipients, :project_url, :webhook,
|
||||
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
|
||||
:build_key, :server, :teamcity_url, :drone_url, :build_type,
|
||||
@@ -10,7 +10,8 @@ class Projects::ServicesController < Projects::ApplicationController
|
||||
:notify_only_broken_builds, :add_pusher,
|
||||
:send_from_committer_email, :disable_diffs, :external_wiki_url,
|
||||
:notify, :color,
|
||||
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification]
|
||||
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification,
|
||||
:jira_issue_transition_id]
|
||||
|
||||
# Parameters to ignore if no value is specified
|
||||
FILTER_BLANK_PARAMS = [:password]
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
class JiraIssue < ExternalIssue
|
||||
end
|
||||
@@ -335,7 +335,7 @@ class MergeRequest < ActiveRecord::Base
|
||||
issues = commits.flat_map { |c| c.closes_issues(current_user) }
|
||||
issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user).
|
||||
closed_by_message(description))
|
||||
issues.uniq
|
||||
issues.uniq(&:id)
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
@@ -499,6 +499,10 @@ class Project < ActiveRecord::Base
|
||||
@ci_service ||= ci_services.find(&:activated?)
|
||||
end
|
||||
|
||||
def jira_tracker?
|
||||
issues_tracker.to_param == 'jira'
|
||||
end
|
||||
|
||||
def avatar_type
|
||||
unless self.avatar.image?
|
||||
self.errors.add :avatar, 'only images allowed'
|
||||
@@ -799,6 +803,10 @@ class Project < ActiveRecord::Base
|
||||
false
|
||||
end
|
||||
|
||||
def jira_tracker_active?
|
||||
jira_tracker? && jira_service.active
|
||||
end
|
||||
|
||||
def ci_commit(sha)
|
||||
ci_commits.find_by(sha: sha)
|
||||
end
|
||||
|
||||
@@ -19,9 +19,24 @@
|
||||
#
|
||||
|
||||
class JiraService < IssueTrackerService
|
||||
include HTTParty
|
||||
include Gitlab::Application.routes.url_helpers
|
||||
|
||||
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
|
||||
DEFAULT_API_VERSION = 2
|
||||
|
||||
prop_accessor :username, :password, :api_url, :jira_issue_transition_id,
|
||||
:title, :description, :project_url, :issues_url, :new_issue_url
|
||||
|
||||
before_validation :set_api_url, :set_jira_issue_transition_id
|
||||
|
||||
before_update :reset_password
|
||||
|
||||
def reset_password
|
||||
# don't reset the password if a new one is provided
|
||||
if api_url_changed? && !password_touched?
|
||||
self.password = nil
|
||||
end
|
||||
end
|
||||
|
||||
def help
|
||||
line1 = 'Setting `project_url`, `issues_url` and `new_issue_url` will '\
|
||||
@@ -54,4 +69,228 @@ class JiraService < IssueTrackerService
|
||||
def to_param
|
||||
'jira'
|
||||
end
|
||||
|
||||
def fields
|
||||
super.push(
|
||||
{ type: 'text', name: 'api_url', placeholder: 'https://jira.example.com/rest/api/2' },
|
||||
{ type: 'text', name: 'username', placeholder: '' },
|
||||
{ type: 'password', name: 'password', placeholder: '' },
|
||||
{ type: 'text', name: 'jira_issue_transition_id', placeholder: '2' }
|
||||
)
|
||||
end
|
||||
|
||||
def execute(push, issue = nil)
|
||||
if issue.nil?
|
||||
# No specific issue, that means
|
||||
# we just want to test settings
|
||||
test_settings
|
||||
else
|
||||
close_issue(push, issue)
|
||||
end
|
||||
end
|
||||
|
||||
def create_cross_reference_note(mentioned, noteable, author)
|
||||
issue_name = mentioned.id
|
||||
project = self.project
|
||||
noteable_name = noteable.class.name.underscore.downcase
|
||||
noteable_id = if noteable.is_a?(Commit)
|
||||
noteable.id
|
||||
else
|
||||
noteable.iid
|
||||
end
|
||||
|
||||
entity_url = build_entity_url(noteable_name.to_sym, noteable_id)
|
||||
|
||||
data = {
|
||||
user: {
|
||||
name: author.name,
|
||||
url: resource_url(user_path(author)),
|
||||
},
|
||||
project: {
|
||||
name: project.path_with_namespace,
|
||||
url: resource_url(namespace_project_path(project.namespace, project))
|
||||
},
|
||||
entity: {
|
||||
name: noteable_name.humanize.downcase,
|
||||
url: entity_url
|
||||
}
|
||||
}
|
||||
|
||||
add_comment(data, issue_name)
|
||||
end
|
||||
|
||||
def test_settings
|
||||
result = JiraService.get(
|
||||
jira_api_test_url,
|
||||
headers: {
|
||||
'Content-Type' => 'application/json',
|
||||
'Authorization' => "Basic #{auth}"
|
||||
}
|
||||
)
|
||||
|
||||
case result.code
|
||||
when 201, 200
|
||||
Rails.logger.info("#{self.class.name} SUCCESS #{result.code}: Successfully connected to #{api_url}.")
|
||||
true
|
||||
else
|
||||
Rails.logger.info("#{self.class.name} ERROR #{result.code}: #{result.parsed_response}")
|
||||
false
|
||||
end
|
||||
rescue Errno::ECONNREFUSED => e
|
||||
Rails.logger.info "#{self.class.name} ERROR: #{e.message}. API URL: #{api_url}."
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_api_url_from_project_url
|
||||
server = URI(project_url)
|
||||
default_ports = [["http",80],["https",443]].include?([server.scheme,server.port])
|
||||
server_url = "#{server.scheme}://#{server.host}"
|
||||
server_url.concat(":#{server.port}") unless default_ports
|
||||
"#{server_url}/rest/api/#{DEFAULT_API_VERSION}"
|
||||
rescue
|
||||
"" # looks like project URL was not valid
|
||||
end
|
||||
|
||||
def set_api_url
|
||||
self.api_url = build_api_url_from_project_url if self.api_url.blank?
|
||||
end
|
||||
|
||||
def set_jira_issue_transition_id
|
||||
self.jira_issue_transition_id ||= "2"
|
||||
end
|
||||
|
||||
def close_issue(entity, issue)
|
||||
commit_id = if entity.is_a?(Commit)
|
||||
entity.id
|
||||
elsif entity.is_a?(MergeRequest)
|
||||
entity.last_commit.id
|
||||
end
|
||||
commit_url = build_entity_url(:commit, commit_id)
|
||||
|
||||
# Depending on the JIRA project's workflow, a comment during transition
|
||||
# may or may not be allowed. Split the operation in to two calls so the
|
||||
# comment always works.
|
||||
transition_issue(issue)
|
||||
add_issue_solved_comment(issue, commit_id, commit_url)
|
||||
end
|
||||
|
||||
def transition_issue(issue)
|
||||
message = {
|
||||
transition: {
|
||||
id: jira_issue_transition_id
|
||||
}
|
||||
}
|
||||
send_message(close_issue_url(issue.iid), message.to_json)
|
||||
end
|
||||
|
||||
def add_issue_solved_comment(issue, commit_id, commit_url)
|
||||
comment = {
|
||||
body: "Issue solved with [#{commit_id}|#{commit_url}]."
|
||||
}
|
||||
|
||||
send_message(comment_url(issue.iid), comment.to_json)
|
||||
end
|
||||
|
||||
def add_comment(data, issue_name)
|
||||
url = comment_url(issue_name)
|
||||
user_name = data[:user][:name]
|
||||
user_url = data[:user][:url]
|
||||
entity_name = data[:entity][:name]
|
||||
entity_url = data[:entity][:url]
|
||||
project_name = data[:project][:name]
|
||||
|
||||
message = {
|
||||
body: "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]."
|
||||
}
|
||||
|
||||
unless existing_comment?(issue_name, message[:body])
|
||||
send_message(url, message.to_json)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def auth
|
||||
require 'base64'
|
||||
Base64.urlsafe_encode64("#{self.username}:#{self.password}")
|
||||
end
|
||||
|
||||
def send_message(url, message)
|
||||
result = JiraService.post(
|
||||
url,
|
||||
body: message,
|
||||
headers: {
|
||||
'Content-Type' => 'application/json',
|
||||
'Authorization' => "Basic #{auth}"
|
||||
}
|
||||
)
|
||||
|
||||
message = case result.code
|
||||
when 201, 200, 204
|
||||
"#{self.class.name} SUCCESS #{result.code}: Successfully posted to #{url}."
|
||||
when 401
|
||||
"#{self.class.name} ERROR 401: Unauthorized. Check the #{self.username} credentials and JIRA access permissions and try again."
|
||||
else
|
||||
"#{self.class.name} ERROR #{result.code}: #{result.parsed_response}"
|
||||
end
|
||||
|
||||
Rails.logger.info(message)
|
||||
message
|
||||
rescue URI::InvalidURIError, Errno::ECONNREFUSED => e
|
||||
Rails.logger.info "#{self.class.name} ERROR: #{e.message}. Hostname: #{url}."
|
||||
end
|
||||
|
||||
def existing_comment?(issue_name, new_comment)
|
||||
result = JiraService.get(
|
||||
comment_url(issue_name),
|
||||
headers: {
|
||||
'Content-Type' => 'application/json',
|
||||
'Authorization' => "Basic #{auth}"
|
||||
}
|
||||
)
|
||||
|
||||
case result.code
|
||||
when 201, 200
|
||||
existing_comments = JSON.parse(result.body)['comments']
|
||||
|
||||
if existing_comments.present?
|
||||
return existing_comments.map { |comment| comment['body'].include?(new_comment) }.any?
|
||||
end
|
||||
end
|
||||
|
||||
false
|
||||
rescue JSON::ParserError
|
||||
false
|
||||
end
|
||||
|
||||
def resource_url(resource)
|
||||
"#{Settings.gitlab['url'].chomp("/")}#{resource}"
|
||||
end
|
||||
|
||||
def build_entity_url(entity_name, entity_id)
|
||||
resource_url(
|
||||
polymorphic_url(
|
||||
[
|
||||
self.project.namespace.becomes(Namespace),
|
||||
self.project,
|
||||
entity_name
|
||||
],
|
||||
id: entity_id,
|
||||
routing_type: :path
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def close_issue_url(issue_name)
|
||||
"#{self.api_url}/issue/#{issue_name}/transitions"
|
||||
end
|
||||
|
||||
def comment_url(issue_name)
|
||||
"#{self.api_url}/issue/#{issue_name}/comment"
|
||||
end
|
||||
|
||||
def jira_api_test_url
|
||||
"#{self.api_url}/myself"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
module Issues
|
||||
class CloseService < Issues::BaseService
|
||||
def execute(issue, commit = nil)
|
||||
if project.jira_tracker? && project.jira_service.active
|
||||
project.jira_service.execute(commit, issue)
|
||||
return issue
|
||||
end
|
||||
|
||||
if project.default_issues_tracker? && issue.close
|
||||
event_service.close_issue(issue, current_user)
|
||||
create_note(issue, commit)
|
||||
|
||||
@@ -241,9 +241,14 @@ class SystemNoteService
|
||||
note_options.merge!(noteable: noteable)
|
||||
end
|
||||
|
||||
create_note(note_options)
|
||||
if noteable.is_a?(ExternalIssue)
|
||||
noteable.project.issues_tracker.create_cross_reference_note(noteable, mentioner, author)
|
||||
else
|
||||
create_note(note_options)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def self.cross_reference?(note_text)
|
||||
note_text.start_with?(cross_reference_note_prefix)
|
||||
end
|
||||
@@ -259,7 +264,7 @@ class SystemNoteService
|
||||
#
|
||||
# Returns Boolean
|
||||
def self.cross_reference_disallowed?(noteable, mentioner)
|
||||
return true if noteable.is_a?(ExternalIssue)
|
||||
return true if noteable.is_a?(ExternalIssue) && !noteable.project.jira_tracker_active?
|
||||
return false unless mentioner.is_a?(MergeRequest)
|
||||
return false unless noteable.is_a?(Commit)
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].
|
||||
Settings.gitlab['twitter_sharing_enabled'] ||= true if Settings.gitlab['twitter_sharing_enabled'].nil?
|
||||
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
|
||||
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
|
||||
Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil?
|
||||
Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z]*-\d*))+)' if Settings.gitlab['issue_closing_pattern'].nil?
|
||||
Settings.gitlab['default_projects_features'] ||= {}
|
||||
Settings.gitlab['webhook_timeout'] ||= 10
|
||||
Settings.gitlab['max_attachment_size'] ||= 10
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
class SetJiraServiceApiUrl < ActiveRecord::Migration
|
||||
# This migration can be performed online without errors, but some Jira API calls may be missed
|
||||
# when doing so because api_url is not yet available.
|
||||
|
||||
def build_api_url_from_project_url(project_url, api_version)
|
||||
# this is the exact logic previously used to build the Jira API URL from project_url
|
||||
server = URI(project_url)
|
||||
default_ports = [80, 443].include?(server.port)
|
||||
server_url = "#{server.scheme}://#{server.host}"
|
||||
server_url.concat(":#{server.port}") unless default_ports
|
||||
"#{server_url}/rest/api/#{api_version}"
|
||||
end
|
||||
|
||||
def get_api_version_from_api_url(api_url)
|
||||
match = /\/rest\/api\/(?<api_version>\w+)$/.match(api_url)
|
||||
match && match['api_version']
|
||||
end
|
||||
|
||||
def change
|
||||
reversible do |dir|
|
||||
select_all("SELECT id, properties FROM services WHERE services.type IN ('JiraService')").each do |jira_service|
|
||||
id = jira_service["id"]
|
||||
properties = JSON.parse(jira_service["properties"])
|
||||
properties_was = properties.clone
|
||||
|
||||
dir.up do
|
||||
# remove api_version and set api_url
|
||||
if properties['api_version'].present? && properties['project_url'].present?
|
||||
begin
|
||||
properties['api_url'] ||= build_api_url_from_project_url(properties['project_url'], properties['api_version'])
|
||||
rescue
|
||||
# looks like project_url was not a valid URL. Do nothing.
|
||||
end
|
||||
end
|
||||
properties.delete('api_version') if properties.include?('api_version')
|
||||
end
|
||||
|
||||
dir.down do
|
||||
# remove api_url and set api_version (default to '2')
|
||||
properties['api_version'] ||= get_api_version_from_api_url(properties['api_url']) || '2'
|
||||
properties.delete('api_url') if properties.include?('api_url')
|
||||
end
|
||||
|
||||
if properties != properties_was
|
||||
execute("UPDATE services SET properties = '#{quote_string(properties.to_json)}' WHERE id = #{id}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,6 +4,7 @@ GitLab integrates with multiple third-party services to allow external issue tra
|
||||
|
||||
See the documentation below for details on how to configure these services.
|
||||
|
||||
- [Jira](jira.md) Integrate with the JIRA issue tracker
|
||||
- [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc.
|
||||
- [LDAP](ldap.md) Set up sign in via LDAP
|
||||
- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab, and Google via OAuth.
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
# GitLab Jira integration
|
||||
|
||||
GitLab can be configured to interact with Jira.
|
||||
Configuration happens via username and password.
|
||||
Connecting to a Jira server via CAS is not possible.
|
||||
|
||||
Each project can be configured to connect to a different Jira instance, configuration is explained [here](#configuration).
|
||||
If you have one Jira instance you can pre-fill the settings page with a default template. To configure the template [see external issue tracker document](external-issue-tracker.md#service-template)).
|
||||
|
||||
Once the project is connected to Jira, you can reference and close the issues in Jira directly from GitLab.
|
||||
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Referencing Jira Issues from GitLab](#referencing-jira-issues)
|
||||
* [Closing Jira Issues from GitLab](#closing-jira-issues)
|
||||
* [Configuration](#configuration)
|
||||
|
||||
### Referencing Jira Issues
|
||||
|
||||
When GitLab project has Jira issue tracker configured and enabled, mentioning Jira issue in GitLab will automatically add a comment in Jira issue with the link back to GitLab. This means that in comments in merge requests and commits referencing an issue, eg. `PROJECT-7`, will add a comment in Jira issue in the format:
|
||||
|
||||
|
||||
```
|
||||
USER mentioned this issue in LINK_TO_THE_MENTION
|
||||
```
|
||||
|
||||
* `USER` A user that mentioned the issue. This is the link to the user profile in GitLab.
|
||||
* `LINK_TO_THE_MENTION` Link to the origin of mention with a name of the entity where Jira issue was mentioned.
|
||||
Can be commit or merge request.
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
### Closing Jira Issues
|
||||
|
||||
Jira issues can be closed directly from GitLab by using trigger words, eg. `Resolves PROJECT-1`, `Closes PROJECT-1` or `Fixes PROJECT-1`, in commits and merge requests.
|
||||
When a commit which contains the trigger word in the commit message is pushed, GitLab will add a comment in the mentioned Jira issue.
|
||||
|
||||
For example, for project named PROJECT in Jira, we implemented a new feature and created a merge request in GitLab.
|
||||
|
||||
This feature was requested in Jira issue PROJECT-7. Merge request in GitLab contains the improvement and in merge request description we say that this merge request `Closes PROJECT-7` issue.
|
||||
|
||||
Once this merge request is merged, Jira issue will be automatically closed with a link to the commit that resolved the issue.
|
||||
|
||||

|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
### Configuring JIRA
|
||||
|
||||
We need to create a user in JIRA which will have access to all projects that need to integrate with GitLab.
|
||||
Login to your JIRA instance as admin and under Administration go to User Management and create a new user.
|
||||
As an example, we'll create a user named `gitlab` and add it to `jira-developers` group.
|
||||
|
||||
**It is important that the user `gitlab` has write-access to projects in JIRA**
|
||||
|
||||
### Configuring GitLab
|
||||
|
||||
### GitLab 7.8 EE and up with JIRA v6.x
|
||||
|
||||
To enable JIRA integration in a project, navigate to the project Settings page and go to Services. Here you will find JIRA.
|
||||
|
||||
Fill in the required details on the page:
|
||||
|
||||

|
||||
|
||||
* `description` A name for the issue tracker (to differentiate between instances, for instance).
|
||||
* `project url` The URL to the JIRA project which is being linked to this GitLab project.
|
||||
* `issues url` The URL to the JIRA project issues overview for the project that is linked to this GitLab project.
|
||||
* `new issue url` This is the URL to create a new issue in JIRA for the project linked to this GitLab project.
|
||||
* `api url` The base URL of the JIRA API. It may be omitted, in which case GitLab will automatically use API version `2` based on the `project url`, i.e. `https://jira.example.com/rest/api/2`.
|
||||
* `username` The username of the user created in [configuring JIRA step](#configuring-jira).
|
||||
* `password` The password of the user created in [configuring JIRA step](#configuring-jira).
|
||||
* `Jira issue transition` This is the id of a transition that moves issues to a closed state. You can find this number under [JIRA workflow administration, see screenshot](jira_workflow_screenshot.png). By default, this id is `2`. (In the example image, this is `2` as well)
|
||||
|
||||
After saving the configuration, your GitLab project will be able to interact with the linked JIRA project.
|
||||
|
||||
|
||||
### GitLab 6.x-7.7 with JIRA v6.x
|
||||
|
||||
**Note: GitLab 7.8 and up contain various integration improvements. We strongly recommend upgrading.**
|
||||
|
||||
|
||||
In `gitlab.yml` enable [JIRA issue tracker section by uncommenting the lines](https://gitlab.com/subscribers/gitlab-ee/blob/6-8-stable-ee/config/gitlab.yml.example#L111-115).
|
||||
This will make sure that all issues within GitLab are pointing to the JIRA issue tracker.
|
||||
|
||||
We can also enable JIRA service that will allow us to interact with JIRA issues.
|
||||
|
||||
For example, we can close issues in JIRA by a commit in GitLab.
|
||||
|
||||
Go to project settings page and fill in the project name for the JIRA project:
|
||||
|
||||

|
||||
|
||||
Next, go to the services page and find JIRA.
|
||||
|
||||

|
||||
|
||||
1. Tick the active check box to enable the service.
|
||||
1. Supply the url to JIRA server, for example http://jira.sample
|
||||
1. Supply the username of a user we created under `Configuring JIRA` section, for example `gitlab`
|
||||
1. Supply the password of the user
|
||||
1. Optional: supply the JIRA api version, default is version
|
||||
1. Optional: supply the JIRA issue transition ID (issue transition to closed). This is dependant on JIRA settings, default is 2
|
||||
1. Save
|
||||
|
||||
Now we should be able to interact with JIRA issues.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 159 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
@@ -55,6 +55,12 @@ Feature: Project Services
|
||||
And I fill email on push settings
|
||||
Then I should see email on push service settings saved
|
||||
|
||||
Scenario: Activate JIRA service
|
||||
When I visit project "Shop" services page
|
||||
And I click jira service link
|
||||
And I fill jira settings
|
||||
Then I should see jira service settings saved
|
||||
|
||||
Scenario: Activate Irker (IRC Gateway) service
|
||||
When I visit project "Shop" services page
|
||||
And I click Irker service link
|
||||
|
||||
@@ -173,6 +173,24 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
|
||||
expect(find_field('Sound').find('option[selected]').value).to eq 'bike'
|
||||
end
|
||||
|
||||
step 'I click jira service link' do
|
||||
click_link 'JIRA'
|
||||
end
|
||||
|
||||
step 'I fill jira settings' do
|
||||
fill_in 'Project url', with: 'http://jira.example'
|
||||
fill_in 'Username', with: 'gitlab'
|
||||
fill_in 'Password', with: 'gitlab'
|
||||
fill_in 'Api url', with: 'http://jira.example/rest/api/2'
|
||||
click_button 'Save'
|
||||
end
|
||||
|
||||
step 'I should see jira service settings saved' do
|
||||
expect(find_field('Project url').value).to eq 'http://jira.example'
|
||||
expect(find_field('Username').value).to eq 'gitlab'
|
||||
expect(find_field('Api url').value).to eq 'http://jira.example/rest/api/2'
|
||||
end
|
||||
|
||||
step 'I click Atlassian Bamboo CI service link' do
|
||||
click_link 'Atlassian Bamboo CI'
|
||||
end
|
||||
|
||||
@@ -23,6 +23,18 @@ module Banzai
|
||||
end
|
||||
end
|
||||
|
||||
def self.referenced_by(node)
|
||||
project = Project.find(node.attr("data-project")) rescue nil
|
||||
return unless project
|
||||
|
||||
id = node.attr("data-external-issue")
|
||||
external_issue = ExternalIssue.new(id, project)
|
||||
|
||||
return unless external_issue
|
||||
|
||||
{ external_issue: external_issue }
|
||||
end
|
||||
|
||||
def call
|
||||
# Early return if the project isn't using an external tracker
|
||||
return doc if project.nil? || project.default_issues_tracker?
|
||||
@@ -46,12 +58,14 @@ module Banzai
|
||||
def issue_link_filter(text, link_text: nil)
|
||||
project = context[:project]
|
||||
|
||||
self.class.references_in(text) do |match, issue|
|
||||
url = url_for_issue(issue, project, only_path: context[:only_path])
|
||||
self.class.references_in(text) do |match, id|
|
||||
ExternalIssue.new(id, project)
|
||||
|
||||
url = url_for_issue(id, project, only_path: context[:only_path])
|
||||
|
||||
title = escape_once("Issue in #{project.external_issue_tracker.title}")
|
||||
klass = reference_class(:issue)
|
||||
data = data_attribute(project: project.id)
|
||||
data = data_attribute(project: project.id, external_issue: id)
|
||||
|
||||
text = link_text || match
|
||||
|
||||
|
||||
@@ -18,10 +18,20 @@ module Gitlab
|
||||
super(text, context.merge(project: project))
|
||||
end
|
||||
|
||||
%i(user label issue merge_request snippet commit commit_range).each do |type|
|
||||
%i(user label merge_request snippet commit commit_range).each do |type|
|
||||
define_method("#{type}s") do
|
||||
@references[type] ||= references(type, project: project, current_user: current_user)
|
||||
end
|
||||
end
|
||||
|
||||
def issues
|
||||
options = { project: project, current_user: current_user }
|
||||
|
||||
if project && project.jira_tracker?
|
||||
@references[:external_issue] ||= references(:external_issue, options)
|
||||
else
|
||||
@references[:issue] ||= references(:issue, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,24 +1,37 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe MergeRequestsHelper do
|
||||
describe "#issues_sentence" do
|
||||
describe '#issues_sentence' do
|
||||
subject { issues_sentence(issues) }
|
||||
let(:issues) do
|
||||
[build(:issue, iid: 1), build(:issue, iid: 2), build(:issue, iid: 3)]
|
||||
end
|
||||
|
||||
it { is_expected.to eq('#1, #2, and #3') }
|
||||
|
||||
context 'for JIRA issues' do
|
||||
let(:project) { create(:project) }
|
||||
let(:issues) do
|
||||
[
|
||||
JiraIssue.new('JIRA-123', project),
|
||||
JiraIssue.new('JIRA-456', project),
|
||||
JiraIssue.new('FOOBAR-7890', project)
|
||||
]
|
||||
end
|
||||
|
||||
it { is_expected.to eq('FOOBAR-7890, JIRA-123, and JIRA-456') }
|
||||
end
|
||||
end
|
||||
|
||||
describe "#format_mr_branch_names" do
|
||||
describe "within the same project" do
|
||||
describe '#format_mr_branch_names' do
|
||||
describe 'within the same project' do
|
||||
let(:merge_request) { create(:merge_request) }
|
||||
subject { format_mr_branch_names(merge_request) }
|
||||
|
||||
it { is_expected.to eq([merge_request.source_branch, merge_request.target_branch]) }
|
||||
end
|
||||
|
||||
describe "within different projects" do
|
||||
describe 'within different projects' do
|
||||
let(:project) { create(:project) }
|
||||
let(:fork_project) { create(:project, forked_from_project: project) }
|
||||
let(:merge_request) { create(:merge_request, source_project: fork_project, target_project: project) }
|
||||
|
||||
@@ -97,6 +97,16 @@ describe Gitlab::ReferenceExtractor, lib: true do
|
||||
expect(extracted.first.commit_to).to eq commit
|
||||
end
|
||||
|
||||
context 'with an external issue tracker' do
|
||||
let(:project) { create(:jira_project) }
|
||||
subject { described_class.new(project, project.creator) }
|
||||
|
||||
it 'returns JIRA issues for a JIRA-integrated project' do
|
||||
subject.analyze('JIRA-123 and FOOBAR-4567')
|
||||
expect(subject.issues).to eq [JiraIssue.new('JIRA-123', project), JiraIssue.new('FOOBAR-4567', project)]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a project with an underscore' do
|
||||
let(:other_project) { create(:project, path: 'test_project') }
|
||||
let(:issue) { create(:issue, project: other_project) }
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Mentionable do
|
||||
include Mentionable
|
||||
|
||||
describe :references do
|
||||
let(:project) { create(:project) }
|
||||
|
||||
it 'excludes JIRA references' do
|
||||
allow(project).to receive_messages(jira_tracker?: true)
|
||||
expect(referenced_mentionables(project, 'JIRA-123')).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe Issue, "Mentionable" do
|
||||
describe '#mentioned_users' do
|
||||
let!(:user) { create(:user, username: 'stranger') }
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe JiraIssue do
|
||||
let(:project) { create(:project) }
|
||||
subject { JiraIssue.new('JIRA-123', project) }
|
||||
|
||||
describe 'id' do
|
||||
subject { super().id }
|
||||
it { is_expected.to eq('JIRA-123') }
|
||||
end
|
||||
|
||||
describe 'iid' do
|
||||
subject { super().iid }
|
||||
it { is_expected.to eq('JIRA-123') }
|
||||
end
|
||||
|
||||
describe 'to_s' do
|
||||
subject { super().to_s }
|
||||
it { is_expected.to eq('JIRA-123') }
|
||||
end
|
||||
|
||||
describe :== do
|
||||
specify { expect(subject).to eq(JiraIssue.new('JIRA-123', project)) }
|
||||
specify { expect(subject).not_to eq(JiraIssue.new('JIRA-124', project)) }
|
||||
|
||||
it 'only compares with JiraIssues' do
|
||||
expect(subject).not_to eq('JIRA-123')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -164,6 +164,17 @@ describe MergeRequest, models: true do
|
||||
|
||||
expect(subject.closes_issues).to include(issue2)
|
||||
end
|
||||
|
||||
context 'for a project with JIRA integration' do
|
||||
let(:issue0) { JiraIssue.new('JIRA-123', subject.project) }
|
||||
let(:issue1) { JiraIssue.new('FOOBAR-4567', subject.project) }
|
||||
|
||||
it 'returns sorted JiraIssues' do
|
||||
allow(subject.project).to receive_messages(default_branch: subject.target_branch)
|
||||
|
||||
expect(subject.closes_issues).to eq([issue0, issue1])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#work_in_progress?" do
|
||||
|
||||
@@ -26,6 +26,113 @@ describe JiraService, models: true do
|
||||
it { is_expected.to have_one :service_hook }
|
||||
end
|
||||
|
||||
describe "Execute" do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project) }
|
||||
let(:merge_request) { create(:merge_request) }
|
||||
|
||||
before do
|
||||
@jira_service = JiraService.new
|
||||
allow(@jira_service).to receive_messages(
|
||||
project_id: project.id,
|
||||
project: project,
|
||||
service_hook: true,
|
||||
project_url: 'http://jira.example.com',
|
||||
username: 'gitlab_jira_username',
|
||||
password: 'gitlab_jira_password'
|
||||
)
|
||||
@jira_service.save # will build API URL, as api_url was not specified above
|
||||
@sample_data = Gitlab::PushDataBuilder.build_sample(project, user)
|
||||
# https://github.com/bblimke/webmock#request-with-basic-authentication
|
||||
@api_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/transitions'
|
||||
@comment_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/comment'
|
||||
|
||||
WebMock.stub_request(:post, @api_url)
|
||||
WebMock.stub_request(:post, @comment_url)
|
||||
end
|
||||
|
||||
it "should call JIRA API" do
|
||||
@jira_service.execute(merge_request, JiraIssue.new("JIRA-123", project))
|
||||
expect(WebMock).to have_requested(:post, @comment_url).with(
|
||||
body: /Issue solved with/
|
||||
).once
|
||||
end
|
||||
|
||||
it "calls the api with jira_issue_transition_id" do
|
||||
@jira_service.jira_issue_transition_id = 'this-is-a-custom-id'
|
||||
@jira_service.execute(merge_request, JiraIssue.new("JIRA-123", project))
|
||||
expect(WebMock).to have_requested(:post, @api_url).with(
|
||||
body: /this-is-a-custom-id/
|
||||
).once
|
||||
end
|
||||
end
|
||||
|
||||
describe "Stored password invalidation" do
|
||||
let(:project) { create(:project) }
|
||||
|
||||
context "when a password was previously set" do
|
||||
before do
|
||||
@jira_service = JiraService.create(
|
||||
project: create(:project),
|
||||
properties: {
|
||||
api_url: 'http://jira.example.com/rest/api/2',
|
||||
username: 'mic',
|
||||
password: "password"
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it "reset password if url changed" do
|
||||
@jira_service.api_url = 'http://jira_edited.example.com/rest/api/2'
|
||||
@jira_service.save
|
||||
expect(@jira_service.password).to be_nil
|
||||
end
|
||||
|
||||
it "does not reset password if username changed" do
|
||||
@jira_service.username = "some_name"
|
||||
@jira_service.save
|
||||
expect(@jira_service.password).to eq("password")
|
||||
end
|
||||
|
||||
it "does not reset password if new url is set together with password, even if it's the same password" do
|
||||
@jira_service.api_url = 'http://jira_edited.example.com/rest/api/2'
|
||||
@jira_service.password = 'password'
|
||||
@jira_service.save
|
||||
expect(@jira_service.password).to eq("password")
|
||||
expect(@jira_service.api_url).to eq("http://jira_edited.example.com/rest/api/2")
|
||||
end
|
||||
|
||||
it "should reset password if url changed, even if setter called multiple times" do
|
||||
@jira_service.api_url = 'http://jira1.example.com/rest/api/2'
|
||||
@jira_service.api_url = 'http://jira1.example.com/rest/api/2'
|
||||
@jira_service.save
|
||||
expect(@jira_service.password).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when no password was previously set" do
|
||||
before do
|
||||
@jira_service = JiraService.create(
|
||||
project: create(:project),
|
||||
properties: {
|
||||
api_url: 'http://jira.example.com/rest/api/2',
|
||||
username: 'mic'
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it "saves password if new url is set together with password" do
|
||||
@jira_service.api_url = 'http://jira_edited.example.com/rest/api/2'
|
||||
@jira_service.password = 'password'
|
||||
@jira_service.save
|
||||
expect(@jira_service.password).to eq("password")
|
||||
expect(@jira_service.api_url).to eq("http://jira_edited.example.com/rest/api/2")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "Validations" do
|
||||
context "active" do
|
||||
before do
|
||||
@@ -78,11 +185,12 @@ describe JiraService, models: true do
|
||||
|
||||
context 'when gitlab.yml was initialized' do
|
||||
before do
|
||||
settings = { "jira" => {
|
||||
"title" => "Jira",
|
||||
"project_url" => "http://jira.sample/projects/project_a",
|
||||
"issues_url" => "http://jira.sample/issues/:id",
|
||||
"new_issue_url" => "http://jira.sample/projects/project_a/issues/new"
|
||||
settings = {
|
||||
"jira" => {
|
||||
"title" => "Jira",
|
||||
"project_url" => "http://jira.sample/projects/project_a",
|
||||
"issues_url" => "http://jira.sample/issues/:id",
|
||||
"new_issue_url" => "http://jira.sample/projects/project_a/issues/new"
|
||||
}
|
||||
}
|
||||
allow(Gitlab.config).to receive(:issues_tracker).and_return(settings)
|
||||
|
||||
@@ -265,6 +265,75 @@ describe GitPushService, services: true do
|
||||
expect(Issue.find(issue.id)).to be_opened
|
||||
end
|
||||
end
|
||||
|
||||
# EE-only tests
|
||||
context "for jira issue tracker" do
|
||||
include JiraServiceHelper
|
||||
|
||||
let(:jira_tracker) { project.create_jira_service if project.jira_service.nil? }
|
||||
|
||||
before do
|
||||
jira_service_settings
|
||||
|
||||
WebMock.stub_request(:post, jira_api_transition_url)
|
||||
WebMock.stub_request(:post, jira_api_comment_url)
|
||||
WebMock.stub_request(:get, jira_api_comment_url).to_return(body: jira_issue_comments)
|
||||
WebMock.stub_request(:get, jira_api_test_url)
|
||||
|
||||
allow(closing_commit).to receive_messages({
|
||||
issue_closing_regex: Regexp.new(Gitlab.config.gitlab.issue_closing_pattern),
|
||||
safe_message: message,
|
||||
author_name: commit_author.name,
|
||||
author_email: commit_author.email
|
||||
})
|
||||
|
||||
allow(project.repository).to receive_messages(commits_between: [closing_commit])
|
||||
end
|
||||
|
||||
after do
|
||||
jira_tracker.destroy!
|
||||
end
|
||||
|
||||
context "mentioning an issue" do
|
||||
let(:message) { "this is some work.\n\nrelated to JIRA-1" }
|
||||
|
||||
it "should initiate one api call to jira server to mention the issue" do
|
||||
service.execute(project, user, @oldrev, @newrev, @ref)
|
||||
|
||||
expect(WebMock).to have_requested(:post, jira_api_comment_url).with(
|
||||
body: /mentioned this issue in/
|
||||
).once
|
||||
end
|
||||
end
|
||||
|
||||
context "closing an issue" do
|
||||
let(:message) { "this is some work.\n\ncloses JIRA-1" }
|
||||
|
||||
it "should initiate one api call to jira server to close the issue" do
|
||||
transition_body = {
|
||||
transition: {
|
||||
id: '2'
|
||||
}
|
||||
}.to_json
|
||||
|
||||
service.execute(project, user, @oldrev, @newrev, @ref)
|
||||
expect(WebMock).to have_requested(:post, jira_api_transition_url).with(
|
||||
body: transition_body
|
||||
).once
|
||||
end
|
||||
|
||||
it "should initiate one api call to jira server to comment on the issue" do
|
||||
comment_body = {
|
||||
body: "Issue solved with [#{closing_commit.id}|http://localhost/#{project.path_with_namespace}/commit/#{closing_commit.id}]."
|
||||
}.to_json
|
||||
|
||||
service.execute(project, user, @oldrev, @newrev, @ref)
|
||||
expect(WebMock).to have_requested(:post, jira_api_comment_url).with(
|
||||
body: comment_body
|
||||
).once
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "empty project" do
|
||||
|
||||
@@ -425,4 +425,65 @@ describe SystemNoteService, services: true do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
include JiraServiceHelper
|
||||
|
||||
describe 'JIRA integration' do
|
||||
let(:project) { create(:project) }
|
||||
let(:author) { create(:user) }
|
||||
let(:issue) { create(:issue, project: project) }
|
||||
let(:mergereq) { create(:merge_request, :simple, target_project: project, source_project: project) }
|
||||
let(:jira_issue) { JiraIssue.new("JIRA-1", project)}
|
||||
let(:jira_tracker) { project.create_jira_service if project.jira_service.nil? }
|
||||
let(:commit) { project.commit }
|
||||
|
||||
context 'in JIRA issue tracker' do
|
||||
before do
|
||||
jira_service_settings
|
||||
WebMock.stub_request(:post, jira_api_comment_url)
|
||||
end
|
||||
|
||||
after do
|
||||
jira_tracker.destroy!
|
||||
end
|
||||
|
||||
describe "new reference" do
|
||||
before do
|
||||
WebMock.stub_request(:get, jira_api_comment_url).to_return(body: jira_issue_comments)
|
||||
end
|
||||
|
||||
subject { described_class.cross_reference(jira_issue, commit, author) }
|
||||
|
||||
it { is_expected.to eq(jira_status_message) }
|
||||
end
|
||||
|
||||
describe "existing reference" do
|
||||
before do
|
||||
message = "[#{author.name}|http://localhost/u/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]."
|
||||
WebMock.stub_request(:get, jira_api_comment_url).to_return(body: "{\"comments\":[{\"body\":\"#{message}\"}]}")
|
||||
end
|
||||
|
||||
subject { described_class.cross_reference(jira_issue, commit, author) }
|
||||
it { is_expected.not_to eq(jira_status_message) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'issue from an issue' do
|
||||
context 'in JIRA issue tracker' do
|
||||
before do
|
||||
jira_service_settings
|
||||
WebMock.stub_request(:post, jira_api_comment_url)
|
||||
WebMock.stub_request(:get, jira_api_comment_url).to_return(body: jira_issue_comments)
|
||||
end
|
||||
|
||||
after do
|
||||
jira_tracker.destroy!
|
||||
end
|
||||
|
||||
subject { described_class.cross_reference(jira_issue, issue, author) }
|
||||
|
||||
it { is_expected.to eq(jira_status_message) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
module JiraServiceHelper
|
||||
|
||||
def jira_service_settings
|
||||
properties = {
|
||||
"title"=>"JIRA tracker",
|
||||
"project_url"=>"http://jira.example/issues/?jql=project=A",
|
||||
"issues_url"=>"http://jira.example/browse/JIRA-1",
|
||||
"new_issue_url"=>"http://jira.example/secure/CreateIssue.jspa",
|
||||
"api_url"=>"http://jira.example/rest/api/2"
|
||||
}
|
||||
|
||||
jira_tracker.update_attributes(properties: properties, active: true)
|
||||
end
|
||||
|
||||
def jira_status_message
|
||||
"JiraService SUCCESS 200: Successfully posted to #{jira_api_comment_url}."
|
||||
end
|
||||
|
||||
def jira_issue_comments
|
||||
"{\"startAt\":0,\"maxResults\":11,\"total\":11,
|
||||
\"comments\":[{\"self\":\"http://0.0.0.0:4567/rest/api/2/issue/10002/comment/10609\",
|
||||
\"id\":\"10609\",\"author\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",
|
||||
\"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\",
|
||||
\"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\",
|
||||
\"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\",
|
||||
\"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\",
|
||||
\"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},
|
||||
\"displayName\":\"GitLab\",\"active\":true},
|
||||
\"body\":\"[Administrator|http://localhost:3000/u/root] mentioned JIRA-1 in Merge request of [gitlab-org/gitlab-test|http://localhost:3000/gitlab-org/gitlab-test/merge_requests/2].\",
|
||||
\"updateAuthor\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",\"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\",
|
||||
\"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\",
|
||||
\"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\",
|
||||
\"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\",
|
||||
\"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},\"displayName\":\"GitLab\",\"active\":true},
|
||||
\"created\":\"2015-02-12T22:47:07.826+0100\",
|
||||
\"updated\":\"2015-02-12T22:47:07.826+0100\"},
|
||||
{\"self\":\"http://0.0.0.0:4567/rest/api/2/issue/10002/comment/10700\",
|
||||
\"id\":\"10700\",\"author\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",
|
||||
\"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\",
|
||||
\"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\",
|
||||
\"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\",
|
||||
\"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\",
|
||||
\"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},\"displayName\":\"GitLab\",\"active\":true},
|
||||
\"body\":\"[Administrator|http://localhost:3000/u/root] mentioned this issue in [a commit of h5bp/html5-boilerplate|http://localhost:3000/h5bp/html5-boilerplate/commit/2439f77897122fbeee3bfd9bb692d3608848433e].\",
|
||||
\"updateAuthor\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",\"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\",
|
||||
\"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\",
|
||||
\"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\",
|
||||
\"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\",
|
||||
\"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},\"displayName\":\"GitLab\",\"active\":true},
|
||||
\"created\":\"2015-04-01T03:45:55.667+0200\",
|
||||
\"updated\":\"2015-04-01T03:45:55.667+0200\"
|
||||
}
|
||||
]}"
|
||||
end
|
||||
|
||||
def jira_api_comment_url
|
||||
'http://jira.example/rest/api/2/issue/JIRA-1/comment'
|
||||
end
|
||||
|
||||
def jira_api_transition_url
|
||||
'http://jira.example/rest/api/2/issue/JIRA-1/transitions'
|
||||
end
|
||||
|
||||
def jira_api_test_url
|
||||
'http://jira.example/rest/api/2/myself'
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user