diff options
Diffstat (limited to 'app/models')
| -rw-r--r-- | app/models/application_setting.rb | 16 | ||||
| -rw-r--r-- | app/models/ci/build.rb | 13 | ||||
| -rw-r--r-- | app/models/ci/commit.rb | 10 | ||||
| -rw-r--r-- | app/models/concerns/mentionable.rb | 18 | ||||
| -rw-r--r-- | app/models/concerns/participable.rb | 31 | ||||
| -rw-r--r-- | app/models/concerns/token_authenticatable.rb | 24 | ||||
| -rw-r--r-- | app/models/issue.rb | 10 | ||||
| -rw-r--r-- | app/models/jira_issue.rb | 2 | ||||
| -rw-r--r-- | app/models/merge_request.rb | 6 | ||||
| -rw-r--r-- | app/models/namespace.rb | 4 | ||||
| -rw-r--r-- | app/models/note.rb | 4 | ||||
| -rw-r--r-- | app/models/project.rb | 22 | ||||
| -rw-r--r-- | app/models/project_services/bamboo_service.rb | 6 | ||||
| -rw-r--r-- | app/models/project_services/flowdock_service.rb | 2 | ||||
| -rw-r--r-- | app/models/project_services/gemnasium_service.rb | 2 | ||||
| -rw-r--r-- | app/models/project_services/gitlab_ci_service.rb | 7 | ||||
| -rw-r--r-- | app/models/project_services/jira_service.rb | 241 | ||||
| -rw-r--r-- | app/models/project_services/teamcity_service.rb | 8 | ||||
| -rw-r--r-- | app/models/user.rb | 7 |
19 files changed, 349 insertions, 84 deletions
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index faa0bdf840b..724429e7558 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -126,12 +126,16 @@ class ApplicationSetting < ActiveRecord::Base def restricted_signup_domains_raw=(values) self.restricted_signup_domains = [] self.restricted_signup_domains = values.split( - /\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace - | # or - \s # any whitespace character - | # or - [\r\n] # any number of newline characters - /x) + /\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace + | # or + \s # any whitespace character + | # or + [\r\n] # any number of newline characters + /x) self.restricted_signup_domains.reject! { |d| d.empty? } end + + def runners_registration_token + ensure_runners_registration_token! + end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 6d9cdb95295..7b89fe069ea 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -135,6 +135,16 @@ module Ci predefined_variables + yaml_variables + project_variables + trigger_variables end + def merge_request + merge_requests = MergeRequest.includes(:merge_request_diff) + .where(source_branch: ref, source_project_id: commit.gl_project_id) + .reorder(iid: :asc) + + merge_requests.find do |merge_request| + merge_request.commits.any? { |ci| ci.id == commit.sha } + end + end + def project commit.project end @@ -170,7 +180,8 @@ module Ci def extract_coverage(text, regex) begin - matches = text.gsub(Regexp.new(regex)).to_a.last + matches = text.scan(Regexp.new(regex)).last + matches = matches.last if matches.kind_of?(Array) coverage = matches.gsub(/\d+(\.\d+)?/).first if coverage.present? diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 6bf596e5d3e..d2a29236942 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -218,16 +218,6 @@ module Ci update!(committed_at: DateTime.now) end - ## - # This method checks if build status should be displayed. - # - # Build status should be available only if builds are enabled - # on project level and `.gitlab-ci.yml` file is present. - # - def show_build_status? - project.builds_enabled? && ci_yaml_file - end - private def save_yaml_error(error) diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index d2ea9ab7313..1fdcda97520 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -23,7 +23,7 @@ module Mentionable included do if self < Participable - participant ->(current_user) { mentioned_users(current_user, load_lazy_references: false) } + participant ->(current_user) { mentioned_users(current_user) } end end @@ -43,15 +43,15 @@ module Mentionable self end - def all_references(current_user = self.author, text = nil, load_lazy_references: true) - ext = Gitlab::ReferenceExtractor.new(self.project, current_user, load_lazy_references: load_lazy_references) - + def all_references(current_user = self.author, text = nil) + ext = Gitlab::ReferenceExtractor.new(self.project, current_user) + if text ext.analyze(text) else self.class.mentionable_attrs.each do |attr, options| text = send(attr) - options[:cache_key] = [self, attr] if options.delete(:cache) + options[:cache_key] = [self, attr] if options.delete(:cache) && self.persisted? ext.analyze(text, options) end end @@ -59,13 +59,13 @@ module Mentionable ext end - def mentioned_users(current_user = nil, load_lazy_references: true) - all_references(current_user, load_lazy_references: load_lazy_references).users + def mentioned_users(current_user = nil) + all_references(current_user).users end # Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference. - def referenced_mentionables(current_user = self.author, text = nil, load_lazy_references: true) - refs = all_references(current_user, text, load_lazy_references: load_lazy_references) + def referenced_mentionables(current_user = self.author, text = nil) + refs = all_references(current_user, text) refs = (refs.issues + refs.merge_requests + refs.commits) # We're using this method instead of Array diffing because that requires diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb index 85367f89f4f..fc6f83b918b 100644 --- a/app/models/concerns/participable.rb +++ b/app/models/concerns/participable.rb @@ -37,21 +37,22 @@ module Participable # Be aware that this method makes a lot of sql queries. # Save result into variable if you are going to reuse it inside same request - def participants(current_user = self.author, load_lazy_references: true) - participants = self.class.participant_attrs.flat_map do |attr| - value = - if attr.respond_to?(:call) - instance_exec(current_user, &attr) - else - send(attr) - end + def participants(current_user = self.author) + participants = + Gitlab::ReferenceExtractor.lazily do + self.class.participant_attrs.flat_map do |attr| + value = + if attr.respond_to?(:call) + instance_exec(current_user, &attr) + else + send(attr) + end - participants_for(value, current_user) - end.compact.uniq - - if load_lazy_references - participants = Gitlab::Markdown::ReferenceFilter::LazyReference.load(participants).uniq + participants_for(value, current_user) + end.compact.uniq + end + unless Gitlab::ReferenceExtractor.lazy? participants.select! do |user| user.can?(:read_project, project) end @@ -64,12 +65,12 @@ module Participable def participants_for(value, current_user = nil) case value - when User, Gitlab::Markdown::ReferenceFilter::LazyReference + when User, Banzai::LazyReference [value] when Enumerable, ActiveRecord::Relation value.flat_map { |v| participants_for(v, current_user) } when Participable - value.participants(current_user, load_lazy_references: false) + value.participants(current_user) end end end diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb index 56d38fe8250..885deaf78d2 100644 --- a/app/models/concerns/token_authenticatable.rb +++ b/app/models/concerns/token_authenticatable.rb @@ -13,20 +13,21 @@ module TokenAuthenticatable @token_fields << token_field define_singleton_method("find_by_#{token_field}") do |token| - where(token_field => token).first if token + find_by(token_field => token) if token end define_method("ensure_#{token_field}") do current_token = read_attribute(token_field) - if current_token.blank? - write_attribute(token_field, generate_token_for(token_field)) - else - current_token - end + current_token.blank? ? write_new_token(token_field) : current_token + end + + define_method("ensure_#{token_field}!") do + send("reset_#{token_field}!") if read_attribute(token_field).blank? + read_attribute(token_field) end define_method("reset_#{token_field}!") do - write_attribute(token_field, generate_token_for(token_field)) + write_new_token(token_field) save! end end @@ -34,10 +35,15 @@ module TokenAuthenticatable private - def generate_token_for(token_field) + def write_new_token(token_field) + new_token = generate_token(token_field) + write_attribute(token_field, new_token) + end + + def generate_token(token_field) loop do token = Devise.friendly_token - break token unless self.class.unscoped.where(token_field => token).first + break token unless self.class.unscoped.find_by(token_field => token) end end end diff --git a/app/models/issue.rb b/app/models/issue.rb index e04035b3af8..80ecd15077f 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -84,11 +84,11 @@ class Issue < ActiveRecord::Base end def referenced_merge_requests - references = [self, *notes].flat_map do |note| - note.all_references(load_lazy_references: false).merge_requests - end.uniq - - Gitlab::Markdown::ReferenceFilter::LazyReference.load(references).uniq.sort_by(&:iid) + Gitlab::ReferenceExtractor.lazily do + [self, *notes].flat_map do |note| + note.all_references.merge_requests + end + end.sort_by(&:iid) end # Reset issue events cache diff --git a/app/models/jira_issue.rb b/app/models/jira_issue.rb new file mode 100644 index 00000000000..5b21aac5e43 --- /dev/null +++ b/app/models/jira_issue.rb @@ -0,0 +1,2 @@ +class JiraIssue < ExternalIssue +end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index f6f77a16267..ac25d38eb63 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -194,9 +194,7 @@ class MergeRequest < ActiveRecord::Base similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id if similar_mrs.any? errors.add :validate_branches, - "Cannot Create: This merge request already exists: #{ - similar_mrs.pluck(:title) - }" + "Cannot Create: This merge request already exists: #{similar_mrs.pluck(:title)}" end end end @@ -337,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 diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 1c4e101cc10..adafabbec07 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -45,7 +45,7 @@ class Namespace < ActiveRecord::Base class << self def by_path(path) - where('lower(path) = :value', value: path.downcase).first + find_by('lower(path) = :value', value: path.downcase) end # Case insensetive search for namespace by path or name @@ -148,6 +148,6 @@ class Namespace < ActiveRecord::Base end def find_fork_of(project) - projects.joins(:forked_project_link).where('forked_project_links.forked_from_project_id = ?', project.id).first + projects.joins(:forked_project_link).find_by('forked_project_links.forked_from_project_id = ?', project.id) end end diff --git a/app/models/note.rb b/app/models/note.rb index 04053ccc61e..8c5b5836f9a 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -373,11 +373,11 @@ class Note < ActiveRecord::Base end def contains_emoji_only? - note =~ /\A#{Gitlab::Markdown::EmojiFilter.emoji_pattern}\s?\Z/ + note =~ /\A#{Banzai::Filter::EmojiFilter.emoji_pattern}\s?\Z/ end def award_emoji_name - original_name = note.match(Gitlab::Markdown::EmojiFilter.emoji_pattern)[1] + original_name = note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1] AwardEmoji.normilize_emoji_name(original_name) end end diff --git a/app/models/project.rb b/app/models/project.rb index e1f7bf971e3..b28a7ca429c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -265,7 +265,7 @@ class Project < ActiveRecord::Base joins(:namespace). iwhere('namespaces.path' => namespace_path) - projects.where('projects.path' => project_path).take || + projects.find_by('projects.path' => project_path) || projects.iwhere('projects.path' => project_path).take end @@ -450,7 +450,7 @@ class Project < ActiveRecord::Base end def external_issue_tracker - @external_issues_tracker ||= external_issues_trackers.select(&:activated?).first + @external_issues_tracker ||= external_issues_trackers.find(&:activated?) end def can_have_issues_tracker_id? @@ -496,7 +496,11 @@ class Project < ActiveRecord::Base end def ci_service - @ci_service ||= ci_services.select(&:activated?).first + @ci_service ||= ci_services.find(&:activated?) + end + + def jira_tracker? + issues_tracker.to_param == 'jira' end def avatar_type @@ -547,7 +551,7 @@ class Project < ActiveRecord::Base end def project_member_by_name_or_email(name = nil, email = nil) - user = users.where('name like ? or email like ?', name, email).first + user = users.find_by('name like ? or email like ?', name, email) project_members.where(user: user) if user end @@ -722,7 +726,7 @@ class Project < ActiveRecord::Base end def project_member(user) - project_members.where(user_id: user).first + project_members.find_by(user_id: user) end def default_branch @@ -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 @@ -850,4 +858,8 @@ class Project < ActiveRecord::Base def build_timeout_in_minutes=(value) self.build_timeout = value.to_i * 60 end + + def open_issues_count + issues.opened.count + end end diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb index 0a61ad96a0e..aa8746beb80 100644 --- a/app/models/project_services/bamboo_service.rb +++ b/app/models/project_services/bamboo_service.rb @@ -27,12 +27,10 @@ class BambooService < CiService validates :build_key, presence: true, if: :activated? validates :username, presence: true, - if: ->(service) { service.password? }, - if: :activated? + if: ->(service) { service.activated? && service.password } validates :password, presence: true, - if: ->(service) { service.username? }, - if: :activated? + if: ->(service) { service.activated? && service.username } attr_accessor :response diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb index 27fc19379f1..15c7c907f7e 100644 --- a/app/models/project_services/flowdock_service.rb +++ b/app/models/project_services/flowdock_service.rb @@ -58,6 +58,6 @@ class FlowdockService < Service repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}", commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s", diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s", - ) + ) end end diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb index 91ef267ad79..202fee042e3 100644 --- a/app/models/project_services/gemnasium_service.rb +++ b/app/models/project_services/gemnasium_service.rb @@ -57,6 +57,6 @@ class GemnasiumService < Service token: token, api_key: api_key, repo: project.repository.path_to_repo - ) + ) end end diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index d73182d40ac..b64d97ce75d 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -18,6 +18,11 @@ # note_events :boolean default(TRUE), not null # +# TODO(ayufan): The GitLabCiService is deprecated and the type should be removed when the database entries are removed class GitlabCiService < CiService - # this is no longer used + # We override the active accessor to always make GitLabCiService disabled + # Otherwise the GitLabCiService can be picked, but should never be since it's deprecated + def active + false + end end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 35e30b1cb0b..e216f406e1c 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -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 diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index 29d4236745a..a63700693d7 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -27,12 +27,10 @@ class TeamcityService < CiService validates :build_type, presence: true, if: :activated? validates :username, presence: true, - if: ->(service) { service.password? }, - if: :activated? + if: ->(service) { service.activated? && service.password } validates :password, presence: true, - if: ->(service) { service.username? }, - if: :activated? + if: ->(service) { service.activated? && service.username } attr_accessor :response @@ -147,6 +145,6 @@ class TeamcityService < CiService '</build>', headers: { 'Content-type' => 'application/xml' }, basic_auth: auth - ) + ) end end diff --git a/app/models/user.rb b/app/models/user.rb index fdd14f4571d..df87f3b79bd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -26,6 +26,7 @@ # bio :string(255) # failed_attempts :integer default(0) # locked_at :datetime +# unlock_token :string(255) # username :string(255) # can_create_group :boolean default(TRUE), not null # can_create_team :boolean default(TRUE), not null @@ -220,9 +221,9 @@ class User < ActiveRecord::Base def find_for_database_authentication(warden_conditions) conditions = warden_conditions.dup if login = conditions.delete(:login) - where(conditions).where(["lower(username) = :value OR lower(email) = :value", { value: login.downcase }]).first + where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase) else - where(conditions).first + find_by(conditions) end end @@ -285,7 +286,7 @@ class User < ActiveRecord::Base end def by_username_or_id(name_or_id) - where('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i).first + find_by('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i) end def build_user(attrs = {}) |
