gitlab-org--gitlab-foss/app/models/project_services/issue_tracker_service.rb

185 lines
5.0 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
2015-01-21 00:46:27 +00:00
class IssueTrackerService < Service
validate :one_issue_tracker, if: :activated?, on: :manual_change
# TODO: we can probably just delegate as part of
# https://gitlab.com/gitlab-org/gitlab/issues/29404
data_field :project_url, :issues_url, :new_issue_url
Greatly improve external_issue_tracker performance This greatly improves the performance of Project#external_issue_tracker by moving most of the fields queried in Ruby to the database and letting the database handle all logic. Prior to this change the process of finding an external issue tracker was along the lines of the following: 1. Load all project services into memory. 2. Reduce the list to only services where "issue_tracker?" returns true 3. Reduce the list from step 2 to service where "default?" returns false 4. Find the first service where "activated?" returns true This has to two big problems: 1. Loading all services into memory only to reduce the list down to a single item later on is a waste of memory (and slow timing wise). 2. Calling Array#select followed by Array#reject followed by Array#find allocates extra objects when this really isn't needed. To work around this the following service fields have been moved to the database (instead of being hardcoded): * category * default This in turn means we can get the external issue tracker using the following query: SELECT * FROM services WHERE active IS TRUE AND default IS FALSE AND category = 'issue_tracker' AND project_id = XXX LIMIT 1 This coupled with memoizing the result (just as before this commit) greatly reduces the time it takes for Project#external_issue_tracker to complete. The exact reduction depends on one's environment, but locally the execution time is reduced from roughly 230 ms to only 2 ms (= a reduction of almost 180x). Fixes gitlab-org/gitlab-ce#10771
2016-01-19 12:48:07 +00:00
default_value_for :category, 'issue_tracker'
before_validation :handle_properties
before_validation :set_default_data, on: :create
# Pattern used to extract links from comments
# Override this method on services that uses different patterns
# This pattern does not support cross-project references
# The other code assumes that this pattern is a superset of all
# overridden patterns. See ReferenceRegexes.external_pattern
def self.reference_pattern(only_long: false)
if only_long
/(\b[A-Z][A-Z0-9_]*-)#{Gitlab::Regex.issue}/
else
/(\b[A-Z][A-Z0-9_]*-|#{Issue.reference_prefix})#{Gitlab::Regex.issue}/
end
end
# this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
def title
if title_attribute = read_attribute(:title)
title_attribute
elsif self.properties && self.properties['title'].present?
self.properties['title']
else
default_title
end
end
# this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
def description
if description_attribute = read_attribute(:description)
description_attribute
elsif self.properties && self.properties['description'].present?
self.properties['description']
else
default_description
end
end
def handle_properties
# this has been moved from initialize_properties and should be improved
# as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
return unless properties
@legacy_properties_data = properties.dup
data_values = properties.slice!('title', 'description')
properties.each do |key, _|
current_value = self.properties.delete(key)
value = attribute_changed?(key) ? attribute_change(key).last : current_value
write_attribute(key, value)
end
data_values.reject! { |key| data_fields.changed.include?(key) }
data_values.slice!(*data_fields.attributes.keys)
data_fields.assign_attributes(data_values) if data_values.present?
self.properties = {}
end
def legacy_properties_data
@legacy_properties_data ||= {}
end
def supports_data_fields?
true
end
def data_fields
issue_tracker_data || self.build_issue_tracker_data
end
def default?
Greatly improve external_issue_tracker performance This greatly improves the performance of Project#external_issue_tracker by moving most of the fields queried in Ruby to the database and letting the database handle all logic. Prior to this change the process of finding an external issue tracker was along the lines of the following: 1. Load all project services into memory. 2. Reduce the list to only services where "issue_tracker?" returns true 3. Reduce the list from step 2 to service where "default?" returns false 4. Find the first service where "activated?" returns true This has to two big problems: 1. Loading all services into memory only to reduce the list down to a single item later on is a waste of memory (and slow timing wise). 2. Calling Array#select followed by Array#reject followed by Array#find allocates extra objects when this really isn't needed. To work around this the following service fields have been moved to the database (instead of being hardcoded): * category * default This in turn means we can get the external issue tracker using the following query: SELECT * FROM services WHERE active IS TRUE AND default IS FALSE AND category = 'issue_tracker' AND project_id = XXX LIMIT 1 This coupled with memoizing the result (just as before this commit) greatly reduces the time it takes for Project#external_issue_tracker to complete. The exact reduction depends on one's environment, but locally the execution time is reduced from roughly 230 ms to only 2 ms (= a reduction of almost 180x). Fixes gitlab-org/gitlab-ce#10771
2016-01-19 12:48:07 +00:00
default
end
2015-01-28 21:19:32 +00:00
def issue_url(iid)
issues_url.gsub(':id', iid.to_s)
2015-01-28 21:19:32 +00:00
end
2017-07-17 18:56:35 +00:00
def issue_tracker_path
project_url
end
def new_issue_path
new_issue_url
end
def issue_path(iid)
issue_url(iid)
end
2015-01-27 00:24:11 +00:00
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 },
{ type: 'text', name: 'new_issue_url', placeholder: 'New Issue url', required: true }
2015-01-27 00:24:11 +00:00
]
end
def initialize_properties
{}
end
# Initialize with default properties values
def set_default_data
return unless issues_tracker.present?
self.title ||= issues_tracker['title']
# we don't want to override if we have set something
return if project_url || issues_url || new_issue_url
data_fields.project_url = issues_tracker['project_url']
data_fields.issues_url = issues_tracker['issues_url']
data_fields.new_issue_url = issues_tracker['new_issue_url']
2015-01-27 00:24:11 +00:00
end
def self.supported_events
%w(push)
end
def execute(data)
return unless supported_events.include?(data[:object_kind])
message = "#{self.type} was unable to reach #{self.project_url}. Check the url and try again."
result = false
begin
response = Gitlab::HTTP.head(self.project_url, verify: true)
if response
message = "#{self.type} received response #{response.code} when attempting to connect to #{self.project_url}"
result = true
end
rescue Gitlab::HTTP::Error, Timeout::Error, SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, OpenSSL::SSL::SSLError => error
message = "#{self.type} had an error when trying to connect to #{self.project_url}: #{error.message}"
end
log_info(message)
result
end
def support_close_issue?
false
end
def support_cross_reference?
false
end
2015-01-27 00:24:11 +00:00
private
def enabled_in_gitlab_config
Gitlab.config.issues_tracker &&
Gitlab.config.issues_tracker.values.any? &&
issues_tracker
2015-01-27 00:24:11 +00:00
end
def issues_tracker
Gitlab.config.issues_tracker[to_param]
end
def one_issue_tracker
return if template? || instance?
return if project.blank?
if project.services.external_issue_trackers.where.not(id: id).any?
errors.add(:base, _('Another issue tracker is already in use. Only one issue tracker service can be active at a time'))
end
end
2015-01-21 00:46:27 +00:00
end
IssueTrackerService.prepend_if_ee('EE::IssueTrackerService')