Commit 1b88d6f8 authored by Valery Sizov's avatar Valery Sizov

Merge branch 'sync-88-to-master' into 'master'

Sync 8-8-stable-ee to master

I solved some merge conflicts that arose when merging CE 8-8-stable (which contains the latest master branch) into 8-8-stable-ee.

See merge request !398
parents 97323faa aeae2f12
...@@ -770,7 +770,7 @@ Lint/DefEndAlignment: ...@@ -770,7 +770,7 @@ Lint/DefEndAlignment:
# Check for deprecated class method calls. # Check for deprecated class method calls.
Lint/DeprecatedClassMethods: Lint/DeprecatedClassMethods:
Enabled: false Enabled: true
# Check for duplicate method definitions. # Check for duplicate method definitions.
Lint/DuplicateMethods: Lint/DuplicateMethods:
......
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.8.0 (unreleased) v 8.8.0 (unreleased)
- Snippets tab under user profile. !4001 (Long Nguyen)
- Fix error when using link to uploads in global snippets - Fix error when using link to uploads in global snippets
- Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen) - Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen)
- Use a case-insensitive comparison in sanitizing URI schemes - Use a case-insensitive comparison in sanitizing URI schemes
...@@ -13,6 +14,7 @@ v 8.8.0 (unreleased) ...@@ -13,6 +14,7 @@ v 8.8.0 (unreleased)
- Reduce delay in destroying a project from 1-minute to immediately - Reduce delay in destroying a project from 1-minute to immediately
- Make build status canceled if any of the jobs was canceled and none failed - Make build status canceled if any of the jobs was canceled and none failed
- Upgrade Sidekiq to 4.1.2 - Upgrade Sidekiq to 4.1.2
- Added /health_check endpoint for checking service status
- Sanitize repo paths in new project error message - Sanitize repo paths in new project error message
- Bump mail_room to 0.7.0 to fix stuck IDLE connections - Bump mail_room to 0.7.0 to fix stuck IDLE connections
- Remove future dates from contribution calendar graph. - Remove future dates from contribution calendar graph.
...@@ -21,6 +23,7 @@ v 8.8.0 (unreleased) ...@@ -21,6 +23,7 @@ v 8.8.0 (unreleased)
- Fix error when visiting commit builds page before build was updated - Fix error when visiting commit builds page before build was updated
- Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project - Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project
- Update SVG sanitizer to conform to SVG 1.1 - Update SVG sanitizer to conform to SVG 1.1
- Speed up push emails with multiple recipients by only generating the email once
- Updated search UI - Updated search UI
- Display informative message when new milestone is created - Display informative message when new milestone is created
- Sanitize milestones and labels titles - Sanitize milestones and labels titles
...@@ -30,10 +33,12 @@ v 8.8.0 (unreleased) ...@@ -30,10 +33,12 @@ v 8.8.0 (unreleased)
- Backport GitHub Enterprise import support from EE - Backport GitHub Enterprise import support from EE
- Create tags using Rugged for performance reasons. !3745 - Create tags using Rugged for performance reasons. !3745
- API: Expose Issue#user_notes_count. !3126 (Anton Popov) - API: Expose Issue#user_notes_count. !3126 (Anton Popov)
- Don't show forks button when user can't view forks
- Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718 - Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718
- Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes) - Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes)
- Add eager load paths to help prevent dependency load issues in Sidekiq workers. !3724 - Add eager load paths to help prevent dependency load issues in Sidekiq workers. !3724
- Added multiple colors for labels in dropdowns when dups happen. - Added multiple colors for labels in dropdowns when dups happen.
- Always group commits by server timezone, not commit timestamp
- Improve description for the Two-factor Authentication sign-in screen. (Connor Shea) - Improve description for the Two-factor Authentication sign-in screen. (Connor Shea)
- API support for the 'since' and 'until' operators on commit requests (Paco Guzman) - API support for the 'since' and 'until' operators on commit requests (Paco Guzman)
- Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko) - Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko)
...@@ -41,6 +46,15 @@ v 8.8.0 (unreleased) ...@@ -41,6 +46,15 @@ v 8.8.0 (unreleased)
- Fix unintentional filtering bug in issues sorted by milestone due (Takuya Noguchi) - Fix unintentional filtering bug in issues sorted by milestone due (Takuya Noguchi)
- Fix adding a todo for private group members (Ahmad Sherif) - Fix adding a todo for private group members (Ahmad Sherif)
- Bump ace-rails-ap gem version from 2.0.1 to 4.0.2 which upgrades Ace Editor from 1.1.2 to 1.2.3 - Bump ace-rails-ap gem version from 2.0.1 to 4.0.2 which upgrades Ace Editor from 1.1.2 to 1.2.3
- Total method execution timings are no longer tracked
- Allow Admins to remove the Login with buttons for OAuth services and still be able to import !4034. (Andrei Gliga)
- Add API endpoints for un/subscribing from/to a label. !4051 (Ahmad Sherif)
v 8.7.5
- Fix relative links in wiki pages. !4050
- Fix always showing build notification message when switching between merge requests !4086
- Fix an issue when filtering merge requests with more than one label. !3886
- Fix short note for the default scope on build page (Takuya Noguchi)
v 8.7.4 v 8.7.4
- Links for Redmine issue references are generated correctly again !4048 (Benedikt Huss) - Links for Redmine issue references are generated correctly again !4048 (Benedikt Huss)
......
...@@ -342,3 +342,6 @@ gem 'oauth2', '~> 1.0.0' ...@@ -342,3 +342,6 @@ gem 'oauth2', '~> 1.0.0'
# Soft deletion # Soft deletion
gem "paranoia", "~> 2.0" gem "paranoia", "~> 2.0"
# Health check
gem 'health_check', '~> 1.5.1'
...@@ -426,6 +426,8 @@ GEM ...@@ -426,6 +426,8 @@ GEM
html2haml (>= 1.0.1) html2haml (>= 1.0.1)
railties (>= 4.0.1) railties (>= 4.0.1)
hashie (3.4.3) hashie (3.4.3)
health_check (1.5.1)
rails (>= 2.3.0)
highline (1.7.8) highline (1.7.8)
hipchat (1.5.2) hipchat (1.5.2)
httparty httparty
...@@ -977,6 +979,7 @@ DEPENDENCIES ...@@ -977,6 +979,7 @@ DEPENDENCIES
grape-entity (~> 0.4.2) grape-entity (~> 0.4.2)
gssapi gssapi
haml-rails (~> 0.9.0) haml-rails (~> 0.9.0)
health_check (~> 1.5.1)
hipchat (~> 1.5.0) hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0) html-pipeline (~> 1.11.0)
httparty (~> 0.13.3) httparty (~> 0.13.3)
......
class CiBuild class CiBuild
@interval: null @interval: null
@state: null
constructor: (build_url, build_status) -> constructor: (build_url, build_status, build_state) ->
clearInterval(CiBuild.interval) clearInterval(CiBuild.interval)
@state = build_state
@initScrollButtonAffix() @initScrollButtonAffix()
if build_status == "running" || build_status == "pending" if build_status == "running" || build_status == "pending"
...@@ -26,14 +29,18 @@ class CiBuild ...@@ -26,14 +29,18 @@ class CiBuild
CiBuild.interval = setInterval => CiBuild.interval = setInterval =>
if window.location.href.split("#").first() is build_url if window.location.href.split("#").first() is build_url
$.ajax $.ajax
url: build_url url: build_url + "/trace.json?state=" + encodeURIComponent(@state)
dataType: "json" dataType: "json"
success: (build) => success: (log) =>
if build.status == "running" @state = log.state
$('#build-trace code').html build.trace_html if log.status is "running"
if log.append
$('.fa-refresh').before log.html
else
$('#build-trace code').html log.html
$('#build-trace code').append '<i class="fa fa-refresh fa-spin"/>' $('#build-trace code').append '<i class="fa fa-refresh fa-spin"/>'
@checkAutoscroll() @checkAutoscroll()
else if build.status != build_status else if log.status isnt build_status
Turbolinks.visit build_url Turbolinks.visit build_url
, 4000 , 4000
......
...@@ -26,6 +26,10 @@ ...@@ -26,6 +26,10 @@
# Personal projects # Personal projects
# </a> # </a>
# </li> # </li>
# <li class="snippets-tab">
# <a data-action="snippets" data-target="#snippets" data-toggle="tab" href="/u/username/snippets">
# </a>
# </li>
# </ul> # </ul>
# #
# <div class="tab-content"> # <div class="tab-content">
...@@ -41,6 +45,9 @@ ...@@ -41,6 +45,9 @@
# <div class="tab-pane" id="projects"> # <div class="tab-pane" id="projects">
# Projects content # Projects content
# </div> # </div>
# <div class="tab-pane" id="snippets">
# Snippets content
# </div>
# </div> # </div>
# #
# <div class="loading-status"> # <div class="loading-status">
...@@ -100,7 +107,7 @@ class @UserTabs ...@@ -100,7 +107,7 @@ class @UserTabs
if action is 'activity' if action is 'activity'
@loadActivities(source) @loadActivities(source)
if action in ['groups', 'contributed', 'projects'] if action in ['groups', 'contributed', 'projects', 'snippets']
@loadTab(source, action) @loadTab(source, action)
loadTab: (source, action) -> loadTab: (source, action) ->
......
...@@ -19,6 +19,12 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -19,6 +19,12 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
redirect_to admin_runners_path redirect_to admin_runners_path
end end
def reset_health_check_token
@application_setting.reset_health_check_access_token!
flash[:notice] = 'New health check access token has been generated!'
redirect_to :back
end
def clear_repository_check_states def clear_repository_check_states
RepositoryCheck::ClearWorker.perform_async RepositoryCheck::ClearWorker.perform_async
...@@ -53,6 +59,12 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -53,6 +59,12 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end end
end end
enabled_oauth_sign_in_sources = params[:application_setting].delete(:enabled_oauth_sign_in_sources)
params[:application_setting][:disabled_oauth_sign_in_sources] =
AuthHelper.button_based_providers.map(&:to_s) -
Array(enabled_oauth_sign_in_sources)
params.require(:application_setting).permit( params.require(:application_setting).permit(
:default_projects_limit, :default_projects_limit,
:default_branch_protection, :default_branch_protection,
...@@ -97,7 +109,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -97,7 +109,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:repository_checks_enabled, :repository_checks_enabled,
:metrics_packet_size, :metrics_packet_size,
restricted_visibility_levels: [], restricted_visibility_levels: [],
import_sources: [] import_sources: [],
disabled_oauth_sign_in_sources: []
) )
end end
end end
class Admin::HealthCheckController < Admin::ApplicationController
def show
@errors = HealthCheck::Utils.process_checks('standard')
end
end
...@@ -58,6 +58,6 @@ class Admin::RunnersController < Admin::ApplicationController ...@@ -58,6 +58,6 @@ class Admin::RunnersController < Admin::ApplicationController
end end
def runner_params def runner_params
params.require(:runner).permit(:token, :description, :tag_list, :active) params.require(:runner).permit(Ci::Runner::FORM_EDITABLE)
end end
end end
...@@ -28,7 +28,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -28,7 +28,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
end end
def starred def starred
@projects = current_user.starred_projects.sorted_by_activity @projects = current_user.viewable_starred_projects.sorted_by_activity
@projects = filter_projects(@projects) @projects = filter_projects(@projects)
@projects = @projects.includes(:namespace, :forked_from_project, :tags) @projects = @projects.includes(:namespace, :forked_from_project, :tags)
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
......
...@@ -25,7 +25,7 @@ class DashboardController < Dashboard::ApplicationController ...@@ -25,7 +25,7 @@ class DashboardController < Dashboard::ApplicationController
def load_events def load_events
projects = projects =
if params[:filter] == "starred" if params[:filter] == "starred"
current_user.starred_projects current_user.viewable_starred_projects
else else
current_user.authorized_projects current_user.authorized_projects
end end
......
class HealthCheckController < HealthCheck::HealthCheckController
before_action :validate_health_check_access!
private
def validate_health_check_access!
render_404 unless token_valid?
end
def token_valid?
token = params[:token].presence || request.headers['TOKEN']
token.present? &&
ActiveSupport::SecurityUtils.variable_size_secure_compare(
token,
current_application_settings.health_check_access_token
)
end
def render_404
render file: Rails.root.join('public', '404'), layout: false, status: '404'
end
end
...@@ -38,6 +38,14 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -38,6 +38,14 @@ class Projects::BuildsController < Projects::ApplicationController
end end
end end
def trace
respond_to do |format|
format.json do
render json: @build.trace_with_state(params[:state]).merge!(id: @build.id, status: @build.status)
end
end
end
def retry def retry
unless @build.retryable? unless @build.retryable?
return render_404 return render_404
......
...@@ -27,8 +27,10 @@ class Projects::HooksController < Projects::ApplicationController ...@@ -27,8 +27,10 @@ class Projects::HooksController < Projects::ApplicationController
if !@project.empty_repo? if !@project.empty_repo?
status, message = TestHookService.new.execute(hook, current_user) status, message = TestHookService.new.execute(hook, current_user)
if status if status && status >= 200 && status < 400
flash[:notice] = 'Hook successfully executed.' flash[:notice] = "Hook executed successfully: HTTP #{status}"
elsif status
flash[:alert] = "Hook executed successfully but returned HTTP #{status} #{message}"
else else
flash[:alert] = "Hook execution failed: #{message}" flash[:alert] = "Hook execution failed: #{message}"
end end
......
...@@ -64,6 +64,6 @@ class Projects::RunnersController < Projects::ApplicationController ...@@ -64,6 +64,6 @@ class Projects::RunnersController < Projects::ApplicationController
end end
def runner_params def runner_params
params.require(:runner).permit(:description, :tag_list, :active) params.require(:runner).permit(Ci::Runner::FORM_EDITABLE)
end end
end end
...@@ -10,7 +10,7 @@ class SnippetsController < ApplicationController ...@@ -10,7 +10,7 @@ class SnippetsController < ApplicationController
# Allow destroy snippet # Allow destroy snippet
before_action :authorize_admin_snippet!, only: [:destroy] before_action :authorize_admin_snippet!, only: [:destroy]
skip_before_action :authenticate_user!, only: [:index, :user_index, :show, :raw] skip_before_action :authenticate_user!, only: [:index, :show, :raw]
layout 'snippets' layout 'snippets'
respond_to :html respond_to :html
......
...@@ -58,6 +58,19 @@ class UsersController < ApplicationController ...@@ -58,6 +58,19 @@ class UsersController < ApplicationController
end end
end end
def snippets
load_snippets
respond_to do |format|
format.html { render 'show' }
format.json do
render json: {
html: view_to_html_string("snippets/_snippets", collection: @snippets)
}
end
end
end
def calendar def calendar
calendar = contributions_calendar calendar = contributions_calendar
@timestamps = calendar.timestamps @timestamps = calendar.timestamps
...@@ -116,6 +129,15 @@ class UsersController < ApplicationController ...@@ -116,6 +129,15 @@ class UsersController < ApplicationController
@groups = JoinedGroupsFinder.new(user).execute(current_user) @groups = JoinedGroupsFinder.new(user).execute(current_user)
end end
def load_snippets
@snippets = SnippetsFinder.new.execute(
current_user,
filter: :by_user,
user: user,
scope: params[:scope]
).page(params[:page])
end
def projects_for_current_user def projects_for_current_user
ProjectsFinder.new.execute(current_user) ProjectsFinder.new.execute(current_user)
end end
......
...@@ -64,4 +64,18 @@ module ApplicationSettingsHelper ...@@ -64,4 +64,18 @@ module ApplicationSettingsHelper
end end
end end
end end
def oauth_providers_checkboxes
button_based_providers.map do |source|
disabled = current_application_settings.disabled_oauth_sign_in_sources.include?(source.to_s)
css_class = 'btn'
css_class << ' active' unless disabled
checkbox_name = 'application_setting[enabled_oauth_sign_in_sources][]'
label_tag(checkbox_name, class: css_class) do
check_box_tag(checkbox_name, source, !disabled,
autocomplete: 'off') + Gitlab::OAuth::Provider.label_for(source)
end
end
end
end end
...@@ -42,6 +42,16 @@ module AuthHelper ...@@ -42,6 +42,16 @@ module AuthHelper
auth_providers.reject { |provider| form_based_provider?(provider) } auth_providers.reject { |provider| form_based_provider?(provider) }
end end
def enabled_button_based_providers
disabled_providers = current_application_settings.disabled_oauth_sign_in_sources || []
button_based_providers.map(&:to_s) - disabled_providers
end
def button_based_providers_enabled?
enabled_button_based_providers.any?
end
def provider_image_tag(provider, size = 64) def provider_image_tag(provider, size = 64)
label = label_for_provider(provider) label = label_for_provider(provider)
......
...@@ -39,15 +39,6 @@ module EventsHelper ...@@ -39,15 +39,6 @@ module EventsHelper
end end
end end
def icon_for_event
{
EventFilter.push => 'upload',
EventFilter.merged => 'check-square-o',
EventFilter.comments => 'comments',
EventFilter.team => 'user',
}
end
def event_preposition(event) def event_preposition(event)
if event.push? || event.commented? || event.target if event.push? || event.commented? || event.target
"at" "at"
......
...@@ -138,10 +138,10 @@ module ProjectsHelper ...@@ -138,10 +138,10 @@ module ProjectsHelper
private private
def get_project_nav_tabs(project, current_user) def get_project_nav_tabs(project, current_user)
nav_tabs = [:home, :forks] nav_tabs = [:home]
if !project.empty_repo? && can?(current_user, :download_code, project) if !project.empty_repo? && can?(current_user, :download_code, project)
nav_tabs << [:files, :commits, :network, :graphs] nav_tabs << [:files, :commits, :network, :graphs, :forks]
end end
if project.repo_exists? && can?(current_user, :read_merge_request, project) if project.repo_exists? && can?(current_user, :read_merge_request, project)
......
...@@ -59,9 +59,9 @@ module Emails ...@@ -59,9 +59,9 @@ module Emails
subject: subject("Project was moved")) subject: subject("Project was moved"))
end end
def repository_push_email(project_id, recipient, opts = {}) def repository_push_email(project_id, opts = {})
@message = @message =
Gitlab::Email::Message::RepositoryPush.new(self, project_id, recipient, opts) Gitlab::Email::Message::RepositoryPush.new(self, project_id, opts)
# used in notify layout # used in notify layout
@target_url = @message.target_url @target_url = @message.target_url
...@@ -72,7 +72,6 @@ module Emails ...@@ -72,7 +72,6 @@ module Emails
mail(from: sender(@message.author_id, @message.send_from_committer_email?), mail(from: sender(@message.author_id, @message.send_from_committer_email?),
reply_to: @message.reply_to, reply_to: @message.reply_to,
to: @message.recipient,
subject: @message.subject) subject: @message.subject)
end end
end end
......
class ApplicationSetting < ActiveRecord::Base class ApplicationSetting < ActiveRecord::Base
include TokenAuthenticatable include TokenAuthenticatable
add_authentication_token_field :runners_registration_token add_authentication_token_field :runners_registration_token
add_authentication_token_field :health_check_access_token
CACHE_KEY = 'application_setting.last' CACHE_KEY = 'application_setting.last'
serialize :restricted_visibility_levels serialize :restricted_visibility_levels
serialize :import_sources serialize :import_sources
serialize :disabled_oauth_sign_in_sources
serialize :restricted_signup_domains, Array serialize :restricted_signup_domains, Array
attr_accessor :restricted_signup_domains_raw attr_accessor :restricted_signup_domains_raw
...@@ -69,7 +71,18 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -69,7 +71,18 @@ class ApplicationSetting < ActiveRecord::Base
end end
end end
validates_each :disabled_oauth_sign_in_sources do |record, attr, value|
unless value.nil?
value.each do |source|
unless Devise.omniauth_providers.include?(source.to_sym)
record.errors.add(attr, "'#{source}' is not an OAuth sign-in source")
end
end
end
end
before_save :ensure_runners_registration_token before_save :ensure_runners_registration_token
before_save :ensure_health_check_access_token
after_commit do after_commit do
Rails.cache.write(CACHE_KEY, self) Rails.cache.write(CACHE_KEY, self)
...@@ -107,6 +120,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -107,6 +120,7 @@ class ApplicationSetting < ActiveRecord::Base
recaptcha_enabled: false, recaptcha_enabled: false,
akismet_enabled: false, akismet_enabled: false,
repository_checks_enabled: true, repository_checks_enabled: true,
disabled_oauth_sign_in_sources: []
) )
end end
...@@ -133,4 +147,8 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -133,4 +147,8 @@ class ApplicationSetting < ActiveRecord::Base
def runners_registration_token def runners_registration_token
ensure_runners_registration_token! ensure_runners_registration_token!
end end
def health_check_access_token
ensure_health_check_access_token!
end
end end
...@@ -95,8 +95,12 @@ module Ci ...@@ -95,8 +95,12 @@ module Ci
end end
def trace_html def trace_html
html = Ci::Ansi2html::convert(trace) if trace.present? trace_with_state[:html] || ''
html || '' end
def trace_with_state(state = nil)
trace_with_state = Ci::Ansi2html::convert(trace, state) if trace.present?
trace_with_state || {}
end end
def timeout def timeout
...@@ -201,7 +205,7 @@ module Ci ...@@ -201,7 +205,7 @@ module Ci
end end
def recreate_trace_dir def recreate_trace_dir
unless Dir.exists?(dir_to_trace) unless Dir.exist?(dir_to_trace)
FileUtils.mkdir_p(dir_to_trace) FileUtils.mkdir_p(dir_to_trace)
end end
end end
......
...@@ -3,7 +3,8 @@ module Ci ...@@ -3,7 +3,8 @@ module Ci
extend Ci::Model extend Ci::Model
LAST_CONTACT_TIME = 5.minutes.ago LAST_CONTACT_TIME = 5.minutes.ago
AVAILABLE_SCOPES = ['specific', 'shared', 'active', 'paused', 'online'] AVAILABLE_SCOPES = %w[specific shared active paused online]
FORM_EDITABLE = %i[description tag_list active]
has_many :builds, class_name: 'Ci::Build' has_many :builds, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
......
...@@ -36,6 +36,12 @@ module Subscribable ...@@ -36,6 +36,12 @@ module Subscribable
update(subscribed: !subscribed?(user)) update(subscribed: !subscribed?(user))
end end
def subscribe(user)
subscriptions.
find_or_initialize_by(user_id: user.id).
update(subscribed: true)
end
def unsubscribe(user) def unsubscribe(user)
subscriptions. subscriptions.
find_or_initialize_by(user_id: user.id). find_or_initialize_by(user_id: user.id).
......
...@@ -38,7 +38,7 @@ class WebHook < ActiveRecord::Base ...@@ -38,7 +38,7 @@ class WebHook < ActiveRecord::Base
basic_auth: auth) basic_auth: auth)
end end
[(response.code >= 200 && response.code < 300), ActionView::Base.full_sanitizer.sanitize(response.to_s)] [response.code, response.to_s]
rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
logger.error("WebHook Error => #{e}") logger.error("WebHook Error => #{e}")
[false, e.to_s] [false, e.to_s]
......
...@@ -570,7 +570,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -570,7 +570,7 @@ class MergeRequest < ActiveRecord::Base
end end
def ref_is_fetched? def ref_is_fetched?
File.exists?(File.join(project.repository.path_to_repo, ref_path)) File.exist?(File.join(project.repository.path_to_repo, ref_path))
end end
def ensure_ref_fetched def ensure_ref_fetched
......
...@@ -413,6 +413,11 @@ class User < ActiveRecord::Base ...@@ -413,6 +413,11 @@ class User < ActiveRecord::Base
Project.where("projects.id IN (#{projects_union.to_sql})") Project.where("projects.id IN (#{projects_union.to_sql})")
end end
def viewable_starred_projects
starred_projects.where("projects.visibility_level IN (?) OR projects.id IN (#{projects_union.to_sql})",
[Project::PUBLIC, Project::INTERNAL])
end
def owned_projects def owned_projects
@owned_projects ||= @owned_projects ||=
Project.where('namespace_id IN (?) OR namespace_id = ?', Project.where('namespace_id IN (?) OR namespace_id = ?',
......
...@@ -25,7 +25,7 @@ module Projects ...@@ -25,7 +25,7 @@ module Projects
trash_repositories! trash_repositories!
end end
log_info("Project \"#{project.name}\" was removed") log_info("Project \"#{project.path_with_namespace}\" was removed")
system_hook_service.execute_hooks_for(project, :destroy) system_hook_service.execute_hooks_for(project, :destroy)
true true
end end
......
...@@ -25,7 +25,7 @@ module Projects ...@@ -25,7 +25,7 @@ module Projects
# Check if we did extract public directory # Check if we did extract public directory
archive_public_path = File.join(archive_path, 'public') archive_public_path = File.join(archive_path, 'public')
raise 'pages miss the public folder' unless Dir.exists?(archive_public_path) raise 'pages miss the public folder' unless Dir.exist?(archive_public_path)
raise 'pages are outdated' unless latest? raise 'pages are outdated' unless latest?
deploy_page!(archive_public_path) deploy_page!(archive_public_path)
......
...@@ -109,6 +109,13 @@ ...@@ -109,6 +109,13 @@
= f.label :signin_enabled do = f.label :signin_enabled do
= f.check_box :signin_enabled = f.check_box :signin_enabled
Sign-in enabled Sign-in enabled
- if omniauth_enabled? && button_based_providers.any?
.form-group
= f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth Sign-In sources', class: 'control-label col-sm-2'
.col-sm-10
.btn-group{ data: { toggle: 'buttons' } }
- oauth_providers_checkboxes.each do |source|
= source
.form-group .form-group
= f.label :two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2' = f.label :two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
= link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post = link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
.row-content-block.second-block .row-content-block.second-block
#{(@scope || 'running').capitalize} builds #{(@scope || 'all').capitalize} builds
%ul.content-list %ul.content-list
- if @builds.blank? - if @builds.blank?
......
- page_title "Health Check"
%h3.page-title
Health Check
.bs-callout.clearfix
.pull-left
%p
Access token is
%code#health-check-token= current_application_settings.health_check_access_token
= button_to reset_health_check_token_admin_application_settings_path,
method: :put, class: 'btn btn-default',
data: { confirm: 'Are you sure you want to reset the health check token?' } do
= icon('refresh')
Reset health check access token
%p.light
Health information can be retrieved as plain text, JSON, or XML using:
%ul
%li
%code= health_check_url(token: current_application_settings.health_check_access_token)
%li
%code= health_check_url(token: current_application_settings.health_check_access_token, format: :json)
%li
%code= health_check_url(token: current_application_settings.health_check_access_token, format: :xml)
%p.light
You can also ask for the status of specific services:
%ul
%li
%code= health_check_url(token: current_application_settings.health_check_access_token, checks: :cache)
%li
%code= health_check_url(token: current_application_settings.health_check_access_token, checks: :database)
%li
%code= health_check_url(token: current_application_settings.health_check_access_token, checks: :migrations)
%hr
.panel.panel-default
.panel-heading
Current Status:
- if @errors.blank?
= icon('circle', class: 'cgreen')
Healthy
- else
= icon('warning', class: 'cred')
Unhealthy
.panel-body
- if @errors.blank?
No Health Problems Detected
- else
= @errors
...@@ -22,25 +22,9 @@ ...@@ -22,25 +22,9 @@
%h4 This runner will process builds only from ASSIGNED projects %h4 This runner will process builds only from ASSIGNED projects
%p You can't make this a shared runner. %p You can't make this a shared runner.
%hr %hr
= form_for @runner, url: admin_runner_path(@runner), html: { class: 'form-horizontal' } do |f|
.form-group .append-bottom-20
= label_tag :token, class: 'control-label' do = render '/projects/runners/form', runner: @runner, runner_form_url: admin_runner_path(@runner)
Token
.col-sm-10
= f.text_field :token, class: 'form-control', readonly: true
.form-group
= label_tag :description, class: 'control-label' do
Description
.col-sm-10
= f.text_field :description, class: 'form-control'
.form-group
= label_tag :tag_list, class: 'control-label' do
Tags
.col-sm-10
= f.text_field :tag_list, value: @runner.tag_list.to_s, class: 'form-control'
.help-block You can setup builds to only use runners with specific tags
.form-actions
= f.submit 'Save', class: 'btn btn-save'
.row .row
.col-md-6 .col-md-6
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
= render 'devise/shared/signin_box' = render 'devise/shared/signin_box'
-# Omniauth fits between signin/ldap signin and signup and does not have a surrounding box -# Omniauth fits between signin/ldap signin and signup and does not have a surrounding box
- if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers.any? - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled?
.clearfix.prepend-top-20 .clearfix.prepend-top-20
= render 'devise/shared/omniauth_box' = render 'devise/shared/omniauth_box'
......
%p %p
%span.light %span.light
Sign in with &nbsp; Sign in with &nbsp;
- providers = button_based_providers - providers = enabled_button_based_providers
- providers.each do |provider| - providers.each do |provider|
%span.light %span.light
- has_icon = provider_has_icon?(provider) - has_icon = provider_has_icon?(provider)
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
%li %li
= link_to dashboard_todos_path, title: 'Todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = link_to dashboard_todos_path, title: 'Todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('bell fw') = icon('bell fw')
- unless todos_pending_count == 0
%span.badge.todos-pending-count %span.badge.todos-pending-count
= todos_pending_count = todos_pending_count
- if current_user.can_create_project? - if current_user.can_create_project?
......
...@@ -41,6 +41,11 @@ ...@@ -41,6 +41,11 @@
= icon('file-text fw') = icon('file-text fw')
%span %span
Logs Logs
= nav_link(controller: :health_check) do
= link_to admin_health_check_path, title: 'Health Check' do
= icon('medkit fw')
%span
Health Check
= nav_link(controller: :broadcast_messages) do = nav_link(controller: :broadcast_messages) do
= link_to admin_broadcast_messages_path, title: 'Messages' do = link_to admin_broadcast_messages_path, title: 'Messages' do
= icon('bullhorn fw') = icon('bullhorn fw')
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
%span CI Lint %span CI Lint
.row-content-block .row-content-block
#{(@scope || 'running').capitalize} builds from this project #{(@scope || 'all').capitalize} builds from this project
%ul.content-list %ul.content-list
- if @builds.blank? - if @builds.blank?
......
- page_title "#{@build.name} (##{@build.id})", "Builds" - page_title "#{@build.name} (##{@build.id})", "Builds"
= render "header_title" = render "header_title"
- trace_with_state = @build.trace_with_state
.build-page .build-page
.row-content-block.top-block .row-content-block.top-block
...@@ -85,7 +86,9 @@ ...@@ -85,7 +86,9 @@
%pre.trace#build-trace %pre.trace#build-trace
%code.bash %code.bash
= preserve do = preserve do
= raw @build.trace_html = raw trace_with_state[:html]
- if @build.active?
%i{:class => "fa fa-refresh fa-spin"}
%div#down-build-trace %div#down-build-trace
...@@ -216,4 +219,4 @@ ...@@ -216,4 +219,4 @@
:javascript :javascript
new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{@build.status}") new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{@build.status}", "#{trace_with_state[:state]}")
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
- commits, hidden = limited_commits(@commits) - commits, hidden = limited_commits(@commits)
- commits.group_by { |c| c.committed_date.to_date }.sort.reverse.each do |day, commits| - commits.group_by { |c| c.committed_date.in_time_zone.to_date }.sort.reverse.each do |day, commits|
.row.commits-row .row.commits-row
.col-md-2.hidden-xs.hidden-sm .col-md-2.hidden-xs.hidden-sm
%h5.commits-row-date %h5.commits-row-date
......
= form_for runner, url: runner_form_url, html: { class: 'form-horizontal' } do |f|
.form-group
= label :active, "Active", class: 'control-label'
.col-sm-10
.checkbox
= f.check_box :active
%span.light Paused runners don't accept new builds
.form-group
= label_tag :token, class: 'control-label' do
Token
.col-sm-10
= f.text_field :token, class: 'form-control', readonly: true
.form-group
= label_tag :description, class: 'control-label' do
Description
.col-sm-10
= f.text_field :description, class: 'form-control'
.form-group
= label_tag :tag_list, class: 'control-label' do
Tags
.col-sm-10
= f.text_field :tag_list, value: runner.tag_list.to_s, class: 'form-control'
.help-block You can setup jobs to only use runners with specific tags
.form-actions
= f.submit 'Save changes', class: 'btn btn-save'
...@@ -2,28 +2,4 @@ ...@@ -2,28 +2,4 @@
%h4 Runner ##{@runner.id} %h4 Runner ##{@runner.id}
%hr %hr
= form_for @runner, url: runner_path(@runner), html: { class: 'form-horizontal' } do |f| = render 'form', runner: @runner, runner_form_url: runner_path(@runner)
.form-group
= label :active, "Active", class: 'control-label'
.col-sm-10
.checkbox
= f.check_box :active
%span.light Paused runners don't accept new builds
.form-group
= label_tag :token, class: 'control-label' do
Token
.col-sm-10
= f.text_field :token, class: 'form-control', readonly: true
.form-group
= label_tag :description, class: 'control-label' do
Description
.col-sm-10
= f.text_field :description, class: 'form-control'
.form-group
= label_tag :tag_list, class: 'control-label' do
Tags
.col-sm-10
= f.text_field :tag_list, value: @runner.tag_list.to_s, class: 'form-control'
.help-block You can setup jobs to only use runners with specific tags
.form-actions
= f.submit 'Save changes', class: 'btn btn-save'
...@@ -81,6 +81,9 @@ ...@@ -81,6 +81,9 @@
%li.projects-tab %li.projects-tab
= link_to user_projects_path, data: {target: 'div#projects', action: 'projects', toggle: 'tab'} do = link_to user_projects_path, data: {target: 'div#projects', action: 'projects', toggle: 'tab'} do
Personal projects Personal projects
%li.snippets-tab
= link_to user_snippets_path, data: {target: 'div#snippets', action: 'snippets', toggle: 'tab'} do
Snippets
%div{ class: container_class } %div{ class: container_class }
.tab-content .tab-content
...@@ -104,6 +107,9 @@ ...@@ -104,6 +107,9 @@
#projects.tab-pane #projects.tab-pane
- # This tab is always loaded via AJAX - # This tab is always loaded via AJAX
#snippets.tab-pane
- # This tab is always loaded via AJAX
.loading-status .loading-status
= spinner = spinner
......
class EmailsOnPushWorker class EmailsOnPushWorker
include Sidekiq::Worker include Sidekiq::Worker
attr_reader :email, :skip_premailer
def perform(project_id, recipients, push_data, options = {}) def perform(project_id, recipients, push_data, options = {})
options.symbolize_keys! options.symbolize_keys!
options.reverse_merge!( options.reverse_merge!(
...@@ -46,9 +48,9 @@ class EmailsOnPushWorker ...@@ -46,9 +48,9 @@ class EmailsOnPushWorker
recipients.split.each do |recipient| recipients.split.each do |recipient|
begin begin
Notify.repository_push_email( send_email(
project_id,
recipient, recipient,
project_id,
author_id: author_id, author_id: author_id,
ref: ref, ref: ref,
action: action, action: action,
...@@ -57,14 +59,29 @@ class EmailsOnPushWorker ...@@ -57,14 +59,29 @@ class EmailsOnPushWorker
diff_refs: diff_refs, diff_refs: diff_refs,
send_from_committer_email: send_from_committer_email, send_from_committer_email: send_from_committer_email,
disable_diffs: disable_diffs disable_diffs: disable_diffs
).deliver_now )
# These are input errors and won't be corrected even if Sidekiq retries # These are input errors and won't be corrected even if Sidekiq retries
rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e
logger.info("Failed to send e-mail for project '#{project.name_with_namespace}' to #{recipient}: #{e}") logger.info("Failed to send e-mail for project '#{project.name_with_namespace}' to #{recipient}: #{e}")
end end
end end
ensure ensure
@email = nil
compare = nil compare = nil
GC.start GC.start
end end
private
def send_email(recipient, project_id, options)
# Generating the body of this email can be expensive, so only do it once
@skip_premailer ||= email.present?
@email ||= Notify.repository_push_email(project_id, options)
email.to = recipient
email.add_message_id
email.header[:skip_premailer] = true if skip_premailer
email.deliver_now
end
end end
...@@ -3,4 +3,4 @@ require 'rubygems' ...@@ -3,4 +3,4 @@ require 'rubygems'
# Set up gems listed in the Gemfile. # Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
...@@ -2,7 +2,7 @@ CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/ ...@@ -2,7 +2,7 @@ CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/
aws_file = Rails.root.join('config', 'aws.yml') aws_file = Rails.root.join('config', 'aws.yml')
if File.exists?(aws_file) if File.exist?(aws_file)
AWS_CONFIG = YAML.load(File.read(aws_file))[Rails.env] AWS_CONFIG = YAML.load(File.read(aws_file))[Rails.env]
CarrierWave.configure do |config| CarrierWave.configure do |config|
......
HealthCheck.setup do |config|
config.standard_checks = ['database', 'migrations', 'cache']
end
## This patch is from rails 4.2-stable. Remove it when 4.2.6 is released
## https://github.com/rails/rails/issues/21108
module ActiveRecord
module ConnectionAdapters
class AbstractMysqlAdapter < AbstractAdapter
# SHOW VARIABLES LIKE 'name'
def show_variable(name)
variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
variables.first['Value'] unless variables.empty?
rescue ActiveRecord::StatementInvalid
nil
end
# MySQL is too stupid to create a temporary table for use subquery, so we have
# to give it some prompting in the form of a subsubquery. Ugh!
def subquery_for(key, select)
subsubselect = select.clone
subsubselect.projections = [key]
subselect = Arel::SelectManager.new(select.engine)
subselect.project Arel.sql(key.name)
# Materialized subquery by adding distinct
# to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
subselect.from subsubselect.distinct.as('__active_record_temp')
end
end
end
end
module ActiveRecord
module ConnectionAdapters
class MysqlAdapter < AbstractMysqlAdapter
ADAPTER_NAME = 'MySQL'.freeze
# Get the client encoding for this database
def client_encoding
return @client_encoding if @client_encoding
result = exec_query(
"select @@character_set_client",
'SCHEMA')
@client_encoding = ENCODINGS[result.rows.last.last]
end
end
end
end
...@@ -78,6 +78,9 @@ Rails.application.routes.draw do ...@@ -78,6 +78,9 @@ Rails.application.routes.draw do
mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq
end end
# Health check
get 'health_check(/:checks)' => 'health_check#index', as: :health_check
# Enable Grack support # Enable Grack support
mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post, :put] mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post, :put]
...@@ -96,7 +99,8 @@ Rails.application.routes.draw do ...@@ -96,7 +99,8 @@ Rails.application.routes.draw do
end end
end end
get '/s/:username' => 'snippets#index', as: :user_snippets, constraints: { username: /.*/ } get '/s/:username', to: redirect('/u/%{username}/snippets'),
constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ }
# #
# Invites # Invites
...@@ -260,6 +264,7 @@ Rails.application.routes.draw do ...@@ -260,6 +264,7 @@ Rails.application.routes.draw do
end end
resource :logs, only: [:show] resource :logs, only: [:show]
resource :health_check, controller: 'health_check', only: [:show]
resource :background_jobs, controller: 'background_jobs', only: [:show] resource :background_jobs, controller: 'background_jobs', only: [:show]
resource :email, only: [:show, :create] resource :email, only: [:show, :create]
...@@ -292,6 +297,7 @@ Rails.application.routes.draw do ...@@ -292,6 +297,7 @@ Rails.application.routes.draw do
resource :application_settings, only: [:show, :update] do resource :application_settings, only: [:show, :update] do
resources :services resources :services
put :reset_runners_token put :reset_runners_token
put :reset_health_check_token
put :clear_repository_check_states put :clear_repository_check_states
end end
...@@ -360,23 +366,18 @@ Rails.application.routes.draw do ...@@ -360,23 +366,18 @@ Rails.application.routes.draw do
end end
end end
get 'u/:username/calendar' => 'users#calendar', as: :user_calendar, scope(path: 'u/:username',
constraints: { username: /.*/ } as: :user,
constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ },
get 'u/:username/calendar_activities' => 'users#calendar_activities', as: :user_calendar_activities, controller: :users) do
constraints: { username: /.*/ } get :calendar
get :calendar_activities
get 'u/:username/groups' => 'users#groups', as: :user_groups, get :groups
constraints: { username: /.*/ } get :projects
get :contributed, as: :contributed_projects
get 'u/:username/projects' => 'users#projects', as: :user_projects, get :snippets
constraints: { username: /.*/ } get '/', action: :show
end
get 'u/:username/contributed' => 'users#contributed', as: :user_contributed_projects,
constraints: { username: /.*/ }
get '/u/:username' => 'users#show', as: :user,
constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ }
# #
# Dashboard Area # Dashboard Area
...@@ -724,6 +725,7 @@ Rails.application.routes.draw do ...@@ -724,6 +725,7 @@ Rails.application.routes.draw do
post :cancel post :cancel
post :retry post :retry
post :erase post :erase
get :trace
get :raw get :raw
end end
......
class AddDisabledOauthSignInSourcesToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :disabled_oauth_sign_in_sources, :text
end
end
class AddHealthCheckAccessTokenToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :health_check_access_token, :string
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160508194200) do ActiveRecord::Schema.define(version: 20160509201028) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -83,6 +83,8 @@ ActiveRecord::Schema.define(version: 20160508194200) do ...@@ -83,6 +83,8 @@ ActiveRecord::Schema.define(version: 20160508194200) do
t.boolean "repository_checks_enabled", default: false t.boolean "repository_checks_enabled", default: false
t.text "shared_runners_text" t.text "shared_runners_text"
t.integer "metrics_packet_size", default: 1 t.integer "metrics_packet_size", default: 1
t.text "disabled_oauth_sign_in_sources"
t.string "health_check_access_token"
end end
create_table "approvals", force: :cascade do |t| create_table "approvals", force: :cascade do |t|
......
...@@ -165,3 +165,73 @@ Example response: ...@@ -165,3 +165,73 @@ Example response:
"description": "Documentation" "description": "Documentation"
} }
``` ```
## Subscribe to a label
Subscribes the authenticated user to a label to receive notifications. If the
operation is successful, status code `201` together with the updated label is
returned. If the user is already subscribed to the label, the status code `304`
is returned. If the project or label is not found, status code `404` is
returned.
```
POST /projects/:id/labels/:label_id/subscription
```
| Attribute | Type | Required | Description |
| ---------- | ----------------- | -------- | ------------------------------------ |
| `id` | integer | yes | The ID of a project |
| `label_id` | integer or string | yes | The ID or title of a project's label |
```bash
curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/labels/1/subscription
```
Example response:
```json
{
"name": "Docs",
"color": "#cc0033",
"description": "",
"open_issues_count": 0,
"closed_issues_count": 0,
"open_merge_requests_count": 0,
"subscribed": true
}
```
## Unsubscribe from a label
Unsubscribes the authenticated user from a label to not receive notifications
from it. If the operation is successful, status code `200` together with the
updated label is returned. If the user is not subscribed to the label, the
status code `304` is returned. If the project or label is not found, status code
`404` is returned.
```
DELETE /projects/:id/labels/:label_id/subscription
```
| Attribute | Type | Required | Description |
| ---------- | ----------------- | -------- | ------------------------------------ |
| `id` | integer | yes | The ID of a project |
| `label_id` | integer or string | yes | The ID or title of a project's label |
```bash
curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/labels/1/subscription
```
Example response:
```json
{
"name": "Docs",
"color": "#cc0033",
"description": "",
"open_issues_count": 0,
"closed_issues_count": 0,
"open_merge_requests_count": 0,
"subscribed": false
}
```
...@@ -269,9 +269,9 @@ sudo usermod -aG redis git ...@@ -269,9 +269,9 @@ sudo usermod -aG redis git
### Clone the Source ### Clone the Source
# Clone GitLab repository # Clone GitLab repository
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-7-stable-ee gitlab sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ee.git -b 8-8-stable-ee gitlab
**Note:** You can change `8-7-stable-ee` to `master` if you want the *bleeding edge* version, but never install master on a production server! **Note:** You can change `8-8-stable-ee` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It ### Configure It
......
...@@ -27,18 +27,19 @@ To enable the CAS OmniAuth provider you must register your application with your ...@@ -27,18 +27,19 @@ To enable the CAS OmniAuth provider you must register your application with your
```ruby ```ruby
gitlab_rails['omniauth_providers'] = [ gitlab_rails['omniauth_providers'] = [
{ {
name: "cas3", "name"=> "cas3",
label: "cas", "label"=> "cas",
args: { "args"=> {
url: 'CAS_SERVER', "url"=> 'CAS_SERVER',
login_url: '/CAS_PATH/login', "login_url"=> '/CAS_PATH/login',
service_validate_url: '/CAS_PATH/p3/serviceValidate', "service_validate_url"=> '/CAS_PATH/p3/serviceValidate',
logout_url: '/CAS_PATH/logout'} } "logout_url"=> '/CAS_PATH/logout'
} }
} }
] ]
``` ```
For installations from source: For installations from source:
``` ```
...@@ -57,6 +58,8 @@ To enable the CAS OmniAuth provider you must register your application with your ...@@ -57,6 +58,8 @@ To enable the CAS OmniAuth provider you must register your application with your
1. Save the configuration file. 1. Save the configuration file.
1. Run `gitlab-ctl reconfigure` for the omnibus package.
1. Restart GitLab for the changes to take effect. 1. Restart GitLab for the changes to take effect.
On the sign in page there should now be a CAS tab in the sign in form. On the sign in page there should now be a CAS tab in the sign in form.
...@@ -11,6 +11,7 @@ of the configured mechanisms. ...@@ -11,6 +11,7 @@ of the configured mechanisms.
- [Supported Providers](#supported-providers) - [Supported Providers](#supported-providers)
- [Enable OmniAuth for an Existing User](#enable-omniauth-for-an-existing-user) - [Enable OmniAuth for an Existing User](#enable-omniauth-for-an-existing-user)
- [OmniAuth configuration sample when using Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master#omniauth-google-twitter-github-login) - [OmniAuth configuration sample when using Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master#omniauth-google-twitter-github-login)
- [Enable or disable Sign In with an OmniAuth provider without disabling import sources](#enable-or-disable-sign-in-with-an-omniauth-provider-without-disabling-import-sources)
## Supported Providers ## Supported Providers
...@@ -191,3 +192,17 @@ experience [in the public Wiki](https://github.com/gitlabhq/gitlab-public-wiki/w ...@@ -191,3 +192,17 @@ experience [in the public Wiki](https://github.com/gitlabhq/gitlab-public-wiki/w
While we can't officially support every possible authentication mechanism out there, While we can't officially support every possible authentication mechanism out there,
we'd like to at least help those with specific needs. we'd like to at least help those with specific needs.
## Enable or disable Sign In with an OmniAuth provider without disabling import sources
>**Note:**
This setting was introduced with version 8.8 of GitLab.
Administrators are able to enable or disable Sign In via some OmniAuth providers.
>**Note:**
By default Sign In is enabled via all the OAuth Providers that have been configured in `config/gitlab.yml`.
In order to enable/disable an OmniAuth provider, go to Admin Area -> Settings -> Sign-in Restrictions section -> Enabled OAuth Sign-In sources and select the providers you want to enable or disable.
![Enabled OAuth Sign-In sources](img/enabled-oauth-sign-in-sources.png)
# From 8.7 to 8.8
Make sure you view this update guide from the tag (version) of GitLab you would
like to install. In most cases this should be the highest numbered production
tag (without rc in it). You can select the tag in the version dropdown at the
top left corner of GitLab (below the menu bar).
If the highest number stable branch is unclear please check the
[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
guide links by version.
### 1. Stop server
sudo service gitlab stop
### 2. Backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
### 3. Get latest code
```bash
sudo -u git -H git fetch --all
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
```
For GitLab Community Edition:
```bash
sudo -u git -H git checkout 8-8-stable
```
OR
For GitLab Enterprise Edition:
```bash
sudo -u git -H git checkout 8-8-stable-ee
```
### 4. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch --all --tags
sudo -u git -H git checkout v2.7.2
```
### 5. Update gitlab-workhorse
Install and compile gitlab-workhorse. This requires
[Go 1.5](https://golang.org/dl) which should already be on your system from
GitLab 8.1.
```bash
cd /home/git/gitlab-workhorse
sudo -u git -H git fetch --all
sudo -u git -H git checkout v0.7.1
sudo -u git -H make
```
### 6. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
# MySQL installations (note: the line below states '--without postgres')
sudo -u git -H bundle install --without postgres development test --deployment
# PostgreSQL installations (note: the line below states '--without mysql')
sudo -u git -H bundle install --without mysql development test --deployment
# Optional: clean up old gems
sudo -u git -H bundle clean
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
# Clean up assets and cache
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
```
### 7. Update configuration files
#### Git configuration
Disable `git gc --auto` because GitLab runs `git gc` for us already.
```sh
sudo -u git -H git config --global gc.auto 0
```
#### Nginx configuration
Ensure you're still up-to-date with the latest NGINX configuration changes:
```sh
# For HTTPS configurations
git diff origin/8-7-stable:lib/support/nginx/gitlab-ssl origin/8-8-stable:lib/support/nginx/gitlab-ssl
# For HTTP configurations
git diff origin/8-7-stable:lib/support/nginx/gitlab origin/8-8-stable:lib/support/nginx/gitlab
```
If you are using Apache instead of NGINX please see the updated [Apache templates].
Also note that because Apache does not support upstreams behind Unix sockets you
will need to let gitlab-workhorse listen on a TCP port. You can do this
via [/etc/default/gitlab].
[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-8-stable/lib/support/init.d/gitlab.default.example#L37
#### Init script
Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
### 8. Start application
sudo service gitlab start
sudo service nginx restart
### 9. Check application status
Check if GitLab and its environment are configured correctly:
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
To make sure you didn't miss anything run a more thorough check:
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
If all items are green, then congratulations, the upgrade is complete!
## Things went south? Revert to previous version (8.6)
### 1. Revert the code to the previous version
Follow the [upgrade guide from 8.6 to 8.7](8.6-to-8.7.md), except for the
database migration (the backup is already migrated to the previous version).
### 2. Restore from the backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
...@@ -16,6 +16,19 @@ In GitLab Enterprise Edition you can configure web hooks globally for the whole ...@@ -16,6 +16,19 @@ In GitLab Enterprise Edition you can configure web hooks globally for the whole
group. You can add the group level web hooks on the group settings page group. You can add the group level web hooks on the group settings page
**Settings > Webhooks**. **Settings > Webhooks**.
## Webhook endpoint tips
If you are writing your own endpoint (web server) that will receive
GitLab webhooks keep in mind the following things:
- Your endpoint should send its HTTP response as fast as possible. If
you wait too long, GitLab may decide the hook failed and retry it.
- Your endpoint should ALWAYS return a valid HTTP response. If you do
not do this then GitLab will think the hook failed and retry it.
Most HTTP libraries take care of this for you automatically but if
you are writing a low-level hook this is important to remember.
- GitLab ignores the HTTP status code returned by your endpoint.
## SSL Verification ## SSL Verification
By default, the SSL certificate of the webhook endpoint is verified based on By default, the SSL certificate of the webhook endpoint is verified based on
......
...@@ -59,7 +59,7 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps ...@@ -59,7 +59,7 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps
step 'hook should be triggered' do step 'hook should be triggered' do
expect(current_path).to eq namespace_project_hooks_path(current_project.namespace, current_project) expect(current_path).to eq namespace_project_hooks_path(current_project.namespace, current_project)
expect(page).to have_selector '.flash-notice', expect(page).to have_selector '.flash-notice',
text: 'Hook successfully executed.' text: 'Hook executed successfully: HTTP 200'
end end
step 'I should see hook error message' do step 'I should see hook error message' do
......
...@@ -62,5 +62,6 @@ module API ...@@ -62,5 +62,6 @@ module API
mount ::API::Variables mount ::API::Variables
mount ::API::Runners mount ::API::Runners
mount ::API::Licenses mount ::API::Licenses
mount ::API::Subscriptions
end end
end end
...@@ -325,6 +325,10 @@ module API ...@@ -325,6 +325,10 @@ module API
class Label < Grape::Entity class Label < Grape::Entity
expose :name, :color, :description expose :name, :color, :description
expose :open_issues_count, :closed_issues_count, :open_merge_requests_count expose :open_issues_count, :closed_issues_count, :open_merge_requests_count
expose :subscribed do |label, options|
label.subscribed?(options[:current_user])
end
end end
class Compare < Grape::Entity class Compare < Grape::Entity
......
...@@ -95,6 +95,17 @@ module API ...@@ -95,6 +95,17 @@ module API
end end
end end
def find_project_label(id)
label = user_project.labels.find_by_id(id) || user_project.labels.find_by_title(id)
label || not_found!('Label')
end
def find_project_issue(id)
issue = user_project.issues.find(id)
not_found! unless can?(current_user, :read_issue, issue)
issue
end
def paginate(relation) def paginate(relation)
relation.page(params[:page]).per(params[:per_page].to_i).tap do |data| relation.page(params[:page]).per(params[:per_page].to_i).tap do |data|
add_pagination_headers(data) add_pagination_headers(data)
......
...@@ -103,8 +103,7 @@ module API ...@@ -103,8 +103,7 @@ module API
# Example Request: # Example Request:
# GET /projects/:id/issues/:issue_id # GET /projects/:id/issues/:issue_id
get ":id/issues/:issue_id" do get ":id/issues/:issue_id" do
@issue = user_project.issues.find(params[:issue_id]) @issue = find_project_issue(params[:issue_id])
not_found! unless can?(current_user, :read_issue, @issue)
present @issue, with: Entities::Issue, current_user: current_user present @issue, with: Entities::Issue, current_user: current_user
end end
...@@ -234,42 +233,6 @@ module API ...@@ -234,42 +233,6 @@ module API
authorize!(:destroy_issue, issue) authorize!(:destroy_issue, issue)
issue.destroy issue.destroy
end end
# Subscribes to a project issue
#
# Parameters:
# id (required) - The ID of a project
# issue_id (required) - The ID of a project issue
# Example Request:
# POST /projects/:id/issues/:issue_id/subscription
post ':id/issues/:issue_id/subscription' do
issue = user_project.issues.find(params[:issue_id])
if issue.subscribed?(current_user)
not_modified!
else
issue.toggle_subscription(current_user)
present issue, with: Entities::Issue, current_user: current_user
end
end
# Unsubscribes from a project issue
#
# Parameters:
# id (required) - The ID of a project
# issue_id (required) - The ID of a project issue
# Example Request:
# DELETE /projects/:id/issues/:issue_id/subscription
delete ':id/issues/:issue_id/subscription' do
issue = user_project.issues.find(params[:issue_id])
if issue.subscribed?(current_user)
issue.unsubscribe(current_user)
present issue, with: Entities::Issue, current_user: current_user
else
not_modified!
end
end
end end
end end
end end
...@@ -11,7 +11,7 @@ module API ...@@ -11,7 +11,7 @@ module API
# Example Request: # Example Request:
# GET /projects/:id/labels # GET /projects/:id/labels
get ':id/labels' do get ':id/labels' do
present user_project.labels, with: Entities::Label present user_project.labels, with: Entities::Label, current_user: current_user
end end
# Creates a new label # Creates a new label
...@@ -36,7 +36,7 @@ module API ...@@ -36,7 +36,7 @@ module API
label = user_project.labels.create(attrs) label = user_project.labels.create(attrs)
if label.valid? if label.valid?
present label, with: Entities::Label present label, with: Entities::Label, current_user: current_user
else else
render_validation_error!(label) render_validation_error!(label)
end end
...@@ -90,7 +90,7 @@ module API ...@@ -90,7 +90,7 @@ module API
attrs[:name] = attrs.delete(:new_name) if attrs.key?(:new_name) attrs[:name] = attrs.delete(:new_name) if attrs.key?(:new_name)
if label.update(attrs) if label.update(attrs)
present label, with: Entities::Label present label, with: Entities::Label, current_user: current_user
else else
render_validation_error!(label) render_validation_error!(label)
end end
......
...@@ -327,42 +327,6 @@ module API ...@@ -327,42 +327,6 @@ module API
issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user)) issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
present paginate(issues), with: Entities::Issue, current_user: current_user present paginate(issues), with: Entities::Issue, current_user: current_user
end end
# Subscribes to a merge request
#
# Parameters:
# id (required) - The ID of a project
# merge_request_id (required) - The ID of a merge request
# Example Request:
# POST /projects/:id/issues/:merge_request_id/subscription
post "#{path}/subscription" do
merge_request = user_project.merge_requests.find(params[:merge_request_id])
if merge_request.subscribed?(current_user)
not_modified!
else
merge_request.toggle_subscription(current_user)
present merge_request, with: Entities::MergeRequest, current_user: current_user
end
end
# Unsubscribes from a merge request
#
# Parameters:
# id (required) - The ID of a project
# merge_request_id (required) - The ID of a merge request
# Example Request:
# DELETE /projects/:id/merge_requests/:merge_request_id/subscription
delete "#{path}/subscription" do
merge_request = user_project.merge_requests.find(params[:merge_request_id])
if merge_request.subscribed?(current_user)
merge_request.unsubscribe(current_user)
present merge_request, with: Entities::MergeRequest, current_user: current_user
else
not_modified!
end
end
end end
end end
end end
......
...@@ -44,7 +44,7 @@ module API ...@@ -44,7 +44,7 @@ module API
# Example Request: # Example Request:
# GET /projects/starred # GET /projects/starred
get '/starred' do get '/starred' do
@projects = current_user.starred_projects @projects = current_user.viewable_starred_projects
@projects = filter_projects(@projects) @projects = filter_projects(@projects)
@projects = paginate @projects @projects = paginate @projects
present @projects, with: Entities::Project present @projects, with: Entities::Project
......
module API
class Subscriptions < Grape::API
before { authenticate! }
subscribable_types = {
'merge_request' => proc { |id| user_project.merge_requests.find(id) },
'merge_requests' => proc { |id| user_project.merge_requests.find(id) },
'issues' => proc { |id| find_project_issue(id) },
'labels' => proc { |id| find_project_label(id) },
}
resource :projects do
subscribable_types.each do |type, finder|
type_singularized = type.singularize
type_id_str = :"#{type_singularized}_id"
entity_class = Entities.const_get(type_singularized.camelcase)
# Subscribe to a resource
#
# Parameters:
# id (required) - The ID of a project
# subscribable_id (required) - The ID of a resource
# Example Request:
# POST /projects/:id/labels/:subscribable_id/subscription
# POST /projects/:id/issues/:subscribable_id/subscription
# POST /projects/:id/merge_requests/:subscribable_id/subscription
post ":id/#{type}/:#{type_id_str}/subscription" do
resource = instance_exec(params[type_id_str], &finder)
if resource.subscribed?(current_user)
not_modified!
else
resource.subscribe(current_user)
present resource, with: entity_class, current_user: current_user
end
end
# Unsubscribe from a resource
#
# Parameters:
# id (required) - The ID of a project
# subscribable_id (required) - The ID of a resource
# Example Request:
# DELETE /projects/:id/labels/:subscribable_id/subscription
# DELETE /projects/:id/issues/:subscribable_id/subscription
# DELETE /projects/:id/merge_requests/:subscribable_id/subscription
delete ":id/#{type}/:#{type_id_str}/subscription" do
resource = instance_exec(params[type_id_str], &finder)
if !resource.subscribed?(current_user)
not_modified!
else
resource.unsubscribe(current_user)
present resource, with: entity_class, current_user: current_user
end
end
end
end
end
end
...@@ -25,7 +25,7 @@ module Banzai ...@@ -25,7 +25,7 @@ module Banzai
end end
def process_link_attr(html_attr) def process_link_attr(html_attr)
return if html_attr.blank? || file_reference?(html_attr) return if html_attr.blank? || file_reference?(html_attr) || hierarchical_link?(html_attr)
uri = URI(html_attr.value) uri = URI(html_attr.value)
if uri.relative? && uri.path.present? if uri.relative? && uri.path.present?
...@@ -40,12 +40,17 @@ module Banzai ...@@ -40,12 +40,17 @@ module Banzai
uri uri
end end
def project_wiki
context[:project_wiki]
end
def file_reference?(html_attr) def file_reference?(html_attr)
!File.extname(html_attr.value).blank? !File.extname(html_attr.value).blank?
end end
def project_wiki # Of the form `./link`, `../link`, or similar
context[:project_wiki] def hierarchical_link?(html_attr)
html_attr.value[0] == '.'
end end
def project_wiki_base_path def project_wiki_base_path
......
...@@ -23,8 +23,8 @@ module Ci ...@@ -23,8 +23,8 @@ module Ci
cross: 0x10, cross: 0x10,
} }
def self.convert(ansi) def self.convert(ansi, state = nil)
Converter.new().convert(ansi) Converter.new.convert(ansi, state)
end end
class Converter class Converter
...@@ -84,22 +84,36 @@ module Ci ...@@ -84,22 +84,36 @@ module Ci
def on_107(s) set_bg_color(7, 'l') end def on_107(s) set_bg_color(7, 'l') end
def on_109(s) set_bg_color(9, 'l') end def on_109(s) set_bg_color(9, 'l') end
def convert(ansi) attr_accessor :offset, :n_open_tags, :fg_color, :bg_color, :style_mask
@out = ""
@n_open_tags = 0 STATE_PARAMS = [:offset, :n_open_tags, :fg_color, :bg_color, :style_mask]
reset()
def convert(raw, new_state)
reset_state
restore_state(raw, new_state) if new_state
start = @offset
ansi = raw[@offset..-1]
s = StringScanner.new(ansi.gsub("<", "&lt;")) open_new_tag
s = StringScanner.new(ansi)
while(!s.eos?) while(!s.eos?)
if s.scan(/\e([@-_])(.*?)([@-~])/) if s.scan(/\e([@-_])(.*?)([@-~])/)
handle_sequence(s) handle_sequence(s)
elsif s.scan(/\e(([@-_])(.*?)?)?$/)
break
elsif s.scan(/</)
@out << '&lt;'
else else
@out << s.scan(/./m) @out << s.scan(/./m)
end end
@offset += s.matched_size
end end
close_open_tags() close_open_tags()
@out
{ state: state, html: @out, text: ansi[0, @offset - start], append: start > 0 }
end end
def handle_sequence(s) def handle_sequence(s)
...@@ -121,6 +135,20 @@ module Ci ...@@ -121,6 +135,20 @@ module Ci
evaluate_command_stack(commands) evaluate_command_stack(commands)
open_new_tag
end
def evaluate_command_stack(stack)
return unless command = stack.shift()
if self.respond_to?("on_#{command}", true)
self.send("on_#{command}", stack)
end
evaluate_command_stack(stack)
end
def open_new_tag
css_classes = [] css_classes = []
unless @fg_color.nil? unless @fg_color.nil?
...@@ -138,20 +166,8 @@ module Ci ...@@ -138,20 +166,8 @@ module Ci
css_classes << "term-#{css_class}" if @style_mask & flag != 0 css_classes << "term-#{css_class}" if @style_mask & flag != 0
end end
open_new_tag(css_classes) if css_classes.length > 0 return if css_classes.empty?
end
def evaluate_command_stack(stack)
return unless command = stack.shift()
if self.respond_to?("on_#{command}", true)
self.send("on_#{command}", stack)
end
evaluate_command_stack(stack)
end
def open_new_tag(css_classes)
@out << %{<span class="#{css_classes.join(' ')}">} @out << %{<span class="#{css_classes.join(' ')}">}
@n_open_tags += 1 @n_open_tags += 1
end end
...@@ -163,6 +179,31 @@ module Ci ...@@ -163,6 +179,31 @@ module Ci
end end
end end
def reset_state
@offset = 0
@n_open_tags = 0
@out = ''
reset
end
def state
state = STATE_PARAMS.inject({}) do |h, param|
h[param] = send(param)
h
end
Base64.urlsafe_encode64(state.to_json)
end
def restore_state(raw, new_state)
state = Base64.urlsafe_decode64(new_state)
state = JSON.parse(state, symbolize_names: true)
return if state[:offset].to_i > raw.length
STATE_PARAMS.each do |param|
send("#{param}=".to_sym, state[param])
end
end
def reset def reset
@fg_color = nil @fg_color = nil
@bg_color = nil @bg_color = nil
......
...@@ -224,7 +224,7 @@ module Gitlab ...@@ -224,7 +224,7 @@ module Gitlab
# exists?('gitlab/cookies.git') # exists?('gitlab/cookies.git')
# #
def exists?(dir_name) def exists?(dir_name)
File.exists?(full_path(dir_name)) File.exist?(full_path(dir_name))
end end
# Push branch to remote repository # Push branch to remote repository
......
...@@ -2,7 +2,6 @@ module Gitlab ...@@ -2,7 +2,6 @@ module Gitlab
module Email module Email
module Message module Message
class RepositoryPush class RepositoryPush
attr_accessor :recipient
attr_reader :author_id, :ref, :action attr_reader :author_id, :ref, :action
include Gitlab::Routing.url_helpers include Gitlab::Routing.url_helpers
...@@ -12,13 +11,12 @@ module Gitlab ...@@ -12,13 +11,12 @@ module Gitlab
delegate :name, to: :author, prefix: :author delegate :name, to: :author, prefix: :author
delegate :username, to: :author, prefix: :author delegate :username, to: :author, prefix: :author
def initialize(notify, project_id, recipient, opts = {}) def initialize(notify, project_id, opts = {})
raise ArgumentError, 'Missing options: author_id, ref, action' unless raise ArgumentError, 'Missing options: author_id, ref, action' unless
opts[:author_id] && opts[:ref] && opts[:action] opts[:author_id] && opts[:ref] && opts[:action]
@notify = notify @notify = notify
@project_id = project_id @project_id = project_id
@recipient = recipient
@opts = opts.dup @opts = opts.dup
@author_id = @opts.delete(:author_id) @author_id = @opts.delete(:author_id)
......
...@@ -88,7 +88,7 @@ module Gitlab ...@@ -88,7 +88,7 @@ module Gitlab
def self.redis_connection def self.redis_connection
redis_config_file = Rails.root.join('config', 'resque.yml') redis_config_file = Rails.root.join('config', 'resque.yml')
redis_url_string = if File.exists?(redis_config_file) redis_url_string = if File.exist?(redis_config_file)
YAML.load_file(redis_config_file)[Rails.env] YAML.load_file(redis_config_file)[Rails.env]
else else
'redis://localhost:6379' 'redis://localhost:6379'
......
...@@ -154,8 +154,6 @@ module Gitlab ...@@ -154,8 +154,6 @@ module Gitlab
duration = (Time.now - start) * 1000.0 duration = (Time.now - start) * 1000.0
if duration >= Gitlab::Metrics.method_call_threshold if duration >= Gitlab::Metrics.method_call_threshold
trans.increment(:method_duration, duration)
trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES, trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES,
{ duration: duration }, { duration: duration },
method: #{label.inspect}) method: #{label.inspect})
......
...@@ -42,7 +42,7 @@ module Gitlab ...@@ -42,7 +42,7 @@ module Gitlab
config_file = File.expand_path('../../../config/resque.yml', __FILE__) config_file = File.expand_path('../../../config/resque.yml', __FILE__)
@url = "redis://localhost:6379" @url = "redis://localhost:6379"
if File.exists?(config_file) if File.exist?(config_file)
@url =YAML.load_file(config_file)[rails_env] @url =YAML.load_file(config_file)[rails_env]
end end
end end
......
...@@ -43,7 +43,7 @@ describe "mail_room.yml" do ...@@ -43,7 +43,7 @@ describe "mail_room.yml" do
redis_config_file = Rails.root.join('config', 'resque.yml') redis_config_file = Rails.root.join('config', 'resque.yml')
redis_url = redis_url =
if File.exists?(redis_config_file) if File.exist?(redis_config_file)
YAML.load_file(redis_config_file)[Rails.env] YAML.load_file(redis_config_file)[Rails.env]
else else
"redis://localhost:6379" "redis://localhost:6379"
......
require 'spec_helper'
describe HealthCheckController do
let(:token) { current_application_settings.health_check_access_token }
let(:json_response) { JSON.parse(response.body) }
let(:xml_response) { Hash.from_xml(response.body)['hash'] }
describe 'GET #index' do
context 'when services are up but NO access token' do
it 'returns a not found page' do
get :index
expect(response).to be_not_found
end
end
context 'when services are up and an access token is provided' do
it 'supports passing the token in the header' do
request.headers['TOKEN'] = token
get :index
expect(response).to be_success
expect(response.content_type).to eq 'text/plain'
end
it 'supports successful plaintest response' do
get :index, token: token
expect(response).to be_success
expect(response.content_type).to eq 'text/plain'
end
it 'supports successful json response' do
get :index, token: token, format: :json
expect(response).to be_success
expect(response.content_type).to eq 'application/json'
expect(json_response['healthy']).to be true
end
it 'supports successful xml response' do
get :index, token: token, format: :xml
expect(response).to be_success
expect(response.content_type).to eq 'application/xml'
expect(xml_response['healthy']).to be true
end
it 'supports successful responses for specific checks' do
get :index, token: token, checks: 'email', format: :json
expect(response).to be_success
expect(response.content_type).to eq 'application/json'
expect(json_response['healthy']).to be true
end
end
context 'when a service is down but NO access token' do
it 'returns a not found page' do
get :index
expect(response).to be_not_found
end
end
context 'when a service is down and an access token is provided' do
before do
allow(HealthCheck::Utils).to receive(:process_checks).with('standard').and_return('The server is on fire')
allow(HealthCheck::Utils).to receive(:process_checks).with('email').and_return('Email is on fire')
end
it 'supports passing the token in the header' do
request.headers['TOKEN'] = token
get :index
expect(response.status).to eq(500)
expect(response.content_type).to eq 'text/plain'
expect(response.body).to include('The server is on fire')
end
it 'supports failure plaintest response' do
get :index, token: token
expect(response.status).to eq(500)
expect(response.content_type).to eq 'text/plain'
expect(response.body).to include('The server is on fire')
end
it 'supports failure json response' do
get :index, token: token, format: :json
expect(response.status).to eq(500)
expect(response.content_type).to eq 'application/json'
expect(json_response['healthy']).to be false
expect(json_response['message']).to include('The server is on fire')
end
it 'supports failure xml response' do
get :index, token: token, format: :xml
expect(response.status).to eq(500)
expect(response.content_type).to eq 'application/xml'
expect(xml_response['healthy']).to be false
expect(xml_response['message']).to include('The server is on fire')
end
it 'supports failure responses for specific checks' do
get :index, token: token, checks: 'email', format: :json
expect(response.status).to eq(500)
expect(response.content_type).to eq 'application/json'
expect(json_response['healthy']).to be false
expect(json_response['message']).to include('Email is on fire')
end
end
end
end
...@@ -112,4 +112,26 @@ describe UsersController do ...@@ -112,4 +112,26 @@ describe UsersController do
expect(response).to render_template('calendar_activities') expect(response).to render_template('calendar_activities')
end end
end end
describe 'GET #snippets' do
before do
sign_in(user)
end
context 'format html' do
it 'renders snippets page' do
get :snippets, username: user.username
expect(response.status).to eq(200)
expect(response).to render_template('show')
end
end
context 'format json' do
it 'response with snippets json data' do
get :snippets, username: user.username, format: :json
expect(response.status).to eq(200)
expect(JSON.parse(response.body)).to have_key('html')
end
end
end
end end
...@@ -19,6 +19,7 @@ describe 'Admin Builds' do ...@@ -19,6 +19,7 @@ describe 'Admin Builds' do
visit admin_builds_path visit admin_builds_path
expect(page).to have_selector('.nav-links li.active', text: 'All') expect(page).to have_selector('.nav-links li.active', text: 'All')
expect(page).to have_selector('.row-content-block', text: 'All builds')
expect(page.all('.build-link').size).to eq(4) expect(page.all('.build-link').size).to eq(4)
expect(page).to have_link 'Cancel all' expect(page).to have_link 'Cancel all'
end end
......
require 'spec_helper'
feature "Admin Health Check", feature: true do
include WaitForAjax
before do
login_as :admin
end
describe '#show' do
before do
visit admin_health_check_path
end
it { page.has_text? 'Health Check' }
it { page.has_text? 'Health information can be retrieved' }
it 'has a health check access token' do
token = current_application_settings.health_check_access_token
expect(page).to have_content("Access token is #{token}")
expect(page).to have_selector('#health-check-token', text: token)
end
describe 'reload access token', js: true do
it 'changes the access token' do
orig_token = current_application_settings.health_check_access_token
click_button 'Reset health check access token'
wait_for_ajax
expect(find('#health-check-token').text).not_to eq orig_token
end
end
end
context 'when services are up' do
before do
visit admin_health_check_path
end
it 'shows healthy status' do
expect(page).to have_content('Current Status: Healthy')
end
end
context 'when a service is down' do
before do
allow(HealthCheck::Utils).to receive(:process_checks).and_return('The server is on fire')
visit admin_health_check_path
end
it 'shows unhealthy status' do
expect(page).to have_content('Current Status: Unhealthy')
expect(page).to have_content('The server is on fire')
end
end
end
...@@ -43,6 +43,7 @@ describe "Builds" do ...@@ -43,6 +43,7 @@ describe "Builds" do
end end
it { expect(page).to have_selector('.nav-links li.active', text: 'All') } it { expect(page).to have_selector('.nav-links li.active', text: 'All') }
it { expect(page).to have_selector('.row-content-block', text: 'All builds from this project') }
it { expect(page).to have_content @build.short_sha } it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref } it { expect(page).to have_content @build.ref }
it { expect(page).to have_content @build.name } it { expect(page).to have_content @build.name }
......
...@@ -2,7 +2,7 @@ require "spec_helper" ...@@ -2,7 +2,7 @@ require "spec_helper"
describe AuthHelper do describe AuthHelper do
describe "button_based_providers" do describe "button_based_providers" do
it 'returns all enabled providers' do it 'returns all enabled providers from devise' do
allow(helper).to receive(:auth_providers) { [:twitter, :github] } allow(helper).to receive(:auth_providers) { [:twitter, :github] }
expect(helper.button_based_providers).to include(*[:twitter, :github]) expect(helper.button_based_providers).to include(*[:twitter, :github])
end end
...@@ -17,4 +17,49 @@ describe AuthHelper do ...@@ -17,4 +17,49 @@ describe AuthHelper do
expect(helper.button_based_providers).to eq([]) expect(helper.button_based_providers).to eq([])
end end
end end
describe 'enabled_button_based_providers' do
before do
allow(helper).to receive(:auth_providers) { [:twitter, :github] }
end
context 'all providers are enabled to sign in' do
it 'returns all the enabled providers from settings' do
expect(helper.enabled_button_based_providers).to include('twitter', 'github')
end
end
context 'GitHub OAuth sign in is disabled from application setting' do
it "doesn't return github as provider" do
stub_application_setting(
disabled_oauth_sign_in_sources: ['github']
)
expect(helper.enabled_button_based_providers).to include('twitter')
expect(helper.enabled_button_based_providers).to_not include('github')
end
end
end
describe 'button_based_providers_enabled?' do
before do
allow(helper).to receive(:auth_providers) { [:twitter, :github] }
end
context 'button based providers enabled' do
it 'returns true' do
expect(helper.button_based_providers_enabled?).to be true
end
end
context 'all the button based providers are disabled via application_setting' do
it 'returns false' do
stub_application_setting(
disabled_oauth_sign_in_sources: ['github', 'twitter']
)
expect(helper.button_based_providers_enabled?).to be false
end
end
end
end end
require 'spec_helper' require 'spec_helper'
describe EventsHelper do describe EventsHelper do
include ApplicationHelper describe '#event_note' do
include GitlabMarkdownHelper before do
allow(helper).to receive(:current_user).and_return(double)
let(:current_user) { create(:user, email: "current@email.com") } end
it 'should display one line of plain text without alteration' do it 'should display one line of plain text without alteration' do
input = 'A short, plain note' input = 'A short, plain note'
expect(event_note(input)).to match(input) expect(helper.event_note(input)).to match(input)
expect(event_note(input)).not_to match(/\.\.\.\z/) expect(helper.event_note(input)).not_to match(/\.\.\.\z/)
end end
it 'should display inline code' do it 'should display inline code' do
input = 'A note with `inline code`' input = 'A note with `inline code`'
expected = 'A note with <code>inline code</code>' expected = 'A note with <code>inline code</code>'
expect(event_note(input)).to match(expected) expect(helper.event_note(input)).to match(expected)
end end
it 'should truncate a note with multiple paragraphs' do it 'should truncate a note with multiple paragraphs' do
input = "Paragraph 1\n\nParagraph 2" input = "Paragraph 1\n\nParagraph 2"
expected = 'Paragraph 1...' expected = 'Paragraph 1...'
expect(event_note(input)).to match(expected) expect(helper.event_note(input)).to match(expected)
end end
it 'should display the first line of a code block' do it 'should display the first line of a code block' do
input = "```\nCode block\nwith two lines\n```" input = "```\nCode block\nwith two lines\n```"
expected = %r{<pre.+><code>Code block\.\.\.</code></pre>} expected = %r{<pre.+><code>Code block\.\.\.</code></pre>}
expect(event_note(input)).to match(expected) expect(helper.event_note(input)).to match(expected)
end end
it 'should truncate a single long line of text' do it 'should truncate a single long line of text' do
text = 'The quick brown fox jumped over the lazy dog twice' # 50 chars text = 'The quick brown fox jumped over the lazy dog twice' # 50 chars
input = "#{text}#{text}#{text}#{text}" # 200 chars input = text * 4
expected = "#{text}#{text}".sub(/.{3}/, '...') expected = (text * 2).sub(/.{3}/, '...')
expect(event_note(input)).to match(expected) expect(helper.event_note(input)).to match(expected)
end end
it 'should preserve a link href when link text is truncated' do it 'should preserve a link href when link text is truncated' do
...@@ -48,8 +48,8 @@ describe EventsHelper do ...@@ -48,8 +48,8 @@ describe EventsHelper do
input << link_url input << link_url
expected_link_text = 'http://example...</a>' expected_link_text = 'http://example...</a>'
expect(event_note(input)).to match(link_url) expect(helper.event_note(input)).to match(link_url)
expect(event_note(input)).to match(expected_link_text) expect(helper.event_note(input)).to match(expected_link_text)
end end
it 'should preserve code color scheme' do it 'should preserve code color scheme' do
...@@ -59,6 +59,7 @@ describe EventsHelper do ...@@ -59,6 +59,7 @@ describe EventsHelper do
" <span class=\"s1\">\'hello world\'</span>\n" \ " <span class=\"s1\">\'hello world\'</span>\n" \
"<span class=\"k\">end</span>" \ "<span class=\"k\">end</span>" \
'</code></pre>' '</code></pre>'
expect(event_note(input)).to eq(expected) expect(helper.event_note(input)).to eq(expected)
end
end end
end end
require 'spec_helper'
describe Banzai::Filter::WikiLinkFilter, lib: true do
include FilterSpecHelper
let(:namespace) { build_stubbed(:namespace, name: "wiki_link_ns") }
let(:project) { build_stubbed(:empty_project, :public, name: "wiki_link_project", namespace: namespace) }
let(:user) { double }
let(:project_wiki) { ProjectWiki.new(project, user) }
describe "links within the wiki (relative)" do
describe "hierarchical links to the current directory" do
it "doesn't rewrite non-file links" do
link = "<a href='./page'>Link to Page</a>"
filtered_link = filter(link, project_wiki: project_wiki).children[0]
expect(filtered_link.attribute('href').value).to eq('./page')
end
it "doesn't rewrite file links" do
link = "<a href='./page.md'>Link to Page</a>"
filtered_link = filter(link, project_wiki: project_wiki).children[0]
expect(filtered_link.attribute('href').value).to eq('./page.md')
end
end
describe "hierarchical links to the parent directory" do
it "doesn't rewrite non-file links" do
link = "<a href='../page'>Link to Page</a>"
filtered_link = filter(link, project_wiki: project_wiki).children[0]
expect(filtered_link.attribute('href').value).to eq('../page')
end
it "doesn't rewrite file links" do
link = "<a href='../page.md'>Link to Page</a>"
filtered_link = filter(link, project_wiki: project_wiki).children[0]
expect(filtered_link.attribute('href').value).to eq('../page.md')
end
end
describe "hierarchical links to a sub-directory" do
it "doesn't rewrite non-file links" do
link = "<a href='./subdirectory/page'>Link to Page</a>"
filtered_link = filter(link, project_wiki: project_wiki).children[0]
expect(filtered_link.attribute('href').value).to eq('./subdirectory/page')
end
it "doesn't rewrite file links" do
link = "<a href='./subdirectory/page.md'>Link to Page</a>"
filtered_link = filter(link, project_wiki: project_wiki).children[0]
expect(filtered_link.attribute('href').value).to eq('./subdirectory/page.md')
end
end
describe "non-hierarchical links" do
it 'rewrites non-file links to be at the scope of the wiki root' do
link = "<a href='page'>Link to Page</a>"
filtered_link = filter(link, project_wiki: project_wiki).children[0]
expect(filtered_link.attribute('href').value).to match('/wiki_link_ns/wiki_link_project/wikis/page')
end
it "doesn't rewrite file links" do
link = "<a href='page.md'>Link to Page</a>"
filtered_link = filter(link, project_wiki: project_wiki).children[0]
expect(filtered_link.attribute('href').value).to eq('page.md')
end
end
end
describe "links outside the wiki (absolute)" do
it "doesn't rewrite links" do
link = "<a href='http://example.com/page'>Link to Page</a>"
filtered_link = filter(link, project_wiki: project_wiki).children[0]
expect(filtered_link.attribute('href').value).to eq('http://example.com/page')
end
end
end
This diff is collapsed.
...@@ -8,7 +8,7 @@ describe Gitlab::Email::Message::RepositoryPush do ...@@ -8,7 +8,7 @@ describe Gitlab::Email::Message::RepositoryPush do
let!(:author) { create(:author, name: 'Author') } let!(:author) { create(:author, name: 'Author') }
let(:message) do let(:message) do
described_class.new(Notify, project.id, 'recipient@example.com', opts) described_class.new(Notify, project.id, opts)
end end
context 'new commits have been pushed to repository' do context 'new commits have been pushed to repository' do
......
...@@ -56,9 +56,6 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -56,9 +56,6 @@ describe Gitlab::Metrics::Instrumentation do
allow(described_class).to receive(:transaction). allow(described_class).to receive(:transaction).
and_return(transaction) and_return(transaction)
expect(transaction).to receive(:increment).
with(:method_duration, a_kind_of(Numeric))
expect(transaction).to receive(:add_metric). expect(transaction).to receive(:add_metric).
with(described_class::SERIES, an_instance_of(Hash), with(described_class::SERIES, an_instance_of(Hash),
method: 'Dummy.foo') method: 'Dummy.foo')
...@@ -139,9 +136,6 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -139,9 +136,6 @@ describe Gitlab::Metrics::Instrumentation do
allow(described_class).to receive(:transaction). allow(described_class).to receive(:transaction).
and_return(transaction) and_return(transaction)
expect(transaction).to receive(:increment).
with(:method_duration, a_kind_of(Numeric))
expect(transaction).to receive(:add_metric). expect(transaction).to receive(:add_metric).
with(described_class::SERIES, an_instance_of(Hash), with(described_class::SERIES, an_instance_of(Hash),
method: 'Dummy#bar') method: 'Dummy#bar')
......
...@@ -612,7 +612,7 @@ describe Notify do ...@@ -612,7 +612,7 @@ describe Notify do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:tree_path) { namespace_project_tree_path(project.namespace, project, "master") } let(:tree_path) { namespace_project_tree_path(project.namespace, project, "master") }
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :create) } subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :create) }
it_behaves_like 'it should not have Gmail Actions links' it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like "a user cannot unsubscribe through footer link"
...@@ -625,10 +625,6 @@ describe Notify do ...@@ -625,10 +625,6 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender) expect(sender.address).to eq(gitlab_sender)
end end
it 'is sent to recipient' do
is_expected.to deliver_to 'devs@company.name'
end
it 'has the correct subject' do it 'has the correct subject' do
is_expected.to have_subject /Pushed new branch master/ is_expected.to have_subject /Pushed new branch master/
end end
...@@ -643,7 +639,7 @@ describe Notify do ...@@ -643,7 +639,7 @@ describe Notify do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:tree_path) { namespace_project_tree_path(project.namespace, project, "v1.0") } let(:tree_path) { namespace_project_tree_path(project.namespace, project, "v1.0") }
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/tags/v1.0', action: :create) } subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/tags/v1.0', action: :create) }
it_behaves_like 'it should not have Gmail Actions links' it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like "a user cannot unsubscribe through footer link"
...@@ -656,10 +652,6 @@ describe Notify do ...@@ -656,10 +652,6 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender) expect(sender.address).to eq(gitlab_sender)
end end
it 'is sent to recipient' do
is_expected.to deliver_to 'devs@company.name'
end
it 'has the correct subject' do it 'has the correct subject' do
is_expected.to have_subject /Pushed new tag v1\.0/ is_expected.to have_subject /Pushed new tag v1\.0/
end end
...@@ -673,7 +665,7 @@ describe Notify do ...@@ -673,7 +665,7 @@ describe Notify do
let(:example_site_path) { root_path } let(:example_site_path) { root_path }
let(:user) { create(:user) } let(:user) { create(:user) }
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :delete) } subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :delete) }
it_behaves_like 'it should not have Gmail Actions links' it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like "a user cannot unsubscribe through footer link"
...@@ -686,10 +678,6 @@ describe Notify do ...@@ -686,10 +678,6 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender) expect(sender.address).to eq(gitlab_sender)
end end
it 'is sent to recipient' do
is_expected.to deliver_to 'devs@company.name'
end
it 'has the correct subject' do it 'has the correct subject' do
is_expected.to have_subject /Deleted branch master/ is_expected.to have_subject /Deleted branch master/
end end
...@@ -699,7 +687,7 @@ describe Notify do ...@@ -699,7 +687,7 @@ describe Notify do
let(:example_site_path) { root_path } let(:example_site_path) { root_path }
let(:user) { create(:user) } let(:user) { create(:user) }
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/tags/v1.0', action: :delete) } subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/tags/v1.0', action: :delete) }
it_behaves_like 'it should not have Gmail Actions links' it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like "a user cannot unsubscribe through footer link"
...@@ -712,10 +700,6 @@ describe Notify do ...@@ -712,10 +700,6 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender) expect(sender.address).to eq(gitlab_sender)
end end
it 'is sent to recipient' do
is_expected.to deliver_to 'devs@company.name'
end
it 'has the correct subject' do it 'has the correct subject' do
is_expected.to have_subject /Deleted tag v1\.0/ is_expected.to have_subject /Deleted tag v1\.0/
end end
...@@ -730,7 +714,7 @@ describe Notify do ...@@ -730,7 +714,7 @@ describe Notify do
let(:send_from_committer_email) { false } let(:send_from_committer_email) { false }
let(:diff_refs) { [project.merge_base_commit(sample_image_commit.id, sample_commit.id), project.commit(sample_commit.id)] } let(:diff_refs) { [project.merge_base_commit(sample_image_commit.id, sample_commit.id), project.commit(sample_commit.id)] }
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, reverse_compare: false, diff_refs: diff_refs, send_from_committer_email: send_from_committer_email) } subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, reverse_compare: false, diff_refs: diff_refs, send_from_committer_email: send_from_committer_email) }
it_behaves_like 'it should not have Gmail Actions links' it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like "a user cannot unsubscribe through footer link"
...@@ -743,10 +727,6 @@ describe Notify do ...@@ -743,10 +727,6 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender) expect(sender.address).to eq(gitlab_sender)
end end
it 'is sent to recipient' do
is_expected.to deliver_to 'devs@company.name'
end
it 'has the correct subject' do it 'has the correct subject' do
is_expected.to have_subject /\[#{project.path_with_namespace}\]\[master\] #{commits.length} commits:/ is_expected.to have_subject /\[#{project.path_with_namespace}\]\[master\] #{commits.length} commits:/
end end
...@@ -839,7 +819,7 @@ describe Notify do ...@@ -839,7 +819,7 @@ describe Notify do
let(:diff_path) { namespace_project_commit_path(project.namespace, project, commits.first) } let(:diff_path) { namespace_project_commit_path(project.namespace, project, commits.first) }
let(:diff_refs) { [project.merge_base_commit(sample_commit.parent_id, sample_commit.id), project.commit(sample_commit.id)] } let(:diff_refs) { [project.merge_base_commit(sample_commit.parent_id, sample_commit.id), project.commit(sample_commit.id)] }
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, diff_refs: diff_refs) } subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, diff_refs: diff_refs) }
it_behaves_like 'it should show Gmail Actions View Commit link' it_behaves_like 'it should show Gmail Actions View Commit link'
it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like "a user cannot unsubscribe through footer link"
...@@ -852,10 +832,6 @@ describe Notify do ...@@ -852,10 +832,6 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender) expect(sender.address).to eq(gitlab_sender)
end end
it 'is sent to recipient' do
is_expected.to deliver_to 'devs@company.name'
end
it 'has the correct subject' do it 'has the correct subject' do
is_expected.to have_subject /#{commits.first.title}/ is_expected.to have_subject /#{commits.first.title}/
end end
......
...@@ -20,6 +20,15 @@ describe ApplicationSetting, models: true do ...@@ -20,6 +20,15 @@ describe ApplicationSetting, models: true do
it { is_expected.to allow_value(https).for(:after_sign_out_path) } it { is_expected.to allow_value(https).for(:after_sign_out_path) }
it { is_expected.not_to allow_value(ftp).for(:after_sign_out_path) } it { is_expected.not_to allow_value(ftp).for(:after_sign_out_path) }
describe 'disabled_oauth_sign_in_sources validations' do
before do
allow(Devise).to receive(:omniauth_providers).and_return([:github])
end
it { is_expected.to allow_value(['github']).for(:disabled_oauth_sign_in_sources) }
it { is_expected.not_to allow_value(['test']).for(:disabled_oauth_sign_in_sources) }
end
it { is_expected.to validate_presence_of(:max_attachment_size) } it { is_expected.to validate_presence_of(:max_attachment_size) }
it do it do
......
...@@ -44,6 +44,16 @@ describe Subscribable, 'Subscribable' do ...@@ -44,6 +44,16 @@ describe Subscribable, 'Subscribable' do
end end
end end
describe '#subscribe' do
it 'subscribes the given user' do
expect(resource.subscribed?(user)).to be_falsey
resource.subscribe(user)
expect(resource.subscribed?(user)).to be_truthy
end
end
describe '#unsubscribe' do describe '#unsubscribe' do
it 'unsubscribes the given current user' do it 'unsubscribes the given current user' do
resource.subscriptions.create(user: user, subscribed: true) resource.subscriptions.create(user: user, subscribed: true)
......
...@@ -95,13 +95,13 @@ describe WebHook, models: true do ...@@ -95,13 +95,13 @@ describe WebHook, models: true do
it "handles 200 status code" do it "handles 200 status code" do
WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: "Success") WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: "Success")
expect(project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success']) expect(project_hook.execute(@data, 'push_hooks')).to eq([200, 'Success'])
end end
it "handles 2xx status codes" do it "handles 2xx status codes" do
WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: "Success") WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: "Success")
expect(project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success']) expect(project_hook.execute(@data, 'push_hooks')).to eq([201, 'Success'])
end end
end end
end end
...@@ -816,4 +816,23 @@ describe User, models: true do ...@@ -816,4 +816,23 @@ describe User, models: true do
it { is_expected.to eq([private_project]) } it { is_expected.to eq([private_project]) }
end end
describe '#viewable_starred_projects' do
let(:user) { create(:user) }
let(:public_project) { create(:empty_project, :public) }
let(:private_project) { create(:empty_project, :private) }
let(:private_viewable_project) { create(:empty_project, :private) }
before do
private_viewable_project.team << [user, Gitlab::Access::MASTER]
[public_project, private_project, private_viewable_project].each do |project|
user.toggle_star(project)
end
end
it 'returns only starred projects the user can view' do
expect(user.viewable_starred_projects).not_to include(private_project)
end
end
end end
...@@ -623,6 +623,12 @@ describe API::API, api: true do ...@@ -623,6 +623,12 @@ describe API::API, api: true do
expect(response.status).to eq(404) expect(response.status).to eq(404)
end end
it 'returns 404 if the issue is confidential' do
post api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscription", non_member)
expect(response.status).to eq(404)
end
end end
describe 'DELETE :id/issues/:issue_id/subscription' do describe 'DELETE :id/issues/:issue_id/subscription' do
...@@ -644,5 +650,11 @@ describe API::API, api: true do ...@@ -644,5 +650,11 @@ describe API::API, api: true do
expect(response.status).to eq(404) expect(response.status).to eq(404)
end end
it 'returns 404 if the issue is confidential' do
delete api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscription", non_member)
expect(response.status).to eq(404)
end
end end
end end
...@@ -190,4 +190,86 @@ describe API::API, api: true do ...@@ -190,4 +190,86 @@ describe API::API, api: true do
expect(json_response['message']['color']).to eq(['must be a valid color code']) expect(json_response['message']['color']).to eq(['must be a valid color code'])
end end
end end
describe "POST /projects/:id/labels/:label_id/subscription" do
context "when label_id is a label title" do
it "should subscribe to the label" do
post api("/projects/#{project.id}/labels/#{label1.title}/subscription", user)
expect(response.status).to eq(201)
expect(json_response["name"]).to eq(label1.title)
expect(json_response["subscribed"]).to be_truthy
end
end
context "when label_id is a label ID" do
it "should subscribe to the label" do
post api("/projects/#{project.id}/labels/#{label1.id}/subscription", user)
expect(response.status).to eq(201)
expect(json_response["name"]).to eq(label1.title)
expect(json_response["subscribed"]).to be_truthy
end
end
context "when user is already subscribed to label" do
before { label1.subscribe(user) }
it "should return 304" do
post api("/projects/#{project.id}/labels/#{label1.id}/subscription", user)
expect(response.status).to eq(304)
end
end
context "when label ID is not found" do
it "should a return 404 error" do
post api("/projects/#{project.id}/labels/1234/subscription", user)
expect(response.status).to eq(404)
end
end
end
describe "DELETE /projects/:id/labels/:label_id/subscription" do
before { label1.subscribe(user) }
context "when label_id is a label title" do
it "should unsubscribe from the label" do
delete api("/projects/#{project.id}/labels/#{label1.title}/subscription", user)
expect(response.status).to eq(200)
expect(json_response["name"]).to eq(label1.title)
expect(json_response["subscribed"]).to be_falsey
end
end
context "when label_id is a label ID" do
it "should unsubscribe from the label" do
delete api("/projects/#{project.id}/labels/#{label1.id}/subscription", user)
expect(response.status).to eq(200)
expect(json_response["name"]).to eq(label1.title)
expect(json_response["subscribed"]).to be_falsey
end
end
context "when user is already unsubscribed from label" do
before { label1.unsubscribe(user) }
it "should return 304" do
delete api("/projects/#{project.id}/labels/#{label1.id}/subscription", user)
expect(response.status).to eq(304)
end
end
context "when label ID is not found" do
it "should a return 404 error" do
delete api("/projects/#{project.id}/labels/1234/subscription", user)
expect(response.status).to eq(404)
end
end
end
end end
...@@ -10,20 +10,20 @@ describe API::API, api: true do ...@@ -10,20 +10,20 @@ describe API::API, api: true do
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) } let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) }
let(:project3) { create(:project, path: 'project3', creator_id: user.id, namespace: user.namespace) }
let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') } let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') }
let(:project_member) { create(:project_member, :master, user: user, project: project) } let(:project_member) { create(:project_member, :master, user: user, project: project) }
let(:project_member2) { create(:project_member, :developer, user: user3, project: project) } let(:project_member2) { create(:project_member, :developer, user: user3, project: project) }
let(:user4) { create(:user) } let(:user4) { create(:user) }
let(:project3) do let(:project3) do
create(:project, create(:project,
:private,
name: 'second_project', name: 'second_project',
path: 'second_project', path: 'second_project',
creator_id: user.id, creator_id: user.id,
namespace: user.namespace, namespace: user.namespace,
merge_requests_enabled: false, merge_requests_enabled: false,
issues_enabled: false, wiki_enabled: false, issues_enabled: false, wiki_enabled: false,
snippets_enabled: false, visibility_level: 0) snippets_enabled: false)
end end
let(:project_member3) do let(:project_member3) do
create(:project_member, create(:project_member,
...@@ -164,21 +164,18 @@ describe API::API, api: true do ...@@ -164,21 +164,18 @@ describe API::API, api: true do
end end
describe 'GET /projects/starred' do describe 'GET /projects/starred' do
let(:public_project) { create(:project, :public) }
before do before do
admin.starred_projects << project project_member2
admin.save! user3.update_attributes(starred_projects: [project, project2, project3, public_project])
end end
it 'should return the starred projects' do it 'should return the starred projects viewable by the user' do
get api('/projects/all', admin) get api('/projects/starred', user3)
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id)
expect(json_response).to satisfy do |response|
response.one? do |entry|
entry['name'] == project.name
end
end
end end
end end
......
...@@ -128,3 +128,10 @@ describe Admin::EmailsController, "routing" do ...@@ -128,3 +128,10 @@ describe Admin::EmailsController, "routing" do
expect(post("/admin/email")).to route_to('admin/emails#create') expect(post("/admin/email")).to route_to('admin/emails#create')
end end
end end
# admin_health_check GET /admin/health_check(.:format) admin/health_check#show
describe Admin::HealthCheckController, "routing" do
it "to #show" do
expect(get("/admin/health_check")).to route_to('admin/health_check#show')
end
end
require 'spec_helper' require 'spec_helper'
# user GET /u/:username/
# user_groups GET /u/:username/groups(.:format)
# user_projects GET /u/:username/projects(.:format)
# user_contributed_projects GET /u/:username/contributed(.:format)
# user_snippets GET /u/:username/snippets(.:format)
# user_calendar GET /u/:username/calendar(.:format)
# user_calendar_activities GET /u/:username/calendar_activities(.:format)
describe UsersController, "routing" do
it "to #show" do
expect(get("/u/User")).to route_to('users#show', username: 'User')
end
it "to #groups" do
expect(get("/u/User/groups")).to route_to('users#groups', username: 'User')
end
it "to #projects" do
expect(get("/u/User/projects")).to route_to('users#projects', username: 'User')
end
it "to #contributed" do
expect(get("/u/User/contributed")).to route_to('users#contributed', username: 'User')
end
it "to #snippets" do
expect(get("/u/User/snippets")).to route_to('users#snippets', username: 'User')
end
it "to #calendar" do
expect(get("/u/User/calendar")).to route_to('users#calendar', username: 'User')
end
it "to #calendar_activities" do
expect(get("/u/User/calendar_activities")).to route_to('users#calendar_activities', username: 'User')
end
end
# search GET /search(.:format) search#show # search GET /search(.:format) search#show
describe SearchController, "routing" do describe SearchController, "routing" do
it "to #show" do it "to #show" do
...@@ -27,10 +64,6 @@ end ...@@ -27,10 +64,6 @@ end
# PUT /snippets/:id(.:format) snippets#update # PUT /snippets/:id(.:format) snippets#update
# DELETE /snippets/:id(.:format) snippets#destroy # DELETE /snippets/:id(.:format) snippets#destroy
describe SnippetsController, "routing" do describe SnippetsController, "routing" do
it "to #user_index" do
expect(get("/s/User")).to route_to('snippets#index', username: 'User')
end
it "to #raw" do it "to #raw" do
expect(get("/snippets/1/raw")).to route_to('snippets#raw', id: '1') expect(get("/snippets/1/raw")).to route_to('snippets#raw', id: '1')
end end
...@@ -243,3 +276,13 @@ describe "Groups", "routing" do ...@@ -243,3 +276,13 @@ describe "Groups", "routing" do
expect(get('/1')).to route_to('namespaces#show', id: '1') expect(get('/1')).to route_to('namespaces#show', id: '1')
end end
end end
describe HealthCheckController, 'routing' do
it 'to #index' do
expect(get('/health_check')).to route_to('health_check#index')
end
it 'also supports passing checks in the url' do
expect(get('/health_check/email')).to route_to('health_check#index', checks: 'email')
end
end
...@@ -64,7 +64,7 @@ describe Projects::CreateService, services: true do ...@@ -64,7 +64,7 @@ describe Projects::CreateService, services: true do
@path = ProjectWiki.new(@project, @user).send(:path_to_repo) @path = ProjectWiki.new(@project, @user).send(:path_to_repo)
end end
it { expect(File.exists?(@path)).to be_truthy } it { expect(File.exist?(@path)).to be_truthy }
end end
context 'wiki_enabled false does not create wiki repository directory' do context 'wiki_enabled false does not create wiki repository directory' do
...@@ -74,7 +74,7 @@ describe Projects::CreateService, services: true do ...@@ -74,7 +74,7 @@ describe Projects::CreateService, services: true do
@path = ProjectWiki.new(@project, @user).send(:path_to_repo) @path = ProjectWiki.new(@project, @user).send(:path_to_repo)
end end
it { expect(File.exists?(@path)).to be_falsey } it { expect(File.exist?(@path)).to be_falsey }
end end
end end
......
This diff is collapsed.
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