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:
# Check for deprecated class method calls.
Lint/DeprecatedClassMethods:
Enabled: false
Enabled: true
# Check for duplicate method definitions.
Lint/DuplicateMethods:
......
Please view this file on the master branch, on stable branches it's out of date.
v 8.8.0 (unreleased)
- Snippets tab under user profile. !4001 (Long Nguyen)
- Fix error when using link to uploads in global snippets
- Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen)
- Use a case-insensitive comparison in sanitizing URI schemes
......@@ -13,6 +14,7 @@ v 8.8.0 (unreleased)
- 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
- Upgrade Sidekiq to 4.1.2
- Added /health_check endpoint for checking service status
- Sanitize repo paths in new project error message
- Bump mail_room to 0.7.0 to fix stuck IDLE connections
- Remove future dates from contribution calendar graph.
......@@ -21,6 +23,7 @@ v 8.8.0 (unreleased)
- 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
- 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
- Display informative message when new milestone is created
- Sanitize milestones and labels titles
......@@ -30,10 +33,12 @@ v 8.8.0 (unreleased)
- Backport GitHub Enterprise import support from EE
- Create tags using Rugged for performance reasons. !3745
- 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
- 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
- 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)
- 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)
......@@ -41,6 +46,15 @@ v 8.8.0 (unreleased)
- Fix unintentional filtering bug in issues sorted by milestone due (Takuya Noguchi)
- 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
- 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
- Links for Redmine issue references are generated correctly again !4048 (Benedikt Huss)
......
......@@ -342,3 +342,6 @@ gem 'oauth2', '~> 1.0.0'
# Soft deletion
gem "paranoia", "~> 2.0"
# Health check
gem 'health_check', '~> 1.5.1'
......@@ -426,6 +426,8 @@ GEM
html2haml (>= 1.0.1)
railties (>= 4.0.1)
hashie (3.4.3)
health_check (1.5.1)
rails (>= 2.3.0)
highline (1.7.8)
hipchat (1.5.2)
httparty
......@@ -977,6 +979,7 @@ DEPENDENCIES
grape-entity (~> 0.4.2)
gssapi
haml-rails (~> 0.9.0)
health_check (~> 1.5.1)
hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0)
httparty (~> 0.13.3)
......
class CiBuild
@interval: null
@state: null
constructor: (build_url, build_status) ->
constructor: (build_url, build_status, build_state) ->
clearInterval(CiBuild.interval)
@state = build_state
@initScrollButtonAffix()
if build_status == "running" || build_status == "pending"
......@@ -26,14 +29,18 @@ class CiBuild
CiBuild.interval = setInterval =>
if window.location.href.split("#").first() is build_url
$.ajax
url: build_url
url: build_url + "/trace.json?state=" + encodeURIComponent(@state)
dataType: "json"
success: (build) =>
if build.status == "running"
$('#build-trace code').html build.trace_html
success: (log) =>
@state = log.state
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"/>'
@checkAutoscroll()
else if build.status != build_status
else if log.status isnt build_status
Turbolinks.visit build_url
, 4000
......
......@@ -26,6 +26,10 @@
# Personal projects
# </a>
# </li>
# <li class="snippets-tab">
# <a data-action="snippets" data-target="#snippets" data-toggle="tab" href="/u/username/snippets">
# </a>
# </li>
# </ul>
#
# <div class="tab-content">
......@@ -41,6 +45,9 @@
# <div class="tab-pane" id="projects">
# Projects content
# </div>
# <div class="tab-pane" id="snippets">
# Snippets content
# </div>
# </div>
#
# <div class="loading-status">
......@@ -100,7 +107,7 @@ class @UserTabs
if action is 'activity'
@loadActivities(source)
if action in ['groups', 'contributed', 'projects']
if action in ['groups', 'contributed', 'projects', 'snippets']
@loadTab(source, action)
loadTab: (source, action) ->
......
......@@ -19,6 +19,12 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
redirect_to admin_runners_path
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
RepositoryCheck::ClearWorker.perform_async
......@@ -53,6 +59,12 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
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(
:default_projects_limit,
:default_branch_protection,
......@@ -97,7 +109,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:repository_checks_enabled,
:metrics_packet_size,
restricted_visibility_levels: [],
import_sources: []
import_sources: [],
disabled_oauth_sign_in_sources: []
)
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
end
def runner_params
params.require(:runner).permit(:token, :description, :tag_list, :active)
params.require(:runner).permit(Ci::Runner::FORM_EDITABLE)
end
end
......@@ -28,7 +28,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
end
def starred
@projects = current_user.starred_projects.sorted_by_activity
@projects = current_user.viewable_starred_projects.sorted_by_activity
@projects = filter_projects(@projects)
@projects = @projects.includes(:namespace, :forked_from_project, :tags)
@projects = @projects.sort(@sort = params[:sort])
......
......@@ -25,7 +25,7 @@ class DashboardController < Dashboard::ApplicationController
def load_events
projects =
if params[:filter] == "starred"
current_user.starred_projects
current_user.viewable_starred_projects
else
current_user.authorized_projects
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
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
unless @build.retryable?
return render_404
......
......@@ -27,8 +27,10 @@ class Projects::HooksController < Projects::ApplicationController
if !@project.empty_repo?
status, message = TestHookService.new.execute(hook, current_user)
if status
flash[:notice] = 'Hook successfully executed.'
if status && status >= 200 && status < 400
flash[:notice] = "Hook executed successfully: HTTP #{status}"
elsif status
flash[:alert] = "Hook executed successfully but returned HTTP #{status} #{message}"
else
flash[:alert] = "Hook execution failed: #{message}"
end
......
......@@ -64,6 +64,6 @@ class Projects::RunnersController < Projects::ApplicationController
end
def runner_params
params.require(:runner).permit(:description, :tag_list, :active)
params.require(:runner).permit(Ci::Runner::FORM_EDITABLE)
end
end
......@@ -10,7 +10,7 @@ class SnippetsController < ApplicationController
# Allow destroy snippet
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'
respond_to :html
......
......@@ -58,6 +58,19 @@ class UsersController < ApplicationController
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
calendar = contributions_calendar
@timestamps = calendar.timestamps
......@@ -116,6 +129,15 @@ class UsersController < ApplicationController
@groups = JoinedGroupsFinder.new(user).execute(current_user)
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
ProjectsFinder.new.execute(current_user)
end
......
......@@ -64,4 +64,18 @@ module ApplicationSettingsHelper
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
......@@ -42,6 +42,16 @@ module AuthHelper
auth_providers.reject { |provider| form_based_provider?(provider) }
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)
label = label_for_provider(provider)
......
......@@ -39,15 +39,6 @@ module EventsHelper
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)
if event.push? || event.commented? || event.target
"at"
......
......@@ -138,10 +138,10 @@ module ProjectsHelper
private
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)
nav_tabs << [:files, :commits, :network, :graphs]
nav_tabs << [:files, :commits, :network, :graphs, :forks]
end
if project.repo_exists? && can?(current_user, :read_merge_request, project)
......
......@@ -59,9 +59,9 @@ module Emails
subject: subject("Project was moved"))
end
def repository_push_email(project_id, recipient, opts = {})
def repository_push_email(project_id, opts = {})
@message =
Gitlab::Email::Message::RepositoryPush.new(self, project_id, recipient, opts)
Gitlab::Email::Message::RepositoryPush.new(self, project_id, opts)
# used in notify layout
@target_url = @message.target_url
......@@ -72,7 +72,6 @@ module Emails
mail(from: sender(@message.author_id, @message.send_from_committer_email?),
reply_to: @message.reply_to,
to: @message.recipient,
subject: @message.subject)
end
end
......
class ApplicationSetting < ActiveRecord::Base
include TokenAuthenticatable
add_authentication_token_field :runners_registration_token
add_authentication_token_field :health_check_access_token
CACHE_KEY = 'application_setting.last'
serialize :restricted_visibility_levels
serialize :import_sources
serialize :disabled_oauth_sign_in_sources
serialize :restricted_signup_domains, Array
attr_accessor :restricted_signup_domains_raw
......@@ -69,7 +71,18 @@ class ApplicationSetting < ActiveRecord::Base
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_health_check_access_token
after_commit do
Rails.cache.write(CACHE_KEY, self)
......@@ -107,6 +120,7 @@ class ApplicationSetting < ActiveRecord::Base
recaptcha_enabled: false,
akismet_enabled: false,
repository_checks_enabled: true,
disabled_oauth_sign_in_sources: []
)
end
......@@ -133,4 +147,8 @@ class ApplicationSetting < ActiveRecord::Base
def runners_registration_token
ensure_runners_registration_token!
end
def health_check_access_token
ensure_health_check_access_token!
end
end
......@@ -95,8 +95,12 @@ module Ci
end
def trace_html
html = Ci::Ansi2html::convert(trace) if trace.present?
html || ''
trace_with_state[:html] || ''
end
def trace_with_state(state = nil)
trace_with_state = Ci::Ansi2html::convert(trace, state) if trace.present?
trace_with_state || {}
end
def timeout
......@@ -201,7 +205,7 @@ module Ci
end
def recreate_trace_dir
unless Dir.exists?(dir_to_trace)
unless Dir.exist?(dir_to_trace)
FileUtils.mkdir_p(dir_to_trace)
end
end
......
......@@ -3,7 +3,8 @@ module Ci
extend Ci::Model
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 :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
......
......@@ -36,6 +36,12 @@ module Subscribable
update(subscribed: !subscribed?(user))
end
def subscribe(user)
subscriptions.
find_or_initialize_by(user_id: user.id).
update(subscribed: true)
end
def unsubscribe(user)
subscriptions.
find_or_initialize_by(user_id: user.id).
......
......@@ -38,7 +38,7 @@ class WebHook < ActiveRecord::Base
basic_auth: auth)
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
logger.error("WebHook Error => #{e}")
[false, e.to_s]
......
......@@ -570,7 +570,7 @@ class MergeRequest < ActiveRecord::Base
end
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
def ensure_ref_fetched
......
......@@ -413,6 +413,11 @@ class User < ActiveRecord::Base
Project.where("projects.id IN (#{projects_union.to_sql})")
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
@owned_projects ||=
Project.where('namespace_id IN (?) OR namespace_id = ?',
......
......@@ -25,7 +25,7 @@ module Projects
trash_repositories!
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)
true
end
......
......@@ -25,7 +25,7 @@ module Projects
# Check if we did extract public directory
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?
deploy_page!(archive_public_path)
......
......@@ -109,6 +109,13 @@
= f.label :signin_enabled do
= f.check_box :signin_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
= f.label :two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2'
.col-sm-10
......
......@@ -20,7 +20,7 @@
= 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
#{(@scope || 'running').capitalize} builds
#{(@scope || 'all').capitalize} builds
%ul.content-list
- 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 @@
%h4 This runner will process builds only from ASSIGNED projects
%p You can't make this a shared runner.
%hr
= form_for @runner, url: admin_runner_path(@runner), html: { class: 'form-horizontal' } do |f|
.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 builds to only use runners with specific tags
.form-actions
= f.submit 'Save', class: 'btn btn-save'
.append-bottom-20
= render '/projects/runners/form', runner: @runner, runner_form_url: admin_runner_path(@runner)
.row
.col-md-6
......
......@@ -4,7 +4,7 @@
= render 'devise/shared/signin_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
= render 'devise/shared/omniauth_box'
......
%p
%span.light
Sign in with &nbsp;
- providers = button_based_providers
- providers = enabled_button_based_providers
- providers.each do |provider|
%span.light
- has_icon = provider_has_icon?(provider)
......
......@@ -27,6 +27,7 @@
%li
= link_to dashboard_todos_path, title: 'Todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('bell fw')
- unless todos_pending_count == 0
%span.badge.todos-pending-count
= todos_pending_count
- if current_user.can_create_project?
......
......@@ -41,6 +41,11 @@
= icon('file-text fw')
%span
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
= link_to admin_broadcast_messages_path, title: 'Messages' do
= icon('bullhorn fw')
......
......@@ -36,7 +36,7 @@
%span CI Lint
.row-content-block
#{(@scope || 'running').capitalize} builds from this project
#{(@scope || 'all').capitalize} builds from this project
%ul.content-list
- if @builds.blank?
......
- page_title "#{@build.name} (##{@build.id})", "Builds"
= render "header_title"
- trace_with_state = @build.trace_with_state
.build-page
.row-content-block.top-block
......@@ -85,7 +86,9 @@
%pre.trace#build-trace
%code.bash
= 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
......@@ -216,4 +219,4 @@
: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 @@
- 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
.col-md-2.hidden-xs.hidden-sm
%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 @@
%h4 Runner ##{@runner.id}
%hr
= form_for @runner, url: runner_path(@runner), 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'
= render 'form', runner: @runner, runner_form_url: runner_path(@runner)
......@@ -81,6 +81,9 @@
%li.projects-tab
= link_to user_projects_path, data: {target: 'div#projects', action: 'projects', toggle: 'tab'} do
Personal projects
%li.snippets-tab
= link_to user_snippets_path, data: {target: 'div#snippets', action: 'snippets', toggle: 'tab'} do
Snippets
%div{ class: container_class }
.tab-content
......@@ -104,6 +107,9 @@
#projects.tab-pane
- # This tab is always loaded via AJAX
#snippets.tab-pane
- # This tab is always loaded via AJAX
.loading-status
= spinner
......
class EmailsOnPushWorker
include Sidekiq::Worker
attr_reader :email, :skip_premailer
def perform(project_id, recipients, push_data, options = {})
options.symbolize_keys!
options.reverse_merge!(
......@@ -46,9 +48,9 @@ class EmailsOnPushWorker
recipients.split.each do |recipient|
begin
Notify.repository_push_email(
project_id,
send_email(
recipient,
project_id,
author_id: author_id,
ref: ref,
action: action,
......@@ -57,14 +59,29 @@ class EmailsOnPushWorker
diff_refs: diff_refs,
send_from_committer_email: send_from_committer_email,
disable_diffs: disable_diffs
).deliver_now
)
# These are input errors and won't be corrected even if Sidekiq retries
rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e
logger.info("Failed to send e-mail for project '#{project.name_with_namespace}' to #{recipient}: #{e}")
end
end
ensure
@email = nil
compare = nil
GC.start
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
......@@ -3,4 +3,4 @@ require 'rubygems'
# Set up gems listed in the Gemfile.
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:]\.\-\+]/
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]
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
mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq
end
# Health check
get 'health_check(/:checks)' => 'health_check#index', as: :health_check
# Enable Grack support
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
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
......@@ -260,6 +264,7 @@ Rails.application.routes.draw do
end
resource :logs, only: [:show]
resource :health_check, controller: 'health_check', only: [:show]
resource :background_jobs, controller: 'background_jobs', only: [:show]
resource :email, only: [:show, :create]
......@@ -292,6 +297,7 @@ Rails.application.routes.draw do
resource :application_settings, only: [:show, :update] do
resources :services
put :reset_runners_token
put :reset_health_check_token
put :clear_repository_check_states
end
......@@ -360,23 +366,18 @@ Rails.application.routes.draw do
end
end
get 'u/:username/calendar' => 'users#calendar', as: :user_calendar,
constraints: { username: /.*/ }
get 'u/:username/calendar_activities' => 'users#calendar_activities', as: :user_calendar_activities,
constraints: { username: /.*/ }
get 'u/:username/groups' => 'users#groups', as: :user_groups,
constraints: { username: /.*/ }
get 'u/:username/projects' => 'users#projects', as: :user_projects,
constraints: { username: /.*/ }
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)/ }
scope(path: 'u/:username',
as: :user,
constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ },
controller: :users) do
get :calendar
get :calendar_activities
get :groups
get :projects
get :contributed, as: :contributed_projects
get :snippets
get '/', action: :show
end
#
# Dashboard Area
......@@ -724,6 +725,7 @@ Rails.application.routes.draw do
post :cancel
post :retry
post :erase
get :trace
get :raw
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 @@
#
# 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
enable_extension "plpgsql"
......@@ -83,6 +83,8 @@ ActiveRecord::Schema.define(version: 20160508194200) do
t.boolean "repository_checks_enabled", default: false
t.text "shared_runners_text"
t.integer "metrics_packet_size", default: 1
t.text "disabled_oauth_sign_in_sources"
t.string "health_check_access_token"
end
create_table "approvals", force: :cascade do |t|
......
......@@ -165,3 +165,73 @@ Example response:
"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
### Clone the Source
# 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
......
......@@ -27,18 +27,19 @@ To enable the CAS OmniAuth provider you must register your application with your
```ruby
gitlab_rails['omniauth_providers'] = [
{
name: "cas3",
label: "cas",
args: {
url: 'CAS_SERVER',
login_url: '/CAS_PATH/login',
service_validate_url: '/CAS_PATH/p3/serviceValidate',
logout_url: '/CAS_PATH/logout'} }
"name"=> "cas3",
"label"=> "cas",
"args"=> {
"url"=> 'CAS_SERVER',
"login_url"=> '/CAS_PATH/login',
"service_validate_url"=> '/CAS_PATH/p3/serviceValidate',
"logout_url"=> '/CAS_PATH/logout'
}
}
]
```
For installations from source:
```
......@@ -57,6 +58,8 @@ To enable the CAS OmniAuth provider you must register your application with your
1. Save the configuration file.
1. Run `gitlab-ctl reconfigure` for the omnibus package.
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.
......@@ -11,6 +11,7 @@ of the configured mechanisms.
- [Supported Providers](#supported-providers)
- [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)
- [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
......@@ -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,
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
group. You can add the group level web hooks on the group settings page
**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
By default, the SSL certificate of the webhook endpoint is verified based on
......
......@@ -59,7 +59,7 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps
step 'hook should be triggered' do
expect(current_path).to eq namespace_project_hooks_path(current_project.namespace, current_project)
expect(page).to have_selector '.flash-notice',
text: 'Hook successfully executed.'
text: 'Hook executed successfully: HTTP 200'
end
step 'I should see hook error message' do
......
......@@ -62,5 +62,6 @@ module API
mount ::API::Variables
mount ::API::Runners
mount ::API::Licenses
mount ::API::Subscriptions
end
end
......@@ -325,6 +325,10 @@ module API
class Label < Grape::Entity
expose :name, :color, :description
expose :open_issues_count, :closed_issues_count, :open_merge_requests_count
expose :subscribed do |label, options|
label.subscribed?(options[:current_user])
end
end
class Compare < Grape::Entity
......
......@@ -95,6 +95,17 @@ module API
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)
relation.page(params[:page]).per(params[:per_page].to_i).tap do |data|
add_pagination_headers(data)
......
......@@ -103,8 +103,7 @@ module API
# Example Request:
# GET /projects/:id/issues/:issue_id
get ":id/issues/:issue_id" do
@issue = user_project.issues.find(params[:issue_id])
not_found! unless can?(current_user, :read_issue, @issue)
@issue = find_project_issue(params[:issue_id])
present @issue, with: Entities::Issue, current_user: current_user
end
......@@ -234,42 +233,6 @@ module API
authorize!(:destroy_issue, issue)
issue.destroy
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
......@@ -11,7 +11,7 @@ module API
# Example Request:
# GET /projects/:id/labels
get ':id/labels' do
present user_project.labels, with: Entities::Label
present user_project.labels, with: Entities::Label, current_user: current_user
end
# Creates a new label
......@@ -36,7 +36,7 @@ module API
label = user_project.labels.create(attrs)
if label.valid?
present label, with: Entities::Label
present label, with: Entities::Label, current_user: current_user
else
render_validation_error!(label)
end
......@@ -90,7 +90,7 @@ module API
attrs[:name] = attrs.delete(:new_name) if attrs.key?(:new_name)
if label.update(attrs)
present label, with: Entities::Label
present label, with: Entities::Label, current_user: current_user
else
render_validation_error!(label)
end
......
......@@ -327,42 +327,6 @@ module API
issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
present paginate(issues), with: Entities::Issue, current_user: current_user
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
......
......@@ -44,7 +44,7 @@ module API
# Example Request:
# GET /projects/starred
get '/starred' do
@projects = current_user.starred_projects
@projects = current_user.viewable_starred_projects
@projects = filter_projects(@projects)
@projects = paginate @projects
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
end
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)
if uri.relative? && uri.path.present?
......@@ -40,12 +40,17 @@ module Banzai
uri
end
def project_wiki
context[:project_wiki]
end
def file_reference?(html_attr)
!File.extname(html_attr.value).blank?
end
def project_wiki
context[:project_wiki]
# Of the form `./link`, `../link`, or similar
def hierarchical_link?(html_attr)
html_attr.value[0] == '.'
end
def project_wiki_base_path
......
......@@ -23,8 +23,8 @@ module Ci
cross: 0x10,
}
def self.convert(ansi)
Converter.new().convert(ansi)
def self.convert(ansi, state = nil)
Converter.new.convert(ansi, state)
end
class Converter
......@@ -84,22 +84,36 @@ module Ci
def on_107(s) set_bg_color(7, 'l') end
def on_109(s) set_bg_color(9, 'l') end
def convert(ansi)
@out = ""
@n_open_tags = 0
reset()
attr_accessor :offset, :n_open_tags, :fg_color, :bg_color, :style_mask
STATE_PARAMS = [:offset, :n_open_tags, :fg_color, :bg_color, :style_mask]
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?)
if s.scan(/\e([@-_])(.*?)([@-~])/)
handle_sequence(s)
elsif s.scan(/\e(([@-_])(.*?)?)?$/)
break
elsif s.scan(/</)
@out << '&lt;'
else
@out << s.scan(/./m)
end
@offset += s.matched_size
end
close_open_tags()
@out
{ state: state, html: @out, text: ansi[0, @offset - start], append: start > 0 }
end
def handle_sequence(s)
......@@ -121,6 +135,20 @@ module Ci
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 = []
unless @fg_color.nil?
......@@ -138,20 +166,8 @@ module Ci
css_classes << "term-#{css_class}" if @style_mask & flag != 0
end
open_new_tag(css_classes) if css_classes.length > 0
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
return if css_classes.empty?
def open_new_tag(css_classes)
@out << %{<span class="#{css_classes.join(' ')}">}
@n_open_tags += 1
end
......@@ -163,6 +179,31 @@ module Ci
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
@fg_color = nil
@bg_color = nil
......
......@@ -224,7 +224,7 @@ module Gitlab
# exists?('gitlab/cookies.git')
#
def exists?(dir_name)
File.exists?(full_path(dir_name))
File.exist?(full_path(dir_name))
end
# Push branch to remote repository
......
......@@ -2,7 +2,6 @@ module Gitlab
module Email
module Message
class RepositoryPush
attr_accessor :recipient
attr_reader :author_id, :ref, :action
include Gitlab::Routing.url_helpers
......@@ -12,13 +11,12 @@ module Gitlab
delegate :name, 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
opts[:author_id] && opts[:ref] && opts[:action]
@notify = notify
@project_id = project_id
@recipient = recipient
@opts = opts.dup
@author_id = @opts.delete(:author_id)
......
......@@ -88,7 +88,7 @@ module Gitlab
def self.redis_connection
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]
else
'redis://localhost:6379'
......
......@@ -154,8 +154,6 @@ module Gitlab
duration = (Time.now - start) * 1000.0
if duration >= Gitlab::Metrics.method_call_threshold
trans.increment(:method_duration, duration)
trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES,
{ duration: duration },
method: #{label.inspect})
......
......@@ -42,7 +42,7 @@ module Gitlab
config_file = File.expand_path('../../../config/resque.yml', __FILE__)
@url = "redis://localhost:6379"
if File.exists?(config_file)
if File.exist?(config_file)
@url =YAML.load_file(config_file)[rails_env]
end
end
......
......@@ -43,7 +43,7 @@ describe "mail_room.yml" do
redis_config_file = Rails.root.join('config', 'resque.yml')
redis_url =
if File.exists?(redis_config_file)
if File.exist?(redis_config_file)
YAML.load_file(redis_config_file)[Rails.env]
else
"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
expect(response).to render_template('calendar_activities')
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
......@@ -19,6 +19,7 @@ describe 'Admin Builds' do
visit admin_builds_path
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).to have_link 'Cancel all'
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
end
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.ref }
it { expect(page).to have_content @build.name }
......
......@@ -2,7 +2,7 @@ require "spec_helper"
describe AuthHelper 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] }
expect(helper.button_based_providers).to include(*[:twitter, :github])
end
......@@ -17,4 +17,49 @@ describe AuthHelper do
expect(helper.button_based_providers).to eq([])
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
require 'spec_helper'
describe EventsHelper do
include ApplicationHelper
include GitlabMarkdownHelper
let(:current_user) { create(:user, email: "current@email.com") }
describe '#event_note' do
before do
allow(helper).to receive(:current_user).and_return(double)
end
it 'should display one line of plain text without alteration' do
input = 'A short, plain note'
expect(event_note(input)).to match(input)
expect(event_note(input)).not_to match(/\.\.\.\z/)
expect(helper.event_note(input)).to match(input)
expect(helper.event_note(input)).not_to match(/\.\.\.\z/)
end
it 'should display inline code' do
input = 'A note with `inline 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
it 'should truncate a note with multiple paragraphs' do
input = "Paragraph 1\n\nParagraph 2"
expected = 'Paragraph 1...'
expect(event_note(input)).to match(expected)
expect(helper.event_note(input)).to match(expected)
end
it 'should display the first line of a code block' do
input = "```\nCode block\nwith two lines\n```"
expected = %r{<pre.+><code>Code block\.\.\.</code></pre>}
expect(event_note(input)).to match(expected)
expect(helper.event_note(input)).to match(expected)
end
it 'should truncate a single long line of text' do
text = 'The quick brown fox jumped over the lazy dog twice' # 50 chars
input = "#{text}#{text}#{text}#{text}" # 200 chars
expected = "#{text}#{text}".sub(/.{3}/, '...')
input = text * 4
expected = (text * 2).sub(/.{3}/, '...')
expect(event_note(input)).to match(expected)
expect(helper.event_note(input)).to match(expected)
end
it 'should preserve a link href when link text is truncated' do
......@@ -48,8 +48,8 @@ describe EventsHelper do
input << link_url
expected_link_text = 'http://example...</a>'
expect(event_note(input)).to match(link_url)
expect(event_note(input)).to match(expected_link_text)
expect(helper.event_note(input)).to match(link_url)
expect(helper.event_note(input)).to match(expected_link_text)
end
it 'should preserve code color scheme' do
......@@ -59,6 +59,7 @@ describe EventsHelper do
" <span class=\"s1\">\'hello world\'</span>\n" \
"<span class=\"k\">end</span>" \
'</code></pre>'
expect(event_note(input)).to eq(expected)
expect(helper.event_note(input)).to eq(expected)
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
let!(:author) { create(:author, name: 'Author') }
let(:message) do
described_class.new(Notify, project.id, 'recipient@example.com', opts)
described_class.new(Notify, project.id, opts)
end
context 'new commits have been pushed to repository' do
......
......@@ -56,9 +56,6 @@ describe Gitlab::Metrics::Instrumentation do
allow(described_class).to receive(:transaction).
and_return(transaction)
expect(transaction).to receive(:increment).
with(:method_duration, a_kind_of(Numeric))
expect(transaction).to receive(:add_metric).
with(described_class::SERIES, an_instance_of(Hash),
method: 'Dummy.foo')
......@@ -139,9 +136,6 @@ describe Gitlab::Metrics::Instrumentation do
allow(described_class).to receive(:transaction).
and_return(transaction)
expect(transaction).to receive(:increment).
with(:method_duration, a_kind_of(Numeric))
expect(transaction).to receive(:add_metric).
with(described_class::SERIES, an_instance_of(Hash),
method: 'Dummy#bar')
......
......@@ -612,7 +612,7 @@ describe Notify do
let(:user) { create(:user) }
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 "a user cannot unsubscribe through footer link"
......@@ -625,10 +625,6 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
it 'is sent to recipient' do
is_expected.to deliver_to 'devs@company.name'
end
it 'has the correct subject' do
is_expected.to have_subject /Pushed new branch master/
end
......@@ -643,7 +639,7 @@ describe Notify do
let(:user) { create(:user) }
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 "a user cannot unsubscribe through footer link"
......@@ -656,10 +652,6 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
it 'is sent to recipient' do
is_expected.to deliver_to 'devs@company.name'
end
it 'has the correct subject' do
is_expected.to have_subject /Pushed new tag v1\.0/
end
......@@ -673,7 +665,7 @@ describe Notify do
let(:example_site_path) { root_path }
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 "a user cannot unsubscribe through footer link"
......@@ -686,10 +678,6 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
it 'is sent to recipient' do
is_expected.to deliver_to 'devs@company.name'
end
it 'has the correct subject' do
is_expected.to have_subject /Deleted branch master/
end
......@@ -699,7 +687,7 @@ describe Notify do
let(:example_site_path) { root_path }
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 "a user cannot unsubscribe through footer link"
......@@ -712,10 +700,6 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
it 'is sent to recipient' do
is_expected.to deliver_to 'devs@company.name'
end
it 'has the correct subject' do
is_expected.to have_subject /Deleted tag v1\.0/
end
......@@ -730,7 +714,7 @@ describe Notify do
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)] }
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 "a user cannot unsubscribe through footer link"
......@@ -743,10 +727,6 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
it 'is sent to recipient' do
is_expected.to deliver_to 'devs@company.name'
end
it 'has the correct subject' do
is_expected.to have_subject /\[#{project.path_with_namespace}\]\[master\] #{commits.length} commits:/
end
......@@ -839,7 +819,7 @@ describe Notify do
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)] }
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 "a user cannot unsubscribe through footer link"
......@@ -852,10 +832,6 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
it 'is sent to recipient' do
is_expected.to deliver_to 'devs@company.name'
end
it 'has the correct subject' do
is_expected.to have_subject /#{commits.first.title}/
end
......
......@@ -20,6 +20,15 @@ describe ApplicationSetting, models: true do
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) }
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 do
......
......@@ -44,6 +44,16 @@ describe Subscribable, 'Subscribable' do
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
it 'unsubscribes the given current user' do
resource.subscriptions.create(user: user, subscribed: true)
......
......@@ -95,13 +95,13 @@ describe WebHook, models: true do
it "handles 200 status code" do
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
it "handles 2xx status codes" do
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
......@@ -816,4 +816,23 @@ describe User, models: true do
it { is_expected.to eq([private_project]) }
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
......@@ -623,6 +623,12 @@ describe API::API, api: true do
expect(response.status).to eq(404)
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
describe 'DELETE :id/issues/:issue_id/subscription' do
......@@ -644,5 +650,11 @@ describe API::API, api: true do
expect(response.status).to eq(404)
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
......@@ -190,4 +190,86 @@ describe API::API, api: true do
expect(json_response['message']['color']).to eq(['must be a valid color code'])
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
......@@ -10,20 +10,20 @@ describe API::API, api: true do
let(:admin) { create(:admin) }
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(: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(:project_member) { create(:project_member, :master, user: user, project: project) }
let(:project_member2) { create(:project_member, :developer, user: user3, project: project) }
let(:user4) { create(:user) }
let(:project3) do
create(:project,
:private,
name: 'second_project',
path: 'second_project',
creator_id: user.id,
namespace: user.namespace,
merge_requests_enabled: false,
issues_enabled: false, wiki_enabled: false,
snippets_enabled: false, visibility_level: 0)
snippets_enabled: false)
end
let(:project_member3) do
create(:project_member,
......@@ -164,21 +164,18 @@ describe API::API, api: true do
end
describe 'GET /projects/starred' do
let(:public_project) { create(:project, :public) }
before do
admin.starred_projects << project
admin.save!
project_member2
user3.update_attributes(starred_projects: [project, project2, project3, public_project])
end
it 'should return the starred projects' do
get api('/projects/all', admin)
it 'should return the starred projects viewable by the user' do
get api('/projects/starred', user3)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response).to satisfy do |response|
response.one? do |entry|
entry['name'] == project.name
end
end
expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id)
end
end
......
......@@ -128,3 +128,10 @@ describe Admin::EmailsController, "routing" do
expect(post("/admin/email")).to route_to('admin/emails#create')
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'
# 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
describe SearchController, "routing" do
it "to #show" do
......@@ -27,10 +64,6 @@ end
# PUT /snippets/:id(.:format) snippets#update
# DELETE /snippets/:id(.:format) snippets#destroy
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
expect(get("/snippets/1/raw")).to route_to('snippets#raw', id: '1')
end
......@@ -243,3 +276,13 @@ describe "Groups", "routing" do
expect(get('/1')).to route_to('namespaces#show', id: '1')
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
@path = ProjectWiki.new(@project, @user).send(:path_to_repo)
end
it { expect(File.exists?(@path)).to be_truthy }
it { expect(File.exist?(@path)).to be_truthy }
end
context 'wiki_enabled false does not create wiki repository directory' do
......@@ -74,7 +74,7 @@ describe Projects::CreateService, services: true do
@path = ProjectWiki.new(@project, @user).send(:path_to_repo)
end
it { expect(File.exists?(@path)).to be_falsey }
it { expect(File.exist?(@path)).to be_falsey }
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