Commit 241e2e87 authored by Martin Cabrera's avatar Martin Cabrera

Merge branch 'master' into i-#25814-500-error

parents c2283a2d 28f633a9
...@@ -101,7 +101,7 @@ gem 'seed-fu', '~> 2.3.5' ...@@ -101,7 +101,7 @@ gem 'seed-fu', '~> 2.3.5'
# Markdown and HTML processing # Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0' gem 'html-pipeline', '~> 1.11.0'
gem 'deckar01-task_list', '1.0.6', require: 'task_list/railtie' gem 'deckar01-task_list', '1.0.6', require: 'task_list/railtie'
gem 'gitlab-markup', '~> 1.5.0' gem 'gitlab-markup', '~> 1.5.1'
gem 'redcarpet', '~> 3.3.3' gem 'redcarpet', '~> 3.3.3'
gem 'RedCloth', '~> 4.3.2' gem 'RedCloth', '~> 4.3.2'
gem 'rdoc', '~> 4.2' gem 'rdoc', '~> 4.2'
......
...@@ -73,12 +73,12 @@ ...@@ -73,12 +73,12 @@
<table class="table ci-table"> <table class="table ci-table">
<thead> <thead>
<tr> <tr>
<th>Status</th> <th class="pipeline-status">Status</th>
<th>Pipeline</th> <th class="pipeline-info">Pipeline</th>
<th>Commit</th> <th class="pipeline-commit">Commit</th>
<th>Stages</th> <th class="pipeline-stages">Stages</th>
<th></th> <th class="pipeline-date"></th>
<th class="hidden-xs"></th> <th class="pipeline-actions hidden-xs"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
......
...@@ -109,6 +109,10 @@ ...@@ -109,6 +109,10 @@
.avatar { .avatar {
float: none; float: none;
} }
> a:not(:last-of-type) {
margin-right: 5px;
}
} }
} }
......
module ServiceParams module ServiceParams
extend ActiveSupport::Concern extend ActiveSupport::Concern
ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_url, :api_version, :subdomain, ALLOWED_PARAMS_CE = [
:room, :recipients, :project_url, :webhook, :active,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password, :add_pusher,
:build_key, :server, :teamcity_url, :drone_url, :build_type, :api_key,
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel, :api_url,
:colorize_messages, :channels, :api_version,
:bamboo_url,
:build_key,
:build_type,
:ca_pem,
:channel,
:channels,
:color,
:colorize_messages,
:confidential_issues_events,
:default_irc_uri,
:description,
:device,
:disable_diffs,
:drone_url,
:enable_ssl_verification,
:external_wiki_url,
# We're using `issues_events` and `merge_requests_events` # We're using `issues_events` and `merge_requests_events`
# in the view so we still need to explicitly state them # in the view so we still need to explicitly state them
# here. `Service#event_names` would only give # here. `Service#event_names` would only give
# `issue_events` and `merge_request_events` (singular!) # `issue_events` and `merge_request_events` (singular!)
# See app/helpers/services_helper.rb for how we # See app/helpers/services_helper.rb for how we
# make those event names plural as special case. # make those event names plural as special case.
:issues_events, :confidential_issues_events, :merge_requests_events, :issues_events,
:notify_only_broken_builds, :notify_only_broken_pipelines, :issues_url,
:add_pusher, :send_from_committer_email, :disable_diffs, :jira_issue_transition_id,
:external_wiki_url, :notify, :color, :merge_requests_events,
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification, :namespace,
:jira_issue_transition_id, :url, :project_key, :ca_pem, :namespace] :new_issue_url,
:notify,
:notify_only_broken_builds,
:notify_only_broken_pipelines,
:password,
:priority,
:project_key,
:project_url,
:recipients,
:restrict_to_branch,
:room,
:send_from_committer_email,
:server,
:server_host,
:server_port,
:sound,
:subdomain,
:teamcity_url,
:title,
:token,
:type,
:url,
:user_key,
:username,
:webhook
]
# Parameters to ignore if no value is specified # Parameters to ignore if no value is specified
FILTER_BLANK_PARAMS = [:password] FILTER_BLANK_PARAMS = [:password]
def service_params def service_params
dynamic_params = @service.event_channel_names + @service.event_names dynamic_params = @service.event_channel_names + @service.event_names
service_params = params.permit(:id, service: ALLOWED_PARAMS + dynamic_params) service_params = params.permit(:id, service: ALLOWED_PARAMS_CE + dynamic_params)
if service_params[:service].is_a?(Hash) if service_params[:service].is_a?(Hash)
FILTER_BLANK_PARAMS.each do |param| FILTER_BLANK_PARAMS.each do |param|
......
...@@ -125,7 +125,11 @@ class GroupsController < Groups::ApplicationController ...@@ -125,7 +125,11 @@ class GroupsController < Groups::ApplicationController
end end
def group_params def group_params
params.require(:group).permit( params.require(:group).permit(group_params_ce)
end
def group_params_ce
[
:avatar, :avatar,
:description, :description,
:lfs_enabled, :lfs_enabled,
...@@ -135,7 +139,7 @@ class GroupsController < Groups::ApplicationController ...@@ -135,7 +139,7 @@ class GroupsController < Groups::ApplicationController
:request_access_enabled, :request_access_enabled,
:share_with_group_lock, :share_with_group_lock,
:visibility_level :visibility_level
) ]
end end
def load_events def load_events
......
...@@ -409,10 +409,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -409,10 +409,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
else else
ci_service = @merge_request.source_project.try(:ci_service) ci_service = @merge_request.source_project.try(:ci_service)
status = ci_service.commit_status(merge_request.diff_head_sha, merge_request.source_branch) if ci_service status = ci_service.commit_status(merge_request.diff_head_sha, merge_request.source_branch) if ci_service
if ci_service.respond_to?(:commit_coverage)
coverage = ci_service.commit_coverage(merge_request.diff_head_sha, merge_request.source_branch)
end
end end
response = { response = {
......
...@@ -137,4 +137,10 @@ class CommitStatus < ActiveRecord::Base ...@@ -137,4 +137,10 @@ class CommitStatus < ActiveRecord::Base
.new(self, current_user) .new(self, current_user)
.fabricate! .fabricate!
end end
def sortable_name
name.split(/(\d+)/).map do |v|
v =~ /\d+/ ? v.to_i : v
end
end
end end
...@@ -32,6 +32,6 @@ module ProjectFeaturesCompatibility ...@@ -32,6 +32,6 @@ module ProjectFeaturesCompatibility
build_project_feature unless project_feature build_project_feature unless project_feature
access_level = Gitlab::Utils.to_boolean(value) ? ProjectFeature::ENABLED : ProjectFeature::DISABLED access_level = Gitlab::Utils.to_boolean(value) ? ProjectFeature::ENABLED : ProjectFeature::DISABLED
project_feature.update_attribute(field, access_level) project_feature.send(:write_attribute, field, access_level)
end end
end end
...@@ -55,30 +55,30 @@ module ReactiveCaching ...@@ -55,30 +55,30 @@ module ReactiveCaching
self.reactive_cache_refresh_interval = 1.minute self.reactive_cache_refresh_interval = 1.minute
self.reactive_cache_lifetime = 10.minutes self.reactive_cache_lifetime = 10.minutes
def calculate_reactive_cache def calculate_reactive_cache(*args)
raise NotImplementedError raise NotImplementedError
end end
def with_reactive_cache(&blk) def with_reactive_cache(*args, &blk)
within_reactive_cache_lifetime do within_reactive_cache_lifetime(*args) do
data = Rails.cache.read(full_reactive_cache_key) data = Rails.cache.read(full_reactive_cache_key(*args))
yield data if data.present? yield data if data.present?
end end
ensure ensure
Rails.cache.write(full_reactive_cache_key('alive'), true, expires_in: self.class.reactive_cache_lifetime) Rails.cache.write(alive_reactive_cache_key(*args), true, expires_in: self.class.reactive_cache_lifetime)
ReactiveCachingWorker.perform_async(self.class, id) ReactiveCachingWorker.perform_async(self.class, id, *args)
end end
def clear_reactive_cache! def clear_reactive_cache!(*args)
Rails.cache.delete(full_reactive_cache_key) Rails.cache.delete(full_reactive_cache_key(*args))
end end
def exclusively_update_reactive_cache! def exclusively_update_reactive_cache!(*args)
locking_reactive_cache do locking_reactive_cache(*args) do
within_reactive_cache_lifetime do within_reactive_cache_lifetime(*args) do
enqueuing_update do enqueuing_update(*args) do
value = calculate_reactive_cache value = calculate_reactive_cache(*args)
Rails.cache.write(full_reactive_cache_key, value) Rails.cache.write(full_reactive_cache_key(*args), value)
end end
end end
end end
...@@ -93,22 +93,26 @@ module ReactiveCaching ...@@ -93,22 +93,26 @@ module ReactiveCaching
([prefix].flatten + qualifiers).join(':') ([prefix].flatten + qualifiers).join(':')
end end
def locking_reactive_cache def alive_reactive_cache_key(*qualifiers)
lease = Gitlab::ExclusiveLease.new(full_reactive_cache_key, timeout: reactive_cache_lease_timeout) full_reactive_cache_key(*(qualifiers + ['alive']))
end
def locking_reactive_cache(*args)
lease = Gitlab::ExclusiveLease.new(full_reactive_cache_key(*args), timeout: reactive_cache_lease_timeout)
uuid = lease.try_obtain uuid = lease.try_obtain
yield if uuid yield if uuid
ensure ensure
Gitlab::ExclusiveLease.cancel(full_reactive_cache_key, uuid) Gitlab::ExclusiveLease.cancel(full_reactive_cache_key(*args), uuid)
end end
def within_reactive_cache_lifetime def within_reactive_cache_lifetime(*args)
yield if Rails.cache.read(full_reactive_cache_key('alive')) yield if Rails.cache.read(alive_reactive_cache_key(*args))
end end
def enqueuing_update def enqueuing_update(*args)
yield yield
ensure ensure
ReactiveCachingWorker.perform_in(self.class.reactive_cache_refresh_interval, self.class, id) ReactiveCachingWorker.perform_in(self.class.reactive_cache_refresh_interval, self.class, id, *args)
end end
end end
end end
module ReactiveService
extend ActiveSupport::Concern
included do
include ReactiveCaching
# Default cache key: class name + project_id
self.reactive_cache_key = ->(service) { [ service.class.model_name.singular, service.project_id ] }
end
end
class BambooService < CiService class BambooService < CiService
include ReactiveService
prop_accessor :bamboo_url, :build_key, :username, :password prop_accessor :bamboo_url, :build_key, :username, :password
validates :bamboo_url, presence: true, url: true, if: :activated? validates :bamboo_url, presence: true, url: true, if: :activated?
...@@ -58,31 +60,46 @@ class BambooService < CiService ...@@ -58,31 +60,46 @@ class BambooService < CiService
%w(push) %w(push)
end end
def build_info(sha) def build_page(sha, ref)
@response = get_path("rest/api/latest/result?label=#{sha}") with_reactive_cache(sha, ref) {|cached| cached[:build_page] }
end end
def build_page(sha, ref) def commit_status(sha, ref)
build_info(sha) if @response.nil? || !@response.code with_reactive_cache(sha, ref) {|cached| cached[:commit_status] }
end
if @response.code != 200 || @response['results']['results']['size'] == '0' def execute(data)
return unless supported_events.include?(data[:object_kind])
get_path("updateAndBuild.action?buildKey=#{build_key}")
end
def calculate_reactive_cache(sha, ref)
response = get_path("rest/api/latest/result?label=#{sha}")
{ build_page: read_build_page(response), commit_status: read_commit_status(response) }
end
private
def read_build_page(response)
if response.code != 200 || response['results']['results']['size'] == '0'
# If actual build link can't be determined, send user to build summary page. # If actual build link can't be determined, send user to build summary page.
URI.join("#{bamboo_url}/", "browse/#{build_key}").to_s URI.join("#{bamboo_url}/", "browse/#{build_key}").to_s
else else
# If actual build link is available, go to build result page. # If actual build link is available, go to build result page.
result_key = @response['results']['results']['result']['planResultKey']['key'] result_key = response['results']['results']['result']['planResultKey']['key']
URI.join("#{bamboo_url}/", "browse/#{result_key}").to_s URI.join("#{bamboo_url}/", "browse/#{result_key}").to_s
end end
end end
def commit_status(sha, ref) def read_commit_status(response)
build_info(sha) if @response.nil? || !@response.code return :error unless response.code == 200 || response.code == 404
return :error unless @response.code == 200 || @response.code == 404
status = if @response.code == 404 || @response['results']['results']['size'] == '0' status = if response.code == 404 || response['results']['results']['size'] == '0'
'Pending' 'Pending'
else else
@response['results']['results']['result']['buildState'] response['results']['results']['result']['buildState']
end end
if status.include?('Success') if status.include?('Success')
...@@ -96,14 +113,6 @@ class BambooService < CiService ...@@ -96,14 +113,6 @@ class BambooService < CiService
end end
end end
def execute(data)
return unless supported_events.include?(data[:object_kind])
get_path("updateAndBuild.action?buildKey=#{build_key}")
end
private
def build_url(path) def build_url(path)
URI.join("#{bamboo_url}/", path).to_s URI.join("#{bamboo_url}/", path).to_s
end end
......
require "addressable/uri" require "addressable/uri"
class BuildkiteService < CiService class BuildkiteService < CiService
include ReactiveService
ENDPOINT = "https://buildkite.com" ENDPOINT = "https://buildkite.com"
prop_accessor :project_url, :token prop_accessor :project_url, :token
...@@ -33,13 +35,7 @@ class BuildkiteService < CiService ...@@ -33,13 +35,7 @@ class BuildkiteService < CiService
end end
def commit_status(sha, ref) def commit_status(sha, ref)
response = HTTParty.get(commit_status_path(sha), verify: false) with_reactive_cache(sha, ref) {|cached| cached[:commit_status] }
if response.code == 200 && response['status']
response['status']
else
:error
end
end end
def commit_status_path(sha) def commit_status_path(sha)
...@@ -78,6 +74,19 @@ class BuildkiteService < CiService ...@@ -78,6 +74,19 @@ class BuildkiteService < CiService
] ]
end end
def calculate_reactive_cache(sha, ref)
response = HTTParty.get(commit_status_path(sha), verify: false)
status =
if response.code == 200 && response['status']
response['status']
else
:error
end
{ commit_status: status }
end
private private
def webhook_token def webhook_token
......
...@@ -12,15 +12,7 @@ class CiService < Service ...@@ -12,15 +12,7 @@ class CiService < Service
%w(push) %w(push)
end end
def merge_request_page(iid, sha, ref) # Return complete url to build page
commit_page(sha, ref)
end
def commit_page(sha, ref)
build_page(sha, ref)
end
# Return complete url to merge_request page
# #
# Ex. # Ex.
# http://jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c # http://jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c
...@@ -29,23 +21,6 @@ class CiService < Service ...@@ -29,23 +21,6 @@ class CiService < Service
# implement inside child # implement inside child
end end
# Return string with build status or :error symbol
#
# Allowed states: 'success', 'failed', 'running', 'pending', 'skipped'
#
#
# Ex.
# @service.merge_request_status(9, '13be4ac', 'dev')
# # => 'success'
#
# @service.merge_request_status(10, '2abe4ac', 'dev)
# # => 'running'
#
#
def merge_request_status(iid, sha, ref)
commit_status(sha, ref)
end
# Return string with build status or :error symbol # Return string with build status or :error symbol
# #
# Allowed states: 'success', 'failed', 'running', 'pending', 'skipped' # Allowed states: 'success', 'failed', 'running', 'pending', 'skipped'
......
class DroneCiService < CiService class DroneCiService < CiService
include ReactiveService
prop_accessor :drone_url, :token prop_accessor :drone_url, :token
boolean_accessor :enable_ssl_verification boolean_accessor :enable_ssl_verification
...@@ -34,14 +36,6 @@ class DroneCiService < CiService ...@@ -34,14 +36,6 @@ class DroneCiService < CiService
%w(push merge_request tag_push) %w(push merge_request tag_push)
end end
def merge_request_status_path(iid, sha = nil, ref = nil)
url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/pulls/#{iid}",
"?access_token=#{token}"]
URI.join(*url).to_s
end
def commit_status_path(sha, ref) def commit_status_path(sha, ref)
url = [drone_url, url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}", "gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}",
...@@ -50,29 +44,14 @@ class DroneCiService < CiService ...@@ -50,29 +44,14 @@ class DroneCiService < CiService
URI.join(*url).to_s URI.join(*url).to_s
end end
def merge_request_status(iid, sha, ref) def commit_status(sha, ref)
response = HTTParty.get(merge_request_status_path(iid), verify: enable_ssl_verification) with_reactive_cache(sha, ref) {|cached| cached[:commit_status] }
if response.code == 200 and response['status']
case response['status']
when 'killed'
:canceled
when 'failure', 'error'
# Because drone return error if some test env failed
:failed
else
response["status"]
end
else
:error
end
rescue Errno::ECONNREFUSED
:error
end end
def commit_status(sha, ref) def calculate_reactive_cache(sha, ref)
response = HTTParty.get(commit_status_path(sha, ref), verify: enable_ssl_verification) response = HTTParty.get(commit_status_path(sha, ref), verify: enable_ssl_verification)
status =
if response.code == 200 and response['status'] if response.code == 200 and response['status']
case response['status'] case response['status']
when 'killed' when 'killed'
...@@ -86,18 +65,13 @@ class DroneCiService < CiService ...@@ -86,18 +65,13 @@ class DroneCiService < CiService
else else
:error :error
end end
rescue Errno::ECONNREFUSED
:error
end
def merge_request_page(iid, sha, ref)
url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/redirect/pulls/#{iid}"]
URI.join(*url).to_s { commit_status: status }
rescue Errno::ECONNREFUSED
{ commit_status: :error }
end end
def commit_page(sha, ref) def build_page(sha, ref)
url = [drone_url, url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}", "gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}",
"?branch=#{URI::encode(ref.to_s)}"] "?branch=#{URI::encode(ref.to_s)}"]
...@@ -105,14 +79,6 @@ class DroneCiService < CiService ...@@ -105,14 +79,6 @@ class DroneCiService < CiService
URI.join(*url).to_s URI.join(*url).to_s
end end
def commit_coverage(sha, ref)
nil
end
def build_page(sha, ref)
commit_page(sha, ref)
end
def title def title
'Drone CI' 'Drone CI'
end end
......
class TeamcityService < CiService class TeamcityService < CiService
include ReactiveService
prop_accessor :teamcity_url, :build_type, :username, :password prop_accessor :teamcity_url, :build_type, :username, :password
validates :teamcity_url, presence: true, url: true, if: :activated? validates :teamcity_url, presence: true, url: true, if: :activated?
...@@ -61,43 +63,18 @@ class TeamcityService < CiService ...@@ -61,43 +63,18 @@ class TeamcityService < CiService
] ]
end end
def build_info(sha)
@response = get_path("httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}")
end
def build_page(sha, ref) def build_page(sha, ref)
build_info(sha) if @response.nil? || !@response.code with_reactive_cache(sha, ref) {|cached| cached[:build_page] }
if @response.code != 200
# If actual build link can't be determined,
# send user to build summary page.
build_url("viewLog.html?buildTypeId=#{build_type}")
else
# If actual build link is available, go to build result page.
built_id = @response['build']['id']
build_url("viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}")
end
end end
def commit_status(sha, ref) def commit_status(sha, ref)
build_info(sha) if @response.nil? || !@response.code with_reactive_cache(sha, ref) {|cached| cached[:commit_status] }
return :error unless @response.code == 200 || @response.code == 404
status = if @response.code == 404
'Pending'
else
@response['build']['status']
end end
if status.include?('SUCCESS') def calculate_reactive_cache(sha, ref)
'success' response = get_path("httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}")
elsif status.include?('FAILURE')
'failed' { build_page: read_build_page(response), commit_status: read_commit_status(response) }
elsif status.include?('Pending')
'pending'
else
:error
end
end end
def execute(data) def execute(data)
...@@ -122,6 +99,40 @@ class TeamcityService < CiService ...@@ -122,6 +99,40 @@ class TeamcityService < CiService
private private
def read_build_page(response)
if response.code != 200
# If actual build link can't be determined,
# send user to build summary page.
build_url("viewLog.html?buildTypeId=#{build_type}")
else
# If actual build link is available, go to build result page.
built_id = response['build']['id']
build_url("viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}")
end
end
def read_commit_status(response)
return :error unless response.code == 200 || response.code == 404
status = if response.code == 404
'Pending'
else
response['build']['status']
end
return :error unless status.present?
if status.include?('SUCCESS')
'success'
elsif status.include?('FAILURE')
'failed'
elsif status.include?('Pending')
'pending'
else
:error
end
end
def build_url(path) def build_url(path)
URI.join("#{teamcity_url}/", path).to_s URI.join("#{teamcity_url}/", path).to_s
end end
......
...@@ -8,16 +8,16 @@ class CommitEntity < API::Entities::RepoCommit ...@@ -8,16 +8,16 @@ class CommitEntity < API::Entities::RepoCommit
end end
expose :commit_url do |commit| expose :commit_url do |commit|
namespace_project_tree_url( namespace_project_commit_url(
request.project.namespace, request.project.namespace,
request.project, request.project,
id: commit.id) commit)
end end
expose :commit_path do |commit| expose :commit_path do |commit|
namespace_project_tree_path( namespace_project_commit_path(
request.project.namespace, request.project.namespace,
request.project, request.project,
id: commit.id) commit)
end end
end end
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
Your New Personal Access Token Your New Personal Access Token
.form-group .form-group
= text_field_tag 'created-personal-access-token', flash[:personal_access_token], readonly: true, class: "form-control", 'aria-describedby' => "created-personal-access-token-help-block" = text_field_tag 'created-personal-access-token', flash[:personal_access_token], readonly: true, class: "form-control", 'aria-describedby' => "created-personal-access-token-help-block"
= clipboard_button(clipboard_text: flash[:personal_access_token]) = clipboard_button(clipboard_text: flash[:personal_access_token], title: "Copy personal access token to clipboard", placement: "left")
%span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again. %span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again.
%hr %hr
......
...@@ -7,20 +7,21 @@ ...@@ -7,20 +7,21 @@
%p %p
= @teams.one? ? 'The team' : 'Select the team' = @teams.one? ? 'The team' : 'Select the team'
where the slash commands will be used in where the slash commands will be used in
- selected_id = @teams.keys.first if @teams.one? - selected_id = @teams.one? ? @teams.keys.first : 0
- options = mattermost_teams_options(@teams) - options = mattermost_teams_options(@teams)
- options = options_for_select(options, selected_id) - options = options_for_select(options, selected_id)
= f.select(:team_id, options, {}, { class: 'form-control', selected: "#{selected_id}" }) = f.select(:team_id, options, {}, { class: 'form-control', disabled: @teams.one?, selected: selected_id })
= f.hidden_field(:team_id, value: selected_id) if @teams.one?
.help-block .help-block
- if @teams.one? - if @teams.one?
This is the only team where you are an administrator. This is the only available team.
- else - else
The list shows teams where you are administrator The list shows all available teams.
To create a team, ask your Mattermost system administrator.
To create a team, To create a team,
= link_to "#{Gitlab.config.mattermost.host}/create_team" do = link_to "#{Gitlab.config.mattermost.host}/create_team" do
use Mattermost's interface use Mattermost's interface
= icon('external-link') = icon('external-link')
or ask your Mattermost system administrator.
%hr %hr
%h4 Command trigger word %h4 Command trigger word
%p Choose the word that will trigger commands %p Choose the word that will trigger commands
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
%p %p
%strong Step 1. %strong Step 1.
Fetch and check out the branch for this merge request Fetch and check out the branch for this merge request
= clipboard_button(clipboard_target: "pre#merge-info-1") = clipboard_button(clipboard_target: "pre#merge-info-1", title: "Copy commands to clipboard")
%pre.dark#merge-info-1 %pre.dark#merge-info-1
- if @merge_request.for_fork? - if @merge_request.for_fork?
:preserve :preserve
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
%p %p
%strong Step 3. %strong Step 3.
Merge the branch and fix any conflicts that come up Merge the branch and fix any conflicts that come up
= clipboard_button(clipboard_target: "pre#merge-info-3") = clipboard_button(clipboard_target: "pre#merge-info-3", title: "Copy commands to clipboard")
%pre.dark#merge-info-3 %pre.dark#merge-info-3
- if @merge_request.for_fork? - if @merge_request.for_fork?
:preserve :preserve
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
%p %p
%strong Step 4. %strong Step 4.
Push the result of the merge to GitLab Push the result of the merge to GitLab
= clipboard_button(clipboard_target: "pre#merge-info-4") = clipboard_button(clipboard_target: "pre#merge-info-4", title: "Copy commands to clipboard")
%pre.dark#merge-info-4 %pre.dark#merge-info-4
:preserve :preserve
git push origin #{h @merge_request.target_branch} git push origin #{h @merge_request.target_branch}
......
- stage = local_assigns.fetch(:stage) - stage = local_assigns.fetch(:stage)
- statuses = stage.statuses.latest - statuses = stage.statuses.latest
- status_groups = statuses.sort_by(&:name).group_by(&:group_name) - status_groups = statuses.sort_by(&:sortable_name).group_by(&:group_name)
%li.stage-column %li.stage-column
.stage-name .stage-name
%a{ name: stage.name } %a{ name: stage.name }
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true
.input-group-btn .input-group-btn
= clipboard_button(clipboard_target: '#project_clone') = clipboard_button(clipboard_target: '#project_clone', title: "Copy URL to clipboard")
:javascript :javascript
$('ul.clone-options-dropdown a').on('click',function(e){ $('ul.clone-options-dropdown a').on('click',function(e){
......
...@@ -153,13 +153,13 @@ ...@@ -153,13 +153,13 @@
- project_ref = cross_project_reference(@project, issuable) - project_ref = cross_project_reference(@project, issuable)
.block.project-reference .block.project-reference
.sidebar-collapsed-icon.dont-change-state .sidebar-collapsed-icon.dont-change-state
= clipboard_button(clipboard_text: project_ref) = clipboard_button(clipboard_text: project_ref, title: "Copy reference to clipboard", placement: "left")
.cross-project-reference.hide-collapsed .cross-project-reference.hide-collapsed
%span %span
Reference: Reference:
%cite{ title: project_ref } %cite{ title: project_ref }
= project_ref = project_ref
= clipboard_button(clipboard_text: project_ref) = clipboard_button(clipboard_text: project_ref, title: "Copy reference to clipboard", placement: "left")
:javascript :javascript
new MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}'); new MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}');
......
...@@ -2,7 +2,7 @@ class ReactiveCachingWorker ...@@ -2,7 +2,7 @@ class ReactiveCachingWorker
include Sidekiq::Worker include Sidekiq::Worker
include DedicatedSidekiqQueue include DedicatedSidekiqQueue
def perform(class_name, id) def perform(class_name, id, *args)
klass = begin klass = begin
Kernel.const_get(class_name) Kernel.const_get(class_name)
rescue NameError rescue NameError
...@@ -10,6 +10,6 @@ class ReactiveCachingWorker ...@@ -10,6 +10,6 @@ class ReactiveCachingWorker
end end
return unless klass return unless klass
klass.find_by(id: id).try(:exclusively_update_reactive_cache!) klass.find_by(id: id).try(:exclusively_update_reactive_cache!, *args)
end end
end end
---
title: Query external CI statuses in the background
merge_request:
author:
---
title: Check for env[Grape::Env::GRAPE_ROUTING_ARGS] instead of endpoint.route
merge_request: 8544
author:
---
title: Fixes pipeline status cell is too wide by adding missing classes in table head cells
merge_request: 8549
author:
---
title: Search bar redesign first iteration
merge_request: 7345
author:
---
title: Mutate the attribute instead of issuing a write operation to the DB in `ProjectFeaturesCompatibility`
concern.
merge_request: 8552
author:
---
title: 'Copy <some text> to clipboard'
merge_request: 8535
---
title: Allow to use ENV variables in redis config
merge_request: 8073
author: Semyon Pupkov
---
title: Sort numbers in build names more intelligently
merge_request: 8277
author:
---
title: 'API: fix query response for `/projects/:id/issues?milestone="No%20Milestone"`'
merge_request: 8457
author: Panagiotis Atmatzidis, David Eisner
---
title: Fix links to commits pages on pipelines list page
merge_request: 8558
author:
...@@ -14,9 +14,8 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration ...@@ -14,9 +14,8 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration
namespace_id = user['namespace_id'] namespace_id = user['namespace_id']
path_was = user['username'] path_was = user['username']
path_was_wildcard = quote_string("#{path_was}/%") path_was_wildcard = quote_string("#{path_was}/%")
path = quote_string(rename_path(path_was))
move_namespace(namespace_id, path_was, path) path = move_namespace(namespace_id, path_was, path)
execute "UPDATE routes SET path = '#{path}' WHERE source_type = 'Namespace' AND source_id = #{namespace_id}" execute "UPDATE routes SET path = '#{path}' WHERE source_type = 'Namespace' AND source_id = #{namespace_id}"
execute "UPDATE namespaces SET path = '#{path}' WHERE id = #{namespace_id}" execute "UPDATE namespaces SET path = '#{path}' WHERE id = #{namespace_id}"
...@@ -45,9 +44,13 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration ...@@ -45,9 +44,13 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration
select_all("SELECT id, path FROM routes WHERE path = '#{quote_string(path)}'").present? select_all("SELECT id, path FROM routes WHERE path = '#{quote_string(path)}'").present?
end end
def path_exists?(repository_storage_path, path)
gitlab_shell.exists?(repository_storage_path, path)
end
# Accepts invalid path like test.git and returns test_git or # Accepts invalid path like test.git and returns test_git or
# test_git1 if test_git already taken # test_git1 if test_git already taken
def rename_path(path) def rename_path(repository_storage_path, path)
# To stay closer with original name and reduce risk of duplicates # To stay closer with original name and reduce risk of duplicates
# we rename suffix instead of removing it # we rename suffix instead of removing it
path = path.sub(/\.git\z/, '_git') path = path.sub(/\.git\z/, '_git')
...@@ -55,7 +58,7 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration ...@@ -55,7 +58,7 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration
counter = 0 counter = 0
base = path base = path
while route_exists?(path) while route_exists?(path) || path_exists?(repository_storage_path, path)
counter += 1 counter += 1
path = "#{base}#{counter}" path = "#{base}#{counter}"
end end
...@@ -73,6 +76,8 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration ...@@ -73,6 +76,8 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration
# Ensure old directory exists before moving it # Ensure old directory exists before moving it
gitlab_shell.add_namespace(repository_storage_path, path_was) gitlab_shell.add_namespace(repository_storage_path, path_was)
path = quote_string(rename_path(repository_storage_path, path_was))
unless gitlab_shell.mv_namespace(repository_storage_path, path_was, path) unless gitlab_shell.mv_namespace(repository_storage_path, path_was, path)
Rails.logger.error "Exception moving path #{repository_storage_path} from #{path_was} to #{path}" Rails.logger.error "Exception moving path #{repository_storage_path} from #{path_was} to #{path}"
...@@ -83,5 +88,7 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration ...@@ -83,5 +88,7 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration
end end
Gitlab::UploadsTransfer.new.rename_namespace(path_was, path) Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
path
end end
end end
...@@ -23,12 +23,15 @@ GET /issues?state=closed ...@@ -23,12 +23,15 @@ GET /issues?state=closed
GET /issues?labels=foo GET /issues?labels=foo
GET /issues?labels=foo,bar GET /issues?labels=foo,bar
GET /issues?labels=foo,bar&state=opened GET /issues?labels=foo,bar&state=opened
GET /issues?milestone=1.0.0
GET /issues?milestone=1.0.0&state=opened
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `state` | string | no | Return all issues or just those that are `opened` or `closed`| | `state` | string | no | Return all issues or just those that are `opened` or `closed`|
| `labels` | string | no | Comma-separated list of label names, issues with any of the labels will be returned | | `labels` | string | no | Comma-separated list of label names, issues with any of the labels will be returned |
| `milestone` | string| no | The milestone title |
| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` | | `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
......
...@@ -601,6 +601,12 @@ If you want to connect the Redis server via socket, then use the "unix:" URL sch ...@@ -601,6 +601,12 @@ If you want to connect the Redis server via socket, then use the "unix:" URL sch
production: production:
url: unix:/path/to/redis/socket url: unix:/path/to/redis/socket
Also you can use environment variables in the `config/resque.yml` file:
# example
production:
url: <%= ENV.fetch('GITLAB_REDIS_URL') %>
### Custom SSH Connection ### Custom SSH Connection
If you are running SSH on a non-standard port, you must change the GitLab user's SSH config. If you are running SSH on a non-standard port, you must change the GitLab user's SSH config.
......
...@@ -6,7 +6,7 @@ Slack commands give users an extra interface to perform common operations ...@@ -6,7 +6,7 @@ Slack commands give users an extra interface to perform common operations
from the chat environment. This allows one to, for example, create an issue as from the chat environment. This allows one to, for example, create an issue as
soon as the idea was discussed in chat. soon as the idea was discussed in chat.
For all available commands try the help subcommand, for example: `/gitlab help`, For all available commands try the help subcommand, for example: `/gitlab help`,
all review the [full list of commands](../integrations/chat_commands.md). all review the [full list of commands](../integration/chat_commands.md).
## Prerequisites ## Prerequisites
......
@admin
Feature: Admin Users
Background:
Given I sign in as an admin
And system has users
Scenario: On Admin Users
Given I visit admin users page
Then I should see all users
Scenario: Edit user and change username to non ascii char
When I visit admin users page
And Click edit
And Input non ascii char in username
And Click save
Then See username error message
And Not changed form action url
Scenario: Show user attributes
Given user "Mike" with groups and projects
Given I visit admin users page
And click on "Mike" link
Then I should see user "Mike" details
Scenario: Edit my user attributes
Given I visit admin users page
And click edit on my user
When I submit modified user
Then I see user attributes changed
@javascript
Scenario: Remove users secondary email
Given I visit admin users page
And I view the user with secondary email
And I see the secondary email
When I click remove secondary email
Then I should not see secondary email anymore
Scenario: Show user keys
Given user "Pete" with ssh keys
And I visit admin users page
And click on user "Pete"
And click on ssh keys tab
Then I should see key list
And I click on the key title
Then I should see key details
And I click on remove key
Then I should see the key removed
Scenario: Show user identities
Given user "Pete" with twitter account
And I visit "Pete" identities page in admin
Then I should see twitter details
Scenario: Update user identities
Given user "Pete" with twitter account
And I visit "Pete" identities page in admin
And I modify twitter identity
Then I should see twitter details updated
Scenario: Remove user identities
Given user "Pete" with twitter account
And I visit "Pete" identities page in admin
And I remove twitter identity
Then I should not see twitter details
@dashboard
Feature: Dashboard Active Tab
Background:
Given I sign in as a user
Scenario: On Dashboard Home
Given I visit dashboard page
Then the active main tab should be Home
And no other main tabs should be active
Scenario: On Dashboard Issues
Given I visit dashboard issues page
Then the active main tab should be Issues
And no other main tabs should be active
Scenario: On Dashboard Merge Requests
Given I visit dashboard merge requests page
Then the active main tab should be Merge Requests
And no other main tabs should be active
Scenario: On Dashboard Groups
Given I visit dashboard groups page
Then the active main tab should be Groups
And no other main tabs should be active
@dashboard
Feature: Dashboard Archived Projects
Background:
Given I sign in as a user
And I own project "Shop"
And I own project "Forum"
And project "Forum" is archived
And I visit dashboard page
Scenario: I should see non-archived projects on dashboard
Then I should see "Shop" project link
And I should not see "Forum" project link
Scenario: I toggle show of archived projects on dashboard
When I click "Show archived projects" link
Then I should see "Shop" project link
And I should see "Forum" project link
@dashboard
Feature: Dashboard Group
Background:
Given I sign in as "John Doe"
And "John Doe" is owner of group "Owned"
And "John Doe" is guest of group "Guest"
Scenario: Create a group from dasboard
And I visit dashboard groups page
And I click new group link
And submit form with new group "Samurai" info
Then I should be redirected to group "Samurai" page
And I should see newly created group "Samurai"
@dashboard
Feature: Dashboard Help
Background:
Given I sign in as a user
And I visit the "Rake Tasks" help page
Scenario: The markdown should be rendered correctly
Then I should see "Rake Tasks" page markdown rendered
And Header "Rebuild project satellites" should have correct ids and links
class Spinach::Features::AdminUsers < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedAdmin
before do
allow(Gitlab::OAuth::Provider).to receive(:providers).and_return([:twitter, :twitter_updated])
allow_any_instance_of(ApplicationHelper).to receive(:user_omniauth_authorize_path).and_return(root_path)
end
after do
allow(Gitlab::OAuth::Provider).to receive(:providers).and_call_original
allow_any_instance_of(ApplicationHelper).to receive(:user_omniauth_authorize_path).and_call_original
end
step 'I should see all users' do
User.all.each do |user|
expect(page).to have_content user.name
end
end
step 'Click edit' do
@user = User.first
find("#edit_user_#{@user.id}").click
end
step 'Input non ascii char in username' do
fill_in 'user_username', with: "\u3042\u3044"
end
step 'Click save' do
click_button("Save")
end
step 'See username error message' do
page.within "#error_explanation" do
expect(page).to have_content "Username"
end
end
step 'Not changed form action url' do
expect(page).to have_selector %(form[action="/admin/users/#{@user.username}"])
end
step 'I submit modified user' do
check :user_can_create_group
click_button 'Save'
end
step 'I see user attributes changed' do
expect(page).to have_content 'Can create groups: Yes'
end
step 'click edit on my user' do
find("#edit_user_#{current_user.id}").click
end
step 'I view the user with secondary email' do
@user_with_secondary_email = User.last
@user_with_secondary_email.emails.new(email: "secondary@example.com")
@user_with_secondary_email.save
visit "/admin/users/#{@user_with_secondary_email.username}"
end
step 'I see the secondary email' do
expect(page).to have_content "Secondary email: #{@user_with_secondary_email.emails.last.email}"
end
step 'I click remove secondary email' do
find("#remove_email_#{@user_with_secondary_email.emails.last.id}").click
end
step 'I should not see secondary email anymore' do
expect(page).not_to have_content "Secondary email:"
end
step 'user "Mike" with groups and projects' do
user = create(:user, name: 'Mike')
project = create(:empty_project)
project.team << [user, :developer]
group = create(:group)
group.add_developer(user)
end
step 'click on "Mike" link' do
click_link "Mike"
end
step 'I should see user "Mike" details' do
expect(page).to have_content 'Account'
expect(page).to have_content 'Personal projects limit'
end
step 'user "Pete" with ssh keys' do
user = create(:user, name: 'Pete')
create(:key, user: user, title: "ssh-rsa Key1", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4FIEBXGi4bPU8kzxMefudPIJ08/gNprdNTaO9BR/ndy3+58s2HCTw2xCHcsuBmq+TsAqgEidVq4skpqoTMB+Uot5Uzp9z4764rc48dZiI661izoREoKnuRQSsRqUTHg5wrLzwxlQbl1MVfRWQpqiz/5KjBC7yLEb9AbusjnWBk8wvC1bQPQ1uLAauEA7d836tgaIsym9BrLsMVnR4P1boWD3Xp1B1T/ImJwAGHvRmP/ycIqmKdSpMdJXwxcb40efWVj0Ibbe7ii9eeoLdHACqevUZi6fwfbymdow+FeqlkPoHyGg3Cu4vD/D8+8cRc7mE/zGCWcQ15Var83Tczour Key1")
create(:key, user: user, title: "ssh-rsa Key2", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2")
end
step 'click on user "Pete"' do
click_link 'Pete'
end
step 'I should see key list' do
expect(page).to have_content 'ssh-rsa Key2'
expect(page).to have_content 'ssh-rsa Key1'
end
step 'I click on the key title' do
click_link 'ssh-rsa Key2'
end
step 'I should see key details' do
expect(page).to have_content 'ssh-rsa Key2'
expect(page).to have_content 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2'
end
step 'I click on remove key' do
click_link 'Remove'
end
step 'I should see the key removed' do
expect(page).not_to have_content 'ssh-rsa Key2'
end
step 'user "Pete" with twitter account' do
@user = create(:user, name: 'Pete')
@user.identities.create!(extern_uid: '123456', provider: 'twitter')
end
step 'I visit "Pete" identities page in admin' do
visit admin_user_identities_path(@user)
end
step 'I should see twitter details' do
expect(page).to have_content 'Pete'
expect(page).to have_content 'twitter'
end
step 'I modify twitter identity' do
find('.table').find(:link, 'Edit').click
fill_in 'identity_extern_uid', with: '654321'
select 'twitter_updated', from: 'identity_provider'
click_button 'Save changes'
end
step 'I should see twitter details updated' do
expect(page).to have_content 'Pete'
expect(page).to have_content 'twitter_updated'
expect(page).to have_content '654321'
end
step 'I remove twitter identity' do
click_link 'Delete'
end
step 'I should not see twitter details' do
expect(page).to have_content 'Pete'
expect(page).not_to have_content 'twitter'
end
step 'click on ssh keys tab' do
click_link 'SSH keys'
end
end
class Spinach::Features::DashboardActiveTab < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedSidebarActiveTab
end
class Spinach::Features::DashboardArchivedProjects < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
When 'project "Forum" is archived' do
project = Project.find_by(name: "Forum")
project.update_attribute(:archived, true)
end
step 'I should see "Shop" project link' do
expect(page).to have_link "Shop"
end
step 'I should not see "Forum" project link' do
expect(page).not_to have_link "Forum"
end
step 'I should see "Forum" project link' do
expect(page).to have_link "Forum"
end
step 'I click "Show archived projects" link' do
click_link "Show archived projects"
end
end
class Spinach::Features::DashboardGroup < Spinach::FeatureSteps
include SharedAuthentication
include SharedGroup
include SharedPaths
include SharedUser
step 'I click new group link' do
click_link "New Group"
end
step 'submit form with new group "Samurai" info' do
fill_in 'group_path', with: 'Samurai'
fill_in 'group_description', with: 'Tokugawa Shogunate'
click_button "Create group"
end
step 'I should be redirected to group "Samurai" page' do
expect(current_path).to eq group_path(Group.find_by(name: 'Samurai'))
end
step 'I should see newly created group "Samurai"' do
expect(page).to have_content "Samurai"
expect(page).to have_content "Tokugawa Shogunate"
end
end
class Spinach::Features::DashboardHelp < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedMarkdown
step 'I visit the help page' do
visit help_path
end
step 'I visit the "Rake Tasks" help page' do
visit help_page_path("administration/raketasks/maintenance")
end
step 'I should see "Rake Tasks" page markdown rendered' do
expect(page).to have_content "Gather information about GitLab and the system it runs on"
end
step 'Header "Rebuild project satellites" should have correct ids and links' do
header_should_have_correct_id_and_link(2, 'Check GitLab configuration', 'check-gitlab-configuration', '.documentation')
end
end
...@@ -5,13 +5,31 @@ module API ...@@ -5,13 +5,31 @@ module API
before { authenticate! } before { authenticate! }
helpers do helpers do
# TODO: Remove in 9.0 and switch to IssueFinder-based label filtering def find_issues(args = {})
def filter_issues_labels(issues, labels) args = params.merge(args)
issues.includes(:labels).where('labels.title' => labels.split(','))
args.delete(:id)
args[:milestone_title] = args.delete(:milestone)
match_all_labels = args.delete(:match_all_labels)
labels = args.delete(:labels)
args[:label_name] = labels if match_all_labels
args[:search] = "#{Issue.reference_prefix}#{args.delete(:iid)}" if args.key?(:iid)
issues = IssuesFinder.new(current_user, args).execute.inc_notes_with_associations
# TODO: Remove in 9.0 pass `label_name: args.delete(:labels)` to IssuesFinder
if !match_all_labels && labels.present?
issues = issues.includes(:labels).where('labels.title' => labels.split(','))
end
issues.reorder(args[:order_by] => args[:sort])
end end
params :issues_params do params :issues_params do
optional :labels, type: String, desc: 'Comma-separated list of label names' optional :labels, type: String, desc: 'Comma-separated list of label names'
optional :milestone, type: String, desc: 'Milestone title'
optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at', optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at',
desc: 'Return issues ordered by `created_at` or `updated_at` fields.' desc: 'Return issues ordered by `created_at` or `updated_at` fields.'
optional :sort, type: String, values: %w[asc desc], default: 'desc', optional :sort, type: String, values: %w[asc desc], default: 'desc',
...@@ -40,9 +58,7 @@ module API ...@@ -40,9 +58,7 @@ module API
use :issues_params use :issues_params
end end
get do get do
issues = IssuesFinder.new(current_user, scope: 'all', author_id: current_user.id, state: params[:state]).execute.inc_notes_with_associations issues = find_issues(scope: 'authored')
issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
issues = issues.reorder(params[:order_by] => params[:sort])
present paginate(issues), with: Entities::Issue, current_user: current_user present paginate(issues), with: Entities::Issue, current_user: current_user
end end
...@@ -61,15 +77,10 @@ module API ...@@ -61,15 +77,10 @@ module API
use :issues_params use :issues_params
end end
get ":id/issues" do get ":id/issues" do
group = find_group!(params.delete(:id)) group = find_group!(params[:id])
params[:group_id] = group.id issues = find_issues(group_id: group.id, state: params[:state] || 'opened', match_all_labels: true)
params[:milestone_title] = params.delete(:milestone)
params[:label_name] = params.delete(:labels)
issues = IssuesFinder.new(current_user, params).execute
issues = issues.reorder(params[:order_by] => params[:sort])
present paginate(issues), with: Entities::Issue, current_user: current_user present paginate(issues), with: Entities::Issue, current_user: current_user
end end
end end
...@@ -84,17 +95,13 @@ module API ...@@ -84,17 +95,13 @@ module API
params do params do
optional :state, type: String, values: %w[opened closed all], default: 'all', optional :state, type: String, values: %w[opened closed all], default: 'all',
desc: 'Return opened, closed, or all issues' desc: 'Return opened, closed, or all issues'
optional :iid, type: Integer, desc: 'The IID of the issue' optional :iid, type: Integer, desc: 'Return the issue having the given `iid`'
use :issues_params use :issues_params
end end
get ":id/issues" do get ":id/issues" do
issues = IssuesFinder.new(current_user, project = find_project(params[:id])
project_id: user_project.id,
state: params[:state], issues = find_issues(project_id: project.id)
milestone_title: params[:milestone]).execute.inc_notes_with_associations
issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
issues = filter_by_iid(issues, params[:iid]) unless params[:iid].nil?
issues = issues.reorder(params[:order_by] => params[:sort])
present paginate(issues), with: Entities::Issue, current_user: current_user, project: user_project present paginate(issues), with: Entities::Issue, current_user: current_user, project: user_project
end end
......
...@@ -30,9 +30,9 @@ module Gitlab ...@@ -30,9 +30,9 @@ module Gitlab
return unless @branch_name return unless @branch_name
return unless project.protected_branch?(@branch_name) return unless project.protected_branch?(@branch_name)
if forced_push? && user_access.cannot_do_action?(:force_push_code_to_protected_branches) if forced_push?
return "You are not allowed to force push code to a protected branch on this project." return "You are not allowed to force push code to a protected branch on this project."
elsif Gitlab::Git.blank_ref?(@newrev) && user_access.cannot_do_action?(:remove_protected_branches) elsif Gitlab::Git.blank_ref?(@newrev)
return "You are not allowed to delete protected branches from this project." return "You are not allowed to delete protected branches from this project."
end end
......
...@@ -71,10 +71,17 @@ module Gitlab ...@@ -71,10 +71,17 @@ module Gitlab
def tag_endpoint(trans, env) def tag_endpoint(trans, env)
endpoint = env[ENDPOINT_KEY] endpoint = env[ENDPOINT_KEY]
# endpoint.route is nil in the case of a 405 response begin
if endpoint.route route = endpoint.route
path = endpoint_paths_cache[endpoint.route.request_method][endpoint.route.path] rescue
trans.action = "Grape##{endpoint.route.request_method} #{path}" # endpoint.route is calling env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info]
# but env[Grape::Env::GRAPE_ROUTING_ARGS] is nil in the case of a 405 response
# so we're rescuing exceptions and bailing out
end
if route
path = endpoint_paths_cache[route.request_method][route.path]
trans.action = "Grape##{route.request_method} #{path}"
end end
end end
......
...@@ -42,7 +42,7 @@ module Gitlab ...@@ -42,7 +42,7 @@ module Gitlab
return @_raw_config if defined?(@_raw_config) return @_raw_config if defined?(@_raw_config)
begin begin
@_raw_config = File.read(CONFIG_FILE).freeze @_raw_config = ERB.new(File.read(CONFIG_FILE)).result.freeze
rescue Errno::ENOENT rescue Errno::ENOENT
@_raw_config = false @_raw_config = false
end end
......
...@@ -12,7 +12,7 @@ describe Dashboard::TodosController do ...@@ -12,7 +12,7 @@ describe Dashboard::TodosController do
end end
context 'when using pagination' do context 'when using pagination' do
let(:last_page) { user.todos.page().total_pages } let(:last_page) { user.todos.page.total_pages }
let!(:issues) { create_list(:issue, 2, project: project, assignee: user) } let!(:issues) { create_list(:issue, 2, project: project, assignee: user) }
before do before do
......
...@@ -24,6 +24,10 @@ FactoryGirl.define do ...@@ -24,6 +24,10 @@ FactoryGirl.define do
visibility_level Gitlab::VisibilityLevel::PRIVATE visibility_level Gitlab::VisibilityLevel::PRIVATE
end end
trait :archived do
archived true
end
trait :access_requestable do trait :access_requestable do
request_access_enabled true request_access_enabled true
end end
......
...@@ -3,7 +3,11 @@ require 'spec_helper' ...@@ -3,7 +3,11 @@ require 'spec_helper'
describe "Admin::Users", feature: true do describe "Admin::Users", feature: true do
include WaitForAjax include WaitForAjax
before { login_as :admin } let!(:user) do
create(:omniauth_user, provider: 'twitter', extern_uid: '123456')
end
let!(:current_user) { login_as :admin }
describe "GET /admin/users" do describe "GET /admin/users" do
before do before do
...@@ -15,8 +19,10 @@ describe "Admin::Users", feature: true do ...@@ -15,8 +19,10 @@ describe "Admin::Users", feature: true do
end end
it "has users list" do it "has users list" do
expect(page).to have_content(@user.email) expect(page).to have_content(current_user.email)
expect(page).to have_content(@user.name) expect(page).to have_content(current_user.name)
expect(page).to have_content(user.email)
expect(page).to have_content(user.name)
end end
describe 'Two-factor Authentication filters' do describe 'Two-factor Authentication filters' do
...@@ -40,8 +46,6 @@ describe "Admin::Users", feature: true do ...@@ -40,8 +46,6 @@ describe "Admin::Users", feature: true do
end end
it 'counts users who have not enabled 2FA' do it 'counts users who have not enabled 2FA' do
create(:user)
visit admin_users_path visit admin_users_path
page.within('.filter-two-factor-disabled small') do page.within('.filter-two-factor-disabled small') do
...@@ -50,8 +54,6 @@ describe "Admin::Users", feature: true do ...@@ -50,8 +54,6 @@ describe "Admin::Users", feature: true do
end end
it 'filters by users who have not enabled 2FA' do it 'filters by users who have not enabled 2FA' do
user = create(:user)
visit admin_users_path visit admin_users_path
click_link '2FA Disabled' click_link '2FA Disabled'
...@@ -110,10 +112,10 @@ describe "Admin::Users", feature: true do ...@@ -110,10 +112,10 @@ describe "Admin::Users", feature: true do
describe "GET /admin/users/:id" do describe "GET /admin/users/:id" do
it "has user info" do it "has user info" do
visit admin_users_path visit admin_users_path
click_link @user.name click_link user.name
expect(page).to have_content(@user.email) expect(page).to have_content(user.email)
expect(page).to have_content(@user.name) expect(page).to have_content(user.name)
end end
describe 'Impersonation' do describe 'Impersonation' do
...@@ -126,7 +128,7 @@ describe "Admin::Users", feature: true do ...@@ -126,7 +128,7 @@ describe "Admin::Users", feature: true do
end end
it 'does not show impersonate button for admin itself' do it 'does not show impersonate button for admin itself' do
visit admin_user_path(@user) visit admin_user_path(current_user)
expect(page).not_to have_content('Impersonate') expect(page).not_to have_content('Impersonate')
end end
...@@ -158,7 +160,7 @@ describe "Admin::Users", feature: true do ...@@ -158,7 +160,7 @@ describe "Admin::Users", feature: true do
it 'logs out of impersonated user back to original user' do it 'logs out of impersonated user back to original user' do
find(:css, 'li.impersonation a').click find(:css, 'li.impersonation a').click
expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(@user.username) expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(current_user.username)
end end
it 'is redirected back to the impersonated users page in the admin after stopping' do it 'is redirected back to the impersonated users page in the admin after stopping' do
...@@ -171,15 +173,15 @@ describe "Admin::Users", feature: true do ...@@ -171,15 +173,15 @@ describe "Admin::Users", feature: true do
describe 'Two-factor Authentication status' do describe 'Two-factor Authentication status' do
it 'shows when enabled' do it 'shows when enabled' do
@user.update_attribute(:otp_required_for_login, true) user.update_attribute(:otp_required_for_login, true)
visit admin_user_path(@user) visit admin_user_path(user)
expect_two_factor_status('Enabled') expect_two_factor_status('Enabled')
end end
it 'shows when disabled' do it 'shows when disabled' do
visit admin_user_path(@user) visit admin_user_path(user)
expect_two_factor_status('Disabled') expect_two_factor_status('Disabled')
end end
...@@ -194,9 +196,8 @@ describe "Admin::Users", feature: true do ...@@ -194,9 +196,8 @@ describe "Admin::Users", feature: true do
describe "GET /admin/users/:id/edit" do describe "GET /admin/users/:id/edit" do
before do before do
@simple_user = create(:user)
visit admin_users_path visit admin_users_path
click_link "edit_user_#{@simple_user.id}" click_link "edit_user_#{user.id}"
end end
it "has user edit page" do it "has user edit page" do
...@@ -220,39 +221,52 @@ describe "Admin::Users", feature: true do ...@@ -220,39 +221,52 @@ describe "Admin::Users", feature: true do
end end
it "changes user entry" do it "changes user entry" do
@simple_user.reload user.reload
expect(@simple_user.name).to eq('Big Bang') expect(user.name).to eq('Big Bang')
expect(@simple_user.is_admin?).to be_truthy expect(user.is_admin?).to be_truthy
expect(@simple_user.password_expires_at).to be <= Time.now expect(user.password_expires_at).to be <= Time.now
end
end
describe 'update username to non ascii char' do
it do
fill_in 'user_username', with: '\u3042\u3044'
click_button('Save')
page.within '#error_explanation' do
expect(page).to have_content('Username')
end
expect(page).to have_selector(%(form[action="/admin/users/#{user.username}"]))
end end
end end
end end
describe "GET /admin/users/:id/projects" do describe "GET /admin/users/:id/projects" do
let(:group) { create(:group) }
let!(:project) { create(:project, group: group) }
before do before do
@group = create(:group) group.add_developer(user)
@project = create(:project, group: @group)
@simple_user = create(:user)
@group.add_developer(@simple_user)
visit projects_admin_user_path(@simple_user) visit projects_admin_user_path(user)
end end
it "lists group projects" do it "lists group projects" do
within(:css, '.append-bottom-default + .panel') do within(:css, '.append-bottom-default + .panel') do
expect(page).to have_content 'Group projects' expect(page).to have_content 'Group projects'
expect(page).to have_link @group.name, admin_group_path(@group) expect(page).to have_link group.name, admin_group_path(group)
end end
end end
it 'allows navigation to the group details' do it 'allows navigation to the group details' do
within(:css, '.append-bottom-default + .panel') do within(:css, '.append-bottom-default + .panel') do
click_link @group.name click_link group.name
end end
within(:css, 'h3.page-title') do within(:css, 'h3.page-title') do
expect(page).to have_content "Group: #{@group.name}" expect(page).to have_content "Group: #{group.name}"
end end
expect(page).to have_content @project.name expect(page).to have_content project.name
end end
it 'shows the group access level' do it 'shows the group access level' do
...@@ -270,4 +284,99 @@ describe "Admin::Users", feature: true do ...@@ -270,4 +284,99 @@ describe "Admin::Users", feature: true do
expect(page).not_to have_selector('.group_member') expect(page).not_to have_selector('.group_member')
end end
end end
describe 'show user attributes' do
it do
visit admin_users_path
click_link user.name
expect(page).to have_content 'Account'
expect(page).to have_content 'Personal projects limit'
end
end
describe 'remove users secondary email', js: true do
let!(:secondary_email) do
create :email, email: 'secondary@example.com', user: user
end
it do
visit admin_user_path(user.username)
expect(page).to have_content("Secondary email: #{secondary_email.email}")
find("#remove_email_#{secondary_email.id}").click
expect(page).not_to have_content(secondary_email.email)
end
end
describe 'show user keys' do
let!(:key1) do
create(:key, user: user, title: "ssh-rsa Key1", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4FIEBXGi4bPU8kzxMefudPIJ08/gNprdNTaO9BR/ndy3+58s2HCTw2xCHcsuBmq+TsAqgEidVq4skpqoTMB+Uot5Uzp9z4764rc48dZiI661izoREoKnuRQSsRqUTHg5wrLzwxlQbl1MVfRWQpqiz/5KjBC7yLEb9AbusjnWBk8wvC1bQPQ1uLAauEA7d836tgaIsym9BrLsMVnR4P1boWD3Xp1B1T/ImJwAGHvRmP/ycIqmKdSpMdJXwxcb40efWVj0Ibbe7ii9eeoLdHACqevUZi6fwfbymdow+FeqlkPoHyGg3Cu4vD/D8+8cRc7mE/zGCWcQ15Var83Tczour Key1")
end
let!(:key2) do
create(:key, user: user, title: "ssh-rsa Key2", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2")
end
it do
visit admin_users_path
click_link user.name
click_link 'SSH keys'
expect(page).to have_content(key1.title)
expect(page).to have_content(key2.title)
click_link key2.title
expect(page).to have_content(key2.title)
expect(page).to have_content(key2.key)
click_link 'Remove'
expect(page).not_to have_content(key2.title)
end
end
describe 'show user identities' do
it 'shows user identities' do
visit admin_user_identities_path(user)
expect(page).to have_content(user.name)
expect(page).to have_content('twitter')
end
end
describe 'update user identities' do
before do
allow(Gitlab::OAuth::Provider).to receive(:providers).and_return([:twitter, :twitter_updated])
end
it 'modifies twitter identity' do
visit admin_user_identities_path(user)
find('.table').find(:link, 'Edit').click
fill_in 'identity_extern_uid', with: '654321'
select 'twitter_updated', from: 'identity_provider'
click_button 'Save changes'
expect(page).to have_content(user.name)
expect(page).to have_content('twitter_updated')
expect(page).to have_content('654321')
end
end
describe 'remove user with identities' do
it 'removes user with twitter identity' do
visit admin_user_identities_path(user)
click_link 'Delete'
expect(page).to have_content(user.name)
expect(page).not_to have_content('twitter')
end
end
end end
...@@ -3,7 +3,6 @@ require 'spec_helper' ...@@ -3,7 +3,6 @@ require 'spec_helper'
feature 'Cycle Analytics', feature: true, js: true do feature 'Cycle Analytics', feature: true, js: true do
include WaitForAjax include WaitForAjax
let(:project) { create(:project) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:guest) { create(:user) } let(:guest) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project) }
......
require 'spec_helper'
RSpec.describe 'Dashboard Active Tab', feature: true do
before do
login_as :user
end
shared_examples 'page has active tab' do |title|
it "#{title} tab" do
expect(page).to have_selector('.nav-sidebar li.active', count: 1)
expect(find('.nav-sidebar li.active')).to have_content(title)
end
end
context 'on dashboard projects' do
before do
visit dashboard_projects_path
end
it_behaves_like 'page has active tab', 'Projects'
end
context 'on dashboard issues' do
before do
visit issues_dashboard_path
end
it_behaves_like 'page has active tab', 'Issues'
end
context 'on dashboard merge requests' do
before do
visit merge_requests_dashboard_path
end
it_behaves_like 'page has active tab', 'Merge Requests'
end
context 'on dashboard groups' do
before do
visit dashboard_groups_path
end
it_behaves_like 'page has active tab', 'Groups'
end
end
require 'spec_helper'
RSpec.describe 'Dashboard Archived Project', feature: true do
let(:user) { create :user }
let(:project) { create :project}
let(:archived_project) { create(:project, :archived) }
before do
project.team << [user, :master]
archived_project.team << [user, :master]
login_as(user)
visit dashboard_projects_path
end
it 'renders non archived projects' do
expect(page).to have_link(project.name)
expect(page).not_to have_link(archived_project.name)
end
it 'renders all projects' do
click_link 'Show archived projects'
expect(page).to have_link(project.name)
expect(page).to have_link(archived_project.name)
end
end
require 'spec_helper'
RSpec.describe 'Dashboard Group', feature: true do
before do
login_as(:user)
end
it 'creates new grpup' do
visit dashboard_groups_path
click_link 'New Group'
fill_in 'group_path', with: 'Samurai'
fill_in 'group_description', with: 'Tokugawa Shogunate'
click_button 'Create group'
expect(current_path).to eq group_path(Group.find_by(name: 'Samurai'))
expect(page).to have_content('Samurai')
expect(page).to have_content('Tokugawa Shogunate')
end
end
require 'spec_helper'
RSpec.describe 'Dashboard Help', feature: true do
before do
login_as(:user)
end
it 'renders correctly markdown' do
visit help_page_path("administration/raketasks/maintenance")
expect(page).to have_content('Gather information about GitLab and the system it runs on')
node = find('.documentation h2 a#user-content-check-gitlab-configuration')
expect(node[:href]).to eq '#check-gitlab-configuration'
expect(find(:xpath, "#{node.path}/..").text).to eq 'Check GitLab configuration'
end
end
...@@ -33,10 +33,89 @@ feature 'Setup Mattermost slash commands', feature: true do ...@@ -33,10 +33,89 @@ feature 'Setup Mattermost slash commands', feature: true do
expect(value).to eq(token) expect(value).to eq(token)
end end
describe 'mattermost service is enabled' do
it 'shows the add to mattermost button' do it 'shows the add to mattermost button' do
expect(page).to have_link 'Add to Mattermost' expect(page).to have_link('Add to Mattermost')
end end
it 'shows an explanation if user is a member of no teams' do
stub_teams(count: 0)
click_link 'Add to Mattermost'
expect(page).to have_content('You aren’t a member of any team on the Mattermost instance')
expect(page).to have_link('join a team', href: "#{Gitlab.config.mattermost.host}/select_team")
end
it 'shows an explanation if user is a member of 1 team' do
stub_teams(count: 1)
click_link 'Add to Mattermost'
expect(page).to have_content('The team where the slash commands will be used in')
expect(page).to have_content('This is the only available team.')
end
it 'shows a disabled prefilled select if user is a member of 1 team' do
teams = stub_teams(count: 1)
click_link 'Add to Mattermost'
team_name = teams.first[1]['display_name']
select_element = find('select#mattermost_team_id')
selected_option = select_element.find('option[selected]')
expect(select_element['disabled']).to be(true)
expect(selected_option).to have_content(team_name.to_s)
end
it 'has a hidden input for the prefilled value if user is a member of 1 team' do
teams = stub_teams(count: 1)
click_link 'Add to Mattermost'
expect(find('input#mattermost_team_id', visible: false).value).to eq(teams.first[0].to_s)
end
it 'shows an explanation user is a member of multiple teams' do
stub_teams(count: 2)
click_link 'Add to Mattermost'
expect(page).to have_content('Select the team where the slash commands will be used in')
expect(page).to have_content('The list shows all available teams.')
end
it 'shows a select with team options user is a member of multiple teams' do
stub_teams(count: 2)
click_link 'Add to Mattermost'
select_element = find('select#mattermost_team_id')
selected_option = select_element.find('option[selected]')
expect(select_element['disabled']).to be(false)
expect(selected_option).to have_content('Select team...')
# The 'Select team...' placeholder is item `0`.
expect(select_element.all('option').count).to eq(3)
end
def stub_teams(count: 0)
teams = create_teams(count)
allow_any_instance_of(MattermostSlashCommandsService).to receive(:list_teams) { teams }
teams
end
def create_teams(count = 0)
teams = {}
count.times do |i|
i += 1
teams[i] = { id: i, display_name: i }
end
teams
end end
describe 'mattermost service is not enabled' do describe 'mattermost service is not enabled' do
......
test:
url: <%= ENV['TEST_GITLAB_REDIS_URL'] %>
...@@ -56,7 +56,6 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -56,7 +56,6 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
it 'returns an error if the user is not allowed to do forced pushes to protected branches' do it 'returns an error if the user is not allowed to do forced pushes to protected branches' do
expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true) expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true)
expect(user_access).to receive(:can_do_action?).with(:force_push_code_to_protected_branches).and_return(false)
expect(subject.status).to be(false) expect(subject.status).to be(false)
expect(subject.message).to eq('You are not allowed to force push code to a protected branch on this project.') expect(subject.message).to eq('You are not allowed to force push code to a protected branch on this project.')
...@@ -88,8 +87,6 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -88,8 +87,6 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
end end
it 'returns an error if the user is not allowed to delete protected branches' do it 'returns an error if the user is not allowed to delete protected branches' do
expect(user_access).to receive(:can_do_action?).with(:remove_protected_branches).and_return(false)
expect(subject.status).to be(false) expect(subject.status).to be(false)
expect(subject.message).to eq('You are not allowed to delete protected branches from this project.') expect(subject.message).to eq('You are not allowed to delete protected branches from this project.')
end end
......
...@@ -126,5 +126,16 @@ describe Gitlab::Metrics::RackMiddleware do ...@@ -126,5 +126,16 @@ describe Gitlab::Metrics::RackMiddleware do
expect(transaction.action).to eq('Grape#GET /projects/:id/archive') expect(transaction.action).to eq('Grape#GET /projects/:id/archive')
end end
it 'does not tag a transaction if route infos are missing' do
endpoint = double(:endpoint)
allow(endpoint).to receive(:route).and_raise
env['api.endpoint'] = endpoint
middleware.tag_endpoint(transaction, env)
expect(transaction.action).to be_nil
end
end end
end end
require 'spec_helper' require 'spec_helper'
describe Gitlab::Redis do describe Gitlab::Redis do
let(:redis_config) { Rails.root.join('config', 'resque.yml').to_s } include StubENV
before(:each) { clear_raw_config } before(:each) { clear_raw_config }
after(:each) { clear_raw_config } after(:each) { clear_raw_config }
...@@ -72,6 +72,20 @@ describe Gitlab::Redis do ...@@ -72,6 +72,20 @@ describe Gitlab::Redis do
expect(url2).not_to end_with('foobar') expect(url2).not_to end_with('foobar')
end end
context 'when yml file with env variable' do
let(:redis_config) { Rails.root.join('spec/fixtures/config/redis_config_with_env.yml') }
before do
stub_env('TEST_GITLAB_REDIS_URL', 'redis://redishost:6379')
end
it 'reads redis url from env variable' do
stub_const("#{described_class}::CONFIG_FILE", redis_config)
expect(described_class.url).to eq 'redis://redishost:6379'
end
end
end end
describe '._raw_config' do describe '._raw_config' do
......
This diff is collapsed.
This diff is collapsed.
...@@ -243,4 +243,23 @@ describe CommitStatus, models: true do ...@@ -243,4 +243,23 @@ describe CommitStatus, models: true do
.to be_a Gitlab::Ci::Status::Success .to be_a Gitlab::Ci::Status::Success
end end
end end
describe '#sortable_name' do
tests = {
'karma' => ['karma'],
'karma 0 20' => ['karma ', 0, ' ', 20],
'karma 10 20' => ['karma ', 10, ' ', 20],
'karma 50:100' => ['karma ', 50, ':', 100],
'karma 1.10' => ['karma ', 1, '.', 10],
'karma 1.5.1' => ['karma ', 1, '.', 5, '.', 1],
'karma 1 a' => ['karma ', 1, ' a']
}
tests.each do |name, sortable_name|
it "'#{name}' sorts as '#{sortable_name}'" do
commit_status.name = name
expect(commit_status.sortable_name).to eq(sortable_name)
end
end
end
end end
require 'spec_helper' require 'spec_helper'
describe BambooService, models: true do describe BambooService, models: true, caching: true do
include ReactiveCachingHelpers
let(:bamboo_url) { 'http://gitlab.com/bamboo' }
subject(:service) do
described_class.create(
project: create(:empty_project),
properties: {
bamboo_url: bamboo_url,
username: 'mic',
password: 'password',
build_key: 'foo'
}
)
end
describe 'Associations' do describe 'Associations' do
it { is_expected.to belong_to :project } it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook } it { is_expected.to have_one :service_hook }
end end
describe 'Validations' do describe 'Validations' do
subject { service }
context 'when service is active' do context 'when service is active' do
before { subject.active = true } before { subject.active = true }
...@@ -103,90 +117,103 @@ describe BambooService, models: true do ...@@ -103,90 +117,103 @@ describe BambooService, models: true do
end end
describe '#build_page' do describe '#build_page' do
it 'returns the contents of the reactive cache' do
stub_reactive_cache(service, { build_page: 'foo' }, 'sha', 'ref')
expect(service.build_page('sha', 'ref')).to eq('foo')
end
end
describe '#commit_status' do
it 'returns the contents of the reactive cache' do
stub_reactive_cache(service, { commit_status: 'foo' }, 'sha', 'ref')
expect(service.commit_status('sha', 'ref')).to eq('foo')
end
end
describe '#calculate_reactive_cache' do
context '#build_page' do
subject { service.calculate_reactive_cache('123', 'unused')[:build_page] }
it 'returns a specific URL when status is 500' do it 'returns a specific URL when status is 500' do
stub_request(status: 500) stub_request(status: 500)
expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/bamboo/browse/foo') is_expected.to eq('http://gitlab.com/bamboo/browse/foo')
end end
it 'returns a specific URL when response has no results' do it 'returns a specific URL when response has no results' do
stub_request(body: %Q({"results":{"results":{"size":"0"}}})) stub_request(body: bamboo_response(size: 0))
expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/bamboo/browse/foo') is_expected.to eq('http://gitlab.com/bamboo/browse/foo')
end end
it 'returns a build URL when bamboo_url has no trailing slash' do it 'returns a build URL when bamboo_url has no trailing slash' do
stub_request(body: %Q({"results":{"results":{"result":{"planResultKey":{"key":"42"}}}}})) stub_request(body: bamboo_response)
expect(service(bamboo_url: 'http://gitlab.com/bamboo').build_page('123', 'unused')).to eq('http://gitlab.com/bamboo/browse/42') is_expected.to eq('http://gitlab.com/bamboo/browse/42')
end end
it 'returns a build URL when bamboo_url has a trailing slash' do context 'bamboo_url has trailing slash' do
stub_request(body: %Q({"results":{"results":{"result":{"planResultKey":{"key":"42"}}}}})) let(:bamboo_url) { 'http://gitlab.com/bamboo/' }
it 'returns a build URL' do
stub_request(body: bamboo_response)
expect(service(bamboo_url: 'http://gitlab.com/bamboo/').build_page('123', 'unused')).to eq('http://gitlab.com/bamboo/browse/42') is_expected.to eq('http://gitlab.com/bamboo/browse/42')
end
end end
end end
describe '#commit_status' do context '#commit_status' do
subject { service.calculate_reactive_cache('123', 'unused')[:commit_status] }
it 'sets commit status to :error when status is 500' do it 'sets commit status to :error when status is 500' do
stub_request(status: 500) stub_request(status: 500)
expect(service.commit_status('123', 'unused')).to eq(:error) is_expected.to eq(:error)
end end
it 'sets commit status to "pending" when status is 404' do it 'sets commit status to "pending" when status is 404' do
stub_request(status: 404) stub_request(status: 404)
expect(service.commit_status('123', 'unused')).to eq('pending') is_expected.to eq('pending')
end end
it 'sets commit status to "pending" when response has no results' do it 'sets commit status to "pending" when response has no results' do
stub_request(body: %Q({"results":{"results":{"size":"0"}}})) stub_request(body: %Q({"results":{"results":{"size":"0"}}}))
expect(service.commit_status('123', 'unused')).to eq('pending') is_expected.to eq('pending')
end end
it 'sets commit status to "success" when build state contains Success' do it 'sets commit status to "success" when build state contains Success' do
stub_request(build_state: 'YAY Success!') stub_request(body: bamboo_response(build_state: 'YAY Success!'))
expect(service.commit_status('123', 'unused')).to eq('success') is_expected.to eq('success')
end end
it 'sets commit status to "failed" when build state contains Failed' do it 'sets commit status to "failed" when build state contains Failed' do
stub_request(build_state: 'NO Failed!') stub_request(body: bamboo_response(build_state: 'NO Failed!'))
expect(service.commit_status('123', 'unused')).to eq('failed') is_expected.to eq('failed')
end end
it 'sets commit status to "pending" when build state contains Pending' do it 'sets commit status to "pending" when build state contains Pending' do
stub_request(build_state: 'NO Pending!') stub_request(body: bamboo_response(build_state: 'NO Pending!'))
expect(service.commit_status('123', 'unused')).to eq('pending') is_expected.to eq('pending')
end end
it 'sets commit status to :error when build state is unknown' do it 'sets commit status to :error when build state is unknown' do
stub_request(build_state: 'FOO BAR!') stub_request(body: bamboo_response(build_state: 'FOO BAR!'))
expect(service.commit_status('123', 'unused')).to eq(:error) is_expected.to eq(:error)
end end
end end
def service(bamboo_url: 'http://gitlab.com/bamboo')
described_class.create(
project: create(:empty_project),
properties: {
bamboo_url: bamboo_url,
username: 'mic',
password: 'password',
build_key: 'foo'
}
)
end end
def stub_request(status: 200, body: nil, build_state: 'success') def stub_request(status: 200, body: nil)
bamboo_full_url = 'http://mic:password@gitlab.com/bamboo/rest/api/latest/result?label=123&os_authType=basic' bamboo_full_url = 'http://mic:password@gitlab.com/bamboo/rest/api/latest/result?label=123&os_authType=basic'
body ||= %Q({"results":{"results":{"result":{"buildState":"#{build_state}"}}}})
WebMock.stub_request(:get, bamboo_full_url).to_return( WebMock.stub_request(:get, bamboo_full_url).to_return(
status: status, status: status,
...@@ -194,4 +221,8 @@ describe BambooService, models: true do ...@@ -194,4 +221,8 @@ describe BambooService, models: true do
body: body body: body
) )
end end
def bamboo_response(result_key: 42, build_state: 'success', size: 1)
%Q({"results":{"results":{"size":"#{size}","result":{"buildState":"#{build_state}","planResultKey":{"key":"#{result_key}"}}}}})
end
end end
require 'spec_helper' require 'spec_helper'
describe BuildkiteService, models: true do describe BuildkiteService, models: true, caching: true do
include ReactiveCachingHelpers
let(:project) { create(:empty_project) }
subject(:service) do
described_class.create(
project: project,
properties: {
service_hook: true,
project_url: 'https://buildkite.com/account-name/example-project',
token: 'secret-sauce-webhook-token:secret-sauce-status-token'
}
)
end
describe 'Associations' do describe 'Associations' do
it { is_expected.to belong_to :project } it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook } it { is_expected.to have_one :service_hook }
...@@ -25,21 +40,12 @@ describe BuildkiteService, models: true do ...@@ -25,21 +40,12 @@ describe BuildkiteService, models: true do
describe 'commits methods' do describe 'commits methods' do
before do before do
@project = Project.new allow(project).to receive(:default_branch).and_return('default-brancho')
allow(@project).to receive(:default_branch).and_return('default-brancho')
@service = BuildkiteService.new
allow(@service).to receive_messages(
project: @project,
service_hook: true,
project_url: 'https://buildkite.com/account-name/example-project',
token: 'secret-sauce-webhook-token:secret-sauce-status-token'
)
end end
describe '#webhook_url' do describe '#webhook_url' do
it 'returns the webhook url' do it 'returns the webhook url' do
expect(@service.webhook_url).to eq( expect(service.webhook_url).to eq(
'https://webhook.buildkite.com/deliver/secret-sauce-webhook-token' 'https://webhook.buildkite.com/deliver/secret-sauce-webhook-token'
) )
end end
...@@ -47,7 +53,7 @@ describe BuildkiteService, models: true do ...@@ -47,7 +53,7 @@ describe BuildkiteService, models: true do
describe '#commit_status_path' do describe '#commit_status_path' do
it 'returns the correct status page' do it 'returns the correct status page' do
expect(@service.commit_status_path('2ab7834c')).to eq( expect(service.commit_status_path('2ab7834c')).to eq(
'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=2ab7834c' 'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=2ab7834c'
) )
end end
...@@ -55,10 +61,53 @@ describe BuildkiteService, models: true do ...@@ -55,10 +61,53 @@ describe BuildkiteService, models: true do
describe '#build_page' do describe '#build_page' do
it 'returns the correct build page' do it 'returns the correct build page' do
expect(@service.build_page('2ab7834c', nil)).to eq( expect(service.build_page('2ab7834c', nil)).to eq(
'https://buildkite.com/account-name/example-project/builds?commit=2ab7834c' 'https://buildkite.com/account-name/example-project/builds?commit=2ab7834c'
) )
end end
end end
describe '#commit_status' do
it 'returns the contents of the reactive cache' do
stub_reactive_cache(service, { commit_status: 'foo' }, 'sha', 'ref')
expect(service.commit_status('sha', 'ref')).to eq('foo')
end
end
describe '#calculate_reactive_cache' do
context '#commit_status' do
subject { service.calculate_reactive_cache('123', 'unused')[:commit_status] }
it 'sets commit status to :error when status is 500' do
stub_request(status: 500)
is_expected.to eq(:error)
end
it 'sets commit status to :error when status is 404' do
stub_request(status: 404)
is_expected.to eq(:error)
end
it 'passes through build status untouched when status is 200' do
stub_request(body: %Q({"status":"Great Success"}))
is_expected.to eq('Great Success')
end
end
end
end
def stub_request(status: 200, body: nil)
body ||= %Q({"status":"success"})
buildkite_full_url = 'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=123'
WebMock.stub_request(:get, buildkite_full_url).to_return(
status: status,
headers: { 'Content-Type' => 'application/json' },
body: body
)
end end
end end
require 'spec_helper' require 'spec_helper'
describe DroneCiService, models: true do describe DroneCiService, models: true, caching: true do
include ReactiveCachingHelpers
describe 'associations' do describe 'associations' do
it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:project) }
it { is_expected.to have_one(:service_hook) } it { is_expected.to have_one(:service_hook) }
...@@ -33,6 +35,10 @@ describe DroneCiService, models: true do ...@@ -33,6 +35,10 @@ describe DroneCiService, models: true do
let(:token) { 'secret' } let(:token) { 'secret' }
let(:iid) { rand(1..9999) } let(:iid) { rand(1..9999) }
# URL's
let(:build_page) { "#{drone_url}/gitlab/#{path}/redirect/commits/#{sha}?branch=#{branch}" }
let(:commit_status_path) { "#{drone_url}/gitlab/#{path}/commits/#{sha}?branch=#{branch}&access_token=#{token}" }
before(:each) do before(:each) do
allow(drone).to receive_messages( allow(drone).to receive_messages(
project_id: project.id, project_id: project.id,
...@@ -42,22 +48,66 @@ describe DroneCiService, models: true do ...@@ -42,22 +48,66 @@ describe DroneCiService, models: true do
token: token token: token
) )
end end
def stub_request(status: 200, body: nil)
body ||= %Q({"status":"success"})
WebMock.stub_request(:get, commit_status_path).to_return(
status: status,
headers: { 'Content-Type' => 'application/json' },
body: body
)
end
end end
describe "service page/path methods" do describe "service page/path methods" do
include_context :drone_ci_service include_context :drone_ci_service
# URL's it { expect(drone.build_page(sha, branch)).to eq(build_page) }
let(:commit_page) { "#{drone_url}/gitlab/#{path}/redirect/commits/#{sha}?branch=#{branch}" }
let(:merge_request_page) { "#{drone_url}/gitlab/#{path}/redirect/pulls/#{iid}" }
let(:commit_status_path) { "#{drone_url}/gitlab/#{path}/commits/#{sha}?branch=#{branch}&access_token=#{token}" }
let(:merge_request_status_path) { "#{drone_url}/gitlab/#{path}/pulls/#{iid}?access_token=#{token}" }
it { expect(drone.build_page(sha, branch)).to eq(commit_page) }
it { expect(drone.commit_page(sha, branch)).to eq(commit_page) }
it { expect(drone.merge_request_page(iid, sha, branch)).to eq(merge_request_page) }
it { expect(drone.commit_status_path(sha, branch)).to eq(commit_status_path) } it { expect(drone.commit_status_path(sha, branch)).to eq(commit_status_path) }
it { expect(drone.merge_request_status_path(iid, sha, branch)).to eq(merge_request_status_path) } end
describe '#commit_status' do
include_context :drone_ci_service
it 'returns the contents of the reactive cache' do
stub_reactive_cache(drone, { commit_status: 'foo' }, 'sha', 'ref')
expect(drone.commit_status('sha', 'ref')).to eq('foo')
end
end
describe '#calculate_reactive_cache' do
include_context :drone_ci_service
context '#commit_status' do
subject { drone.calculate_reactive_cache(sha, branch)[:commit_status] }
it 'sets commit status to :error when status is 500' do
stub_request(status: 500)
is_expected.to eq(:error)
end
it 'sets commit status to :error when status is 404' do
stub_request(status: 404)
is_expected.to eq(:error)
end
{ "killed" => :canceled,
"failure" => :failed,
"error" => :failed,
"success" => "success",
}.each do |drone_status, our_status|
it "sets commit status to #{our_status.inspect} when returned status is #{drone_status.inspect}" do
stub_request(body: %Q({"status":"#{drone_status}"}))
is_expected.to eq(our_status)
end
end
end
end end
describe "execute" do describe "execute" do
......
This diff is collapsed.
...@@ -33,10 +33,12 @@ describe CommitEntity do ...@@ -33,10 +33,12 @@ describe CommitEntity do
it 'contains path to commit' do it 'contains path to commit' do
expect(subject).to include(:commit_path) expect(subject).to include(:commit_path)
expect(subject[:commit_path]).to include "commit/#{commit.id}"
end end
it 'contains URL to commit' do it 'contains URL to commit' do
expect(subject).to include(:commit_url) expect(subject).to include(:commit_url)
expect(subject[:commit_path]).to include "commit/#{commit.id}"
end end
it 'needs to receive project in the request' do it 'needs to receive project in the request' do
......
...@@ -3,31 +3,35 @@ module ReactiveCachingHelpers ...@@ -3,31 +3,35 @@ module ReactiveCachingHelpers
([subject.class.reactive_cache_key.call(subject)].flatten + qualifiers).join(':') ([subject.class.reactive_cache_key.call(subject)].flatten + qualifiers).join(':')
end end
def stub_reactive_cache(subject = nil, data = nil) def alive_reactive_cache_key(subject, *qualifiers)
reactive_cache_key(subject, *(qualifiers + ['alive']))
end
def stub_reactive_cache(subject = nil, data = nil, *qualifiers)
allow(ReactiveCachingWorker).to receive(:perform_async) allow(ReactiveCachingWorker).to receive(:perform_async)
allow(ReactiveCachingWorker).to receive(:perform_in) allow(ReactiveCachingWorker).to receive(:perform_in)
write_reactive_cache(subject, data) if data write_reactive_cache(subject, data, *qualifiers) if data
end end
def read_reactive_cache(subject) def read_reactive_cache(subject, *qualifiers)
Rails.cache.read(reactive_cache_key(subject)) Rails.cache.read(reactive_cache_key(subject, *qualifiers))
end end
def write_reactive_cache(subject, data) def write_reactive_cache(subject, data, *qualifiers)
start_reactive_cache_lifetime(subject) start_reactive_cache_lifetime(subject, *qualifiers)
Rails.cache.write(reactive_cache_key(subject), data) Rails.cache.write(reactive_cache_key(subject, *qualifiers), data)
end end
def reactive_cache_alive?(subject) def reactive_cache_alive?(subject, *qualifiers)
Rails.cache.read(reactive_cache_key(subject, 'alive')) Rails.cache.read(alive_reactive_cache_key(subject, *qualifiers))
end end
def invalidate_reactive_cache(subject) def invalidate_reactive_cache(subject, *qualifiers)
Rails.cache.delete(reactive_cache_key(subject, 'alive')) Rails.cache.delete(alive_reactive_cache_key(subject, *qualifiers))
end end
def start_reactive_cache_lifetime(subject) def start_reactive_cache_lifetime(subject, *qualifiers)
Rails.cache.write(reactive_cache_key(subject, 'alive'), true) Rails.cache.write(alive_reactive_cache_key(subject, *qualifiers), true)
end end
def expect_reactive_cache_update_queued(subject) def expect_reactive_cache_update_queued(subject)
......
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