Commit 1ba2ef4f authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into mc-ui

parents 882f97e4 931eadaa
...@@ -15,6 +15,7 @@ variables: ...@@ -15,6 +15,7 @@ variables:
USE_DB: "true" USE_DB: "true"
USE_BUNDLE_INSTALL: "true" USE_BUNDLE_INSTALL: "true"
GIT_DEPTH: "20" GIT_DEPTH: "20"
PHANTOMJS_VERSION: "2.1.1"
before_script: before_script:
- source ./scripts/prepare_build.sh - source ./scripts/prepare_build.sh
......
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.11.0 (unreleased) v 8.11.0 (unreleased)
- Add test coverage report badge. !5708
- Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar) - Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar)
- Ability to specify branches for Pivotal Tracker integration (Egor Lynko)
- Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres) - Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres)
- Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres) - Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres)
- Fix the title of the toggle dropdown button. !5515 (herminiotorres) - Fix the title of the toggle dropdown button. !5515 (herminiotorres)
...@@ -32,12 +34,14 @@ v 8.11.0 (unreleased) ...@@ -32,12 +34,14 @@ v 8.11.0 (unreleased)
- Add "No one can push" as an option for protected branches. !5081 - Add "No one can push" as an option for protected branches. !5081
- Improve performance of AutolinkFilter#text_parse by using XPath - Improve performance of AutolinkFilter#text_parse by using XPath
- Add experimental Redis Sentinel support !1877 - Add experimental Redis Sentinel support !1877
- Rendering of SVGs as blobs is now limited to SVGs with a size smaller or equal to 2MB
- Fix branches page dropdown sort initial state (ClemMakesApps) - Fix branches page dropdown sort initial state (ClemMakesApps)
- Environments have an url to link to - Environments have an url to link to
- Various redundant database indexes have been removed - Various redundant database indexes have been removed
- Update `timeago` plugin to use multiple string/locale settings - Update `timeago` plugin to use multiple string/locale settings
- Remove unused images (ClemMakesApps) - Remove unused images (ClemMakesApps)
- Limit git rev-list output count to one in forced push check - Limit git rev-list output count to one in forced push check
- Show deployment status on merge requests with external URLs
- Clean up unused routes (Josef Strzibny) - Clean up unused routes (Josef Strzibny)
- Fix issue on empty project to allow developers to only push to protected branches if given permission - Fix issue on empty project to allow developers to only push to protected branches if given permission
- Add green outline to New Branch button. !5447 (winniehell) - Add green outline to New Branch button. !5447 (winniehell)
...@@ -54,6 +58,7 @@ v 8.11.0 (unreleased) ...@@ -54,6 +58,7 @@ v 8.11.0 (unreleased)
- Optimize checking if a user has read access to a list of issues !5370 - Optimize checking if a user has read access to a list of issues !5370
- Store all DB secrets in secrets.yml, under descriptive names !5274 - Store all DB secrets in secrets.yml, under descriptive names !5274
- Nokogiri's various parsing methods are now instrumented - Nokogiri's various parsing methods are now instrumented
- Add archived badge to project list !5798
- Add simple identifier to public SSH keys (muteor) - Add simple identifier to public SSH keys (muteor)
- Admin page now references docs instead of a specific file !5600 (AnAverageHuman) - Admin page now references docs instead of a specific file !5600 (AnAverageHuman)
- Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363 - Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363
...@@ -107,6 +112,9 @@ v 8.11.0 (unreleased) ...@@ -107,6 +112,9 @@ v 8.11.0 (unreleased)
- Sort folders with submodules in Files view !5521 - Sort folders with submodules in Files view !5521
- Each `File::exists?` replaced to `File::exist?` because of deprecate since ruby version 2.2.0 - Each `File::exists?` replaced to `File::exist?` because of deprecate since ruby version 2.2.0
- Add auto-completition in pipeline (Katarzyna Kobierska Ula Budziszewska) - Add auto-completition in pipeline (Katarzyna Kobierska Ula Budziszewska)
- Fix a memory leak caused by Banzai::Filter::SanitizationFilter
- Speed up todos queries by limiting the projects set we join with
- Ensure file editing in UI does not overwrite commited changes without warning user
v 8.10.5 v 8.10.5
- Add a data migration to fix some missing timestamps in the members table. !5670 - Add a data migration to fix some missing timestamps in the members table. !5670
...@@ -271,6 +279,7 @@ v 8.10.0 ...@@ -271,6 +279,7 @@ v 8.10.0
- Fix new snippet style bug (elliotec) - Fix new snippet style bug (elliotec)
- Instrument Rinku usage - Instrument Rinku usage
- Be explicit to define merge request discussion variables - Be explicit to define merge request discussion variables
- Use cache for todos counter calling TodoService
- Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab - Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab
- RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info. - RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info.
- Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w) - Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w)
......
...@@ -5,13 +5,10 @@ ...@@ -5,13 +5,10 @@
this.Issuable = { this.Issuable = {
init: function() { init: function() {
if (!issuable_created) { Issuable.initTemplates();
issuable_created = true; Issuable.initSearch();
Issuable.initTemplates(); Issuable.initChecks();
Issuable.initSearch(); return Issuable.initLabelFilterRemove();
Issuable.initChecks();
return Issuable.initLabelFilterRemove();
}
}, },
initTemplates: function() { initTemplates: function() {
return Issuable.labelRow = _.template('<% _.each(labels, function(label){ %> <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;"> <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body"> <%- label.title %> </a> <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>"> <i class="fa fa-times"></i> </button> </span> <% }); %>'); return Issuable.labelRow = _.template('<% _.each(labels, function(label){ %> <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;"> <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body"> <%- label.title %> </a> <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>"> <i class="fa fa-times"></i> </button> </span> <% }); %>');
......
...@@ -69,6 +69,10 @@ ...@@ -69,6 +69,10 @@
&.ci-success { &.ci-success {
color: $gl-success; color: $gl-success;
a.environment {
color: inherit;
}
} }
&.ci-success_with_warnings { &.ci-success_with_warnings {
...@@ -126,7 +130,6 @@ ...@@ -126,7 +130,6 @@
&.has-conflicts .fa-exclamation-triangle { &.has-conflicts .fa-exclamation-triangle {
color: $gl-warning; color: $gl-warning;
} }
} }
p:last-child { p:last-child {
......
...@@ -37,8 +37,8 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -37,8 +37,8 @@ class Dashboard::TodosController < Dashboard::ApplicationController
def todos_counts def todos_counts
{ {
count: TodosFinder.new(current_user, state: :pending).execute.count, count: current_user.todos_pending_count,
done_count: TodosFinder.new(current_user, state: :done).execute.count done_count: current_user.todos_done_count
} }
end end
end end
...@@ -4,11 +4,24 @@ class Projects::BadgesController < Projects::ApplicationController ...@@ -4,11 +4,24 @@ class Projects::BadgesController < Projects::ApplicationController
before_action :no_cache_headers, except: [:index] before_action :no_cache_headers, except: [:index]
def build def build
badge = Gitlab::Badge::Build.new(project, params[:ref]) build_status = Gitlab::Badge::Build::Status
.new(project, params[:ref])
render_badge build_status
end
def coverage
coverage_report = Gitlab::Badge::Coverage::Report
.new(project, params[:ref], params[:job])
render_badge coverage_report
end
private
def render_badge(badge)
respond_to do |format| respond_to do |format|
format.html { render_404 } format.html { render_404 }
format.svg do format.svg do
render 'badge', locals: { badge: badge.template } render 'badge', locals: { badge: badge.template }
end end
......
...@@ -17,6 +17,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -17,6 +17,7 @@ class Projects::BlobController < Projects::ApplicationController
before_action :require_branch_head, only: [:edit, :update] before_action :require_branch_head, only: [:edit, :update]
before_action :editor_variables, except: [:show, :preview, :diff] before_action :editor_variables, except: [:show, :preview, :diff]
before_action :validate_diff_params, only: :diff before_action :validate_diff_params, only: :diff
before_action :set_last_commit_sha, only: [:edit, :update]
def new def new
commit unless @repository.empty? commit unless @repository.empty?
...@@ -33,7 +34,6 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -33,7 +34,6 @@ class Projects::BlobController < Projects::ApplicationController
end end
def edit def edit
@last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
blob.load_all_data!(@repository) blob.load_all_data!(@repository)
end end
...@@ -55,6 +55,10 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -55,6 +55,10 @@ class Projects::BlobController < Projects::ApplicationController
create_commit(Files::UpdateService, success_path: after_edit_path, create_commit(Files::UpdateService, success_path: after_edit_path,
failure_view: :edit, failure_view: :edit,
failure_path: namespace_project_blob_path(@project.namespace, @project, @id)) failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
rescue Files::UpdateService::FileChangedError
@conflict = true
render :edit
end end
def preview def preview
...@@ -152,7 +156,8 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -152,7 +156,8 @@ class Projects::BlobController < Projects::ApplicationController
file_path: @file_path, file_path: @file_path,
commit_message: params[:commit_message], commit_message: params[:commit_message],
file_content: params[:content], file_content: params[:content],
file_content_encoding: params[:encoding] file_content_encoding: params[:encoding],
last_commit_sha: params[:last_commit_sha]
} }
end end
...@@ -161,4 +166,9 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -161,4 +166,9 @@ class Projects::BlobController < Projects::ApplicationController
render nothing: true render nothing: true
end end
end end
def set_last_commit_sha
@last_commit_sha = Gitlab::Git::Commit.
last_for_path(@repository, @ref, @path).sha
end
end end
...@@ -3,7 +3,13 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController ...@@ -3,7 +3,13 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
def show def show
@ref = params[:ref] || @project.default_branch || 'master' @ref = params[:ref] || @project.default_branch || 'master'
@build_badge = Gitlab::Badge::Build.new(@project, @ref).metadata
@badges = [Gitlab::Badge::Build::Status,
Gitlab::Badge::Coverage::Report]
@badges.map! do |badge|
badge.new(@project, @ref).metadata
end
end end
def update def update
......
class ProjectsFinder < UnionFinder class ProjectsFinder < UnionFinder
def execute(current_user = nil, options = {}) def execute(current_user = nil, project_ids_relation = nil)
segments = all_projects(current_user) segments = all_projects(current_user)
segments.map! { |s| s.where(id: project_ids_relation) } if project_ids_relation
find_union(segments, Project) find_union(segments, Project)
end end
......
...@@ -27,9 +27,11 @@ class TodosFinder ...@@ -27,9 +27,11 @@ class TodosFinder
items = by_action_id(items) items = by_action_id(items)
items = by_action(items) items = by_action(items)
items = by_author(items) items = by_author(items)
items = by_project(items)
items = by_state(items) items = by_state(items)
items = by_type(items) items = by_type(items)
# Filtering by project HAS TO be the last because we use
# the project IDs yielded by the todos query thus far
items = by_project(items)
items.reorder(id: :desc) items.reorder(id: :desc)
end end
...@@ -91,14 +93,9 @@ class TodosFinder ...@@ -91,14 +93,9 @@ class TodosFinder
@project @project
end end
def projects def projects(items)
return @projects if defined?(@projects) item_project_ids = items.reorder(nil).select(:project_id)
ProjectsFinder.new.execute(current_user, item_project_ids)
if project?
@projects = project
else
@projects = ProjectsFinder.new.execute(current_user)
end
end end
def type? def type?
...@@ -136,8 +133,9 @@ class TodosFinder ...@@ -136,8 +133,9 @@ class TodosFinder
def by_project(items) def by_project(items)
if project? if project?
items = items.where(project: project) items = items.where(project: project)
elsif projects else
items = items.merge(projects).joins(:project) item_projects = projects(items)
items = items.merge(item_projects).joins(:project)
end end
items items
......
...@@ -20,13 +20,19 @@ module SortingHelper ...@@ -20,13 +20,19 @@ module SortingHelper
end end
def projects_sort_options_hash def projects_sort_options_hash
{ options = {
sort_value_name => sort_title_name, sort_value_name => sort_title_name,
sort_value_recently_updated => sort_title_recently_updated, sort_value_recently_updated => sort_title_recently_updated,
sort_value_oldest_updated => sort_title_oldest_updated, sort_value_oldest_updated => sort_title_oldest_updated,
sort_value_recently_created => sort_title_recently_created, sort_value_recently_created => sort_title_recently_created,
sort_value_oldest_created => sort_title_oldest_created, sort_value_oldest_created => sort_title_oldest_created,
} }
if current_controller?('admin/projects')
options.merge!(sort_value_largest_repo => sort_title_largest_repo)
end
options
end end
def sort_title_priority def sort_title_priority
......
module TodosHelper module TodosHelper
def todos_pending_count def todos_pending_count
@todos_pending_count ||= TodosFinder.new(current_user, state: :pending).execute.count @todos_pending_count ||= current_user.todos_pending_count
end end
def todos_done_count def todos_done_count
@todos_done_count ||= TodosFinder.new(current_user, state: :done).execute.count @todos_done_count ||= current_user.todos_done_count
end end
def todo_action_name(todo) def todo_action_name(todo)
......
...@@ -3,6 +3,9 @@ class Blob < SimpleDelegator ...@@ -3,6 +3,9 @@ class Blob < SimpleDelegator
CACHE_TIME = 60 # Cache raw blobs referred to by a (mutable) ref for 1 minute CACHE_TIME = 60 # Cache raw blobs referred to by a (mutable) ref for 1 minute
CACHE_TIME_IMMUTABLE = 3600 # Cache blobs referred to by an immutable reference for 1 hour CACHE_TIME_IMMUTABLE = 3600 # Cache blobs referred to by an immutable reference for 1 hour
# The maximum size of an SVG that can be displayed.
MAXIMUM_SVG_SIZE = 2.megabytes
# Wrap a Gitlab::Git::Blob object, or return nil when given nil # Wrap a Gitlab::Git::Blob object, or return nil when given nil
# #
# This method prevents the decorated object from evaluating to "truthy" when # This method prevents the decorated object from evaluating to "truthy" when
...@@ -31,6 +34,10 @@ class Blob < SimpleDelegator ...@@ -31,6 +34,10 @@ class Blob < SimpleDelegator
text? && language && language.name == 'SVG' text? && language && language.name == 'SVG'
end end
def size_within_svg_limits?
size <= MAXIMUM_SVG_SIZE
end
def video? def video?
UploaderHelper::VIDEO_EXT.include?(extname.downcase.delete('.')) UploaderHelper::VIDEO_EXT.include?(extname.downcase.delete('.'))
end end
......
...@@ -36,4 +36,10 @@ class Deployment < ActiveRecord::Base ...@@ -36,4 +36,10 @@ class Deployment < ActiveRecord::Base
def manual_actions def manual_actions
deployable.try(:other_actions) deployable.try(:other_actions)
end end
def includes_commit?(commit)
return false unless commit
project.repository.is_ancestor?(commit.id, sha)
end
end end
...@@ -25,4 +25,10 @@ class Environment < ActiveRecord::Base ...@@ -25,4 +25,10 @@ class Environment < ActiveRecord::Base
def nullify_external_url def nullify_external_url
self.external_url = nil if self.external_url.blank? self.external_url = nil if self.external_url.blank?
end end
def includes_commit?(commit)
return false unless last_deployment
last_deployment.includes_commit?(commit)
end
end end
...@@ -591,6 +591,14 @@ class MergeRequest < ActiveRecord::Base ...@@ -591,6 +591,14 @@ class MergeRequest < ActiveRecord::Base
!pipeline || pipeline.success? !pipeline || pipeline.success?
end end
def environments
return unless diff_head_commit
target_project.environments.select do |environment|
environment.includes_commit?(diff_head_commit)
end
end
def state_human_name def state_human_name
if merged? if merged?
"Merged" "Merged"
......
class PivotaltrackerService < Service class PivotaltrackerService < Service
include HTTParty include HTTParty
prop_accessor :token API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits'
prop_accessor :token, :restrict_to_branch
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
def title def title
...@@ -18,7 +20,17 @@ class PivotaltrackerService < Service ...@@ -18,7 +20,17 @@ class PivotaltrackerService < Service
def fields def fields
[ [
{ type: 'text', name: 'token', placeholder: '' } {
type: 'text',
name: 'token',
placeholder: 'Pivotal Tracker API token.'
},
{
type: 'text',
name: 'restrict_to_branch',
placeholder: 'Comma-separated list of branches which will be ' \
'automatically inspected. Leave blank to include all branches.'
}
] ]
end end
...@@ -28,8 +40,8 @@ class PivotaltrackerService < Service ...@@ -28,8 +40,8 @@ class PivotaltrackerService < Service
def execute(data) def execute(data)
return unless supported_events.include?(data[:object_kind]) return unless supported_events.include?(data[:object_kind])
return unless allowed_branch?(data[:ref])
url = 'https://www.pivotaltracker.com/services/v5/source_commits'
data[:commits].each do |commit| data[:commits].each do |commit|
message = { message = {
'source_commit' => { 'source_commit' => {
...@@ -40,7 +52,7 @@ class PivotaltrackerService < Service ...@@ -40,7 +52,7 @@ class PivotaltrackerService < Service
} }
} }
PivotaltrackerService.post( PivotaltrackerService.post(
url, API_ENDPOINT,
body: message.to_json, body: message.to_json,
headers: { headers: {
'Content-Type' => 'application/json', 'Content-Type' => 'application/json',
...@@ -49,4 +61,15 @@ class PivotaltrackerService < Service ...@@ -49,4 +61,15 @@ class PivotaltrackerService < Service
) )
end end
end end
private
def allowed_branch?(ref)
return true unless ref.present? && restrict_to_branch.present?
branch = Gitlab::Git.ref_name(ref)
allowed_branches = restrict_to_branch.split(',').map(&:strip)
branch.present? && allowed_branches.include?(branch)
end
end end
...@@ -56,6 +56,10 @@ class ProjectWiki ...@@ -56,6 +56,10 @@ class ProjectWiki
end end
end end
def repository_exists?
!!repository.exists?
end
def empty? def empty?
pages.empty? pages.empty?
end end
......
...@@ -809,13 +809,13 @@ class User < ActiveRecord::Base ...@@ -809,13 +809,13 @@ class User < ActiveRecord::Base
def todos_done_count(force: false) def todos_done_count(force: false)
Rails.cache.fetch(['users', id, 'todos_done_count'], force: force) do Rails.cache.fetch(['users', id, 'todos_done_count'], force: force) do
todos.done.count TodosFinder.new(self, state: :done).execute.count
end end
end end
def todos_pending_count(force: false) def todos_pending_count(force: false)
Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force) do Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force) do
todos.pending.count TodosFinder.new(self, state: :pending).execute.count
end end
end end
......
...@@ -15,6 +15,7 @@ module Files ...@@ -15,6 +15,7 @@ module Files
else else
params[:file_content] params[:file_content]
end end
@last_commit_sha = params[:last_commit_sha]
# Validate parameters # Validate parameters
validate validate
......
...@@ -2,11 +2,34 @@ require_relative "base_service" ...@@ -2,11 +2,34 @@ require_relative "base_service"
module Files module Files
class UpdateService < Files::BaseService class UpdateService < Files::BaseService
class FileChangedError < StandardError; end
def commit def commit
repository.update_file(current_user, @file_path, @file_content, repository.update_file(current_user, @file_path, @file_content,
branch: @target_branch, branch: @target_branch,
previous_path: @previous_path, previous_path: @previous_path,
message: @commit_message) message: @commit_message)
end end
private
def validate
super
if file_has_changed?
raise FileChangedError.new("You are attempting to update a file that has changed since you started editing it.")
end
end
def file_has_changed?
return false unless @last_commit_sha && last_commit
@last_commit_sha != last_commit.sha
end
def last_commit
@last_commit ||= Gitlab::Git::Commit.
last_for_path(@source_project.repository, @source_branch, @file_path)
end
end end
end end
...@@ -30,10 +30,21 @@ module MergeRequests ...@@ -30,10 +30,21 @@ module MergeRequests
end end
def get_branches(changes) def get_branches(changes)
return [] if project.empty_repo?
return [] unless project.merge_requests_enabled
changes_list = Gitlab::ChangesList.new(changes) changes_list = Gitlab::ChangesList.new(changes)
changes_list.map do |change| changes_list.map do |change|
next unless Gitlab::Git.branch_ref?(change[:ref]) next unless Gitlab::Git.branch_ref?(change[:ref])
Gitlab::Git.branch_name(change[:ref])
# Deleted branch
next if Gitlab::Git.blank_ref?(change[:newrev])
# Default branch
branch_name = Gitlab::Git.branch_name(change[:ref])
next if branch_name == project.default_branch
branch_name
end.compact end.compact
end end
......
...@@ -144,8 +144,9 @@ class TodoService ...@@ -144,8 +144,9 @@ class TodoService
def mark_todos_as_done(todos, current_user) def mark_todos_as_done(todos, current_user)
todos = current_user.todos.where(id: todos.map(&:id)) unless todos.respond_to?(:update_all) todos = current_user.todos.where(id: todos.map(&:id)) unless todos.respond_to?(:update_all)
todos.update_all(state: :done) marked_todos = todos.update_all(state: :done)
current_user.update_todos_count_cache current_user.update_todos_count_cache
marked_todos
end end
# When user marks an issue as todo # When user marks an issue as todo
......
.file-content.image_file .file-content.image_file
- if blob.svg? - if blob.svg?
- # We need to scrub SVG but we cannot do so in the RawController: it would - if blob.size_within_svg_limits?
- # be wrong/strange if RawController modified the data. - # We need to scrub SVG but we cannot do so in the RawController: it would
- blob.load_all_data!(@repository) - # be wrong/strange if RawController modified the data.
- blob = sanitize_svg(blob) - blob.load_all_data!(@repository)
%img{src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"} - blob = sanitize_svg(blob)
%img{src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"}
- else
.nothing-here-block
The SVG could not be displayed as it is too large, you can
#{link_to('view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank')}
instead.
- else - else
%img{src: namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, blob.path))} %img{src: namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, blob.path))}
- page_title "Edit", @blob.path, @ref - page_title "Edit", @blob.path, @ref
- if @conflict
.alert.alert-danger
Someone edited the file the same time you did. Please check out
= link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@target_branch, @file_path)), target: "_blank"
and make sure your changes will not unintentionally remove theirs.
.file-editor .file-editor
%ul.nav-links.no-bottom.js-edit-mode %ul.nav-links.no-bottom.js-edit-mode
%li.active %li.active
...@@ -13,8 +19,7 @@ ...@@ -13,8 +19,7 @@
= form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form') do = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form') do
= render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
= render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
= hidden_field_tag 'last_commit_sha', @last_commit_sha
= hidden_field_tag 'last_commit', @last_commit
= hidden_field_tag 'content', '', id: "file-content" = hidden_field_tag 'content', '', id: "file-content"
= hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
= render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id) = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id)
......
...@@ -42,3 +42,16 @@ ...@@ -42,3 +42,16 @@
.ci_widget.ci-error{style: "display:none"} .ci_widget.ci-error{style: "display:none"}
= icon("times-circle") = icon("times-circle")
Could not connect to the CI server. Please check your settings and try again. Could not connect to the CI server. Please check your settings and try again.
- @merge_request.environments.each do |environment|
.mr-widget-heading
.ci_widget.ci-success
= ci_icon_for_status("success")
%span.hidden-sm
Deployed to
= succeed '.' do
= link_to environment.name, namespace_project_environment_path(@project.namespace, @project, environment), class: 'environment'
- external_url = environment.external_url
- if external_url
= link_to external_url, target: '_blank' do
= icon('external-link', text: "View on #{external_url.gsub(/\A.*?:\/\//, '')}", right: true)
.row{ class: badge.title.gsub(' ', '-') }
.col-lg-3.profile-settings-sidebar
%h4.prepend-top-0
= badge.title.capitalize
.col-lg-9
.prepend-top-10
.panel.panel-default
.panel-heading
%b
= badge.title.capitalize
&middot;
= badge.to_html
.pull-right
= render 'shared/ref_switcher', destination: 'badges', align_right: true
.panel-body
.row
.col-md-2.text-center
Markdown
.col-md-10.code.js-syntax-highlight
= highlight('.md', badge.to_markdown)
.row
%hr
.row
.col-md-2.text-center
HTML
.col-md-10.code.js-syntax-highlight
= highlight('.html', badge.to_html)
...@@ -77,27 +77,4 @@ ...@@ -77,27 +77,4 @@
%hr %hr
.row.prepend-top-default .row.prepend-top-default
.col-lg-3.profile-settings-sidebar = render partial: 'badge', collection: @badges
%h4.prepend-top-0
Builds Badge
.col-lg-9
.prepend-top-10
.panel.panel-default
.panel-heading
%b Builds badge &middot;
= @build_badge.to_html
.pull-right
= render 'shared/ref_switcher', destination: 'badges', align_right: true
.panel-body
.row
.col-md-2.text-center
Markdown
.col-md-10.code.js-syntax-highlight
= highlight('.md', @build_badge.to_markdown)
.row
%hr
.row
.col-md-2.text-center
HTML
.col-md-10.code.js-syntax-highlight
= highlight('.html', @build_badge.to_html)
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
%li.project-row{ class: css_class } %li.project-row{ class: css_class }
= cache(cache_key) do = cache(cache_key) do
.controls .controls
- if project.archived
%span.label.label-warning archived
- if project.commit.try(:status) - if project.commit.try(:status)
%span %span
= render_commit_status(project.commit) = render_commit_status(project.commit)
......
...@@ -871,7 +871,10 @@ Rails.application.routes.draw do ...@@ -871,7 +871,10 @@ Rails.application.routes.draw do
resources :badges, only: [:index] do resources :badges, only: [:index] do
collection do collection do
scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do
get :build, constraints: { format: /svg/ } constraints format: /svg/ do
get :build
get :coverage
end
end end
end end
end end
......
class Gitlab::Seeder::Builds class Gitlab::Seeder::Builds
STAGES = %w[build notify_build test notify_test deploy notify_deploy] STAGES = %w[build notify_build test notify_test deploy notify_deploy]
BUILDS = [
{ name: 'build:linux', stage: 'build', status: :success },
{ name: 'build:osx', stage: 'build', status: :success },
{ name: 'slack post build', stage: 'notify_build', status: :success },
{ name: 'rspec:linux', stage: 'test', status: :success },
{ name: 'rspec:windows', stage: 'test', status: :success },
{ name: 'rspec:windows', stage: 'test', status: :success },
{ name: 'rspec:osx', stage: 'test', status_event: :success },
{ name: 'spinach:linux', stage: 'test', status: :pending },
{ name: 'spinach:osx', stage: 'test', status: :canceled },
{ name: 'cucumber:linux', stage: 'test', status: :running },
{ name: 'cucumber:osx', stage: 'test', status: :failed },
{ name: 'slack post test', stage: 'notify_test', status: :success },
{ name: 'staging', stage: 'deploy', environment: 'staging', status: :success },
{ name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :success },
]
def initialize(project) def initialize(project)
@project = project @project = project
...@@ -8,25 +24,7 @@ class Gitlab::Seeder::Builds ...@@ -8,25 +24,7 @@ class Gitlab::Seeder::Builds
def seed! def seed!
pipelines.each do |pipeline| pipelines.each do |pipeline|
begin begin
build_create!(pipeline, name: 'build:linux', stage: 'build', status_event: :success) BUILDS.each { |opts| build_create!(pipeline, opts) }
build_create!(pipeline, name: 'build:osx', stage: 'build', status_event: :success)
build_create!(pipeline, name: 'slack post build', stage: 'notify_build', status_event: :success)
build_create!(pipeline, name: 'rspec:linux', stage: 'test', status_event: :success)
build_create!(pipeline, name: 'rspec:windows', stage: 'test', status_event: :success)
build_create!(pipeline, name: 'rspec:windows', stage: 'test', status_event: :success)
build_create!(pipeline, name: 'rspec:osx', stage: 'test', status_event: :success)
build_create!(pipeline, name: 'spinach:linux', stage: 'test', status: :pending)
build_create!(pipeline, name: 'spinach:osx', stage: 'test', status_event: :cancel)
build_create!(pipeline, name: 'cucumber:linux', stage: 'test', status_event: :run)
build_create!(pipeline, name: 'cucumber:osx', stage: 'test', status_event: :drop)
build_create!(pipeline, name: 'slack post test', stage: 'notify_test', status_event: :success)
build_create!(pipeline, name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success)
build_create!(pipeline, name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :success)
commit_status_create!(pipeline, name: 'jenkins', status: :success) commit_status_create!(pipeline, name: 'jenkins', status: :success)
print '.' print '.'
...@@ -48,21 +46,22 @@ class Gitlab::Seeder::Builds ...@@ -48,21 +46,22 @@ class Gitlab::Seeder::Builds
def build_create!(pipeline, opts = {}) def build_create!(pipeline, opts = {})
attributes = build_attributes_for(pipeline, opts) attributes = build_attributes_for(pipeline, opts)
build = Ci::Build.create!(attributes)
if opts[:name].start_with?('build') Ci::Build.create!(attributes) do |build|
artifacts_cache_file(artifacts_archive_path) do |file| if opts[:name].start_with?('build')
build.artifacts_file = file artifacts_cache_file(artifacts_archive_path) do |file|
end build.artifacts_file = file
end
artifacts_cache_file(artifacts_metadata_path) do |file| artifacts_cache_file(artifacts_metadata_path) do |file|
build.artifacts_metadata = file build.artifacts_metadata = file
end
end end
end
if %w(running success failed).include?(build.status) if %w(running success failed).include?(build.status)
# We need to set build trace after saving a build (id required) # We need to set build trace after saving a build (id required)
build.trace = FFaker::Lorem.paragraphs(6).join("\n\n") build.trace = FFaker::Lorem.paragraphs(6).join("\n\n")
end
end end
end end
......
...@@ -589,12 +589,12 @@ ActiveRecord::Schema.define(version: 20160810142633) do ...@@ -589,12 +589,12 @@ ActiveRecord::Schema.define(version: 20160810142633) do
t.datetime "locked_at" t.datetime "locked_at"
t.integer "updated_by_id" t.integer "updated_by_id"
t.string "merge_error" t.string "merge_error"
t.text "merge_params"
t.boolean "merge_when_build_succeeds", default: false, null: false t.boolean "merge_when_build_succeeds", default: false, null: false
t.integer "merge_user_id" t.integer "merge_user_id"
t.string "merge_commit_sha" t.string "merge_commit_sha"
t.datetime "deleted_at" t.datetime "deleted_at"
t.string "in_progress_merge_commit_sha" t.string "in_progress_merge_commit_sha"
t.text "merge_params"
end end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
......
...@@ -355,7 +355,7 @@ PUT /projects/:id/services/gemnasium ...@@ -355,7 +355,7 @@ PUT /projects/:id/services/gemnasium
Parameters: Parameters:
- `api_key` (**required**) - Your personal API KEY on gemnasium.com - `api_key` (**required**) - Your personal API KEY on gemnasium.com
- `token` (**required**) - The project's slug on gemnasium.com - `token` (**required**) - The project's slug on gemnasium.com
### Delete Gemnasium service ### Delete Gemnasium service
...@@ -503,6 +503,7 @@ PUT /projects/:id/services/pivotaltracker ...@@ -503,6 +503,7 @@ PUT /projects/:id/services/pivotaltracker
Parameters: Parameters:
- `token` (**required**) - `token` (**required**)
- `restrict_to_branch` (optional) - Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.
### Delete PivotalTracker service ### Delete PivotalTracker service
...@@ -661,4 +662,3 @@ Get JetBrains TeamCity CI service settings for a project. ...@@ -661,4 +662,3 @@ Get JetBrains TeamCity CI service settings for a project.
``` ```
GET /projects/:id/services/teamcity GET /projects/:id/services/teamcity
``` ```
...@@ -32,6 +32,41 @@ project. ...@@ -32,6 +32,41 @@ project.
Clicking on a pipeline will show the builds that were run for that pipeline. Clicking on a pipeline will show the builds that were run for that pipeline.
## Badges
There are build status and test coverage report badges available.
Go to pipeline settings to see available badges and code you can use to embed
badges in the `README.md` or your website.
### Build status badge
You can access a build status badge image using following link:
```
http://example.gitlab.com/namespace/project/badges/branch/build.svg
```
### Test coverage report badge
GitLab makes it possible to define the regular expression for coverage report,
that each build log will be matched against. This means that each build in the
pipeline can have the test coverage percentage value defined.
You can access test coverage badge using following link:
```
http://example.gitlab.com/namespace/project/badges/branch/coverage.svg
```
If you would like to get the coverage report from the specific job, you can add
a `job=coverage_job_name` parameter to the URL. For example, it is possible to
use following Markdown code to embed the est coverage report into `README.md`:
```markdown
![coverage](http://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)
```
[builds]: #builds [builds]: #builds
[jobs]: yaml/README.md#jobs [jobs]: yaml/README.md#jobs
[stages]: yaml/README.md#stages [stages]: yaml/README.md#stages
......
...@@ -218,21 +218,13 @@ project's settings. ...@@ -218,21 +218,13 @@ project's settings.
For more information read the For more information read the
[Builds emails service documentation](../../project_services/builds_emails.md). [Builds emails service documentation](../../project_services/builds_emails.md).
## Builds badge
You can access a builds badge image using following link:
```
http://example.gitlab.com/namespace/project/badges/branch/build.svg
```
Awesome! You started using CI in GitLab!
## Examples ## Examples
Visit the [examples README][examples] to see a list of examples using GitLab Visit the [examples README][examples] to see a list of examples using GitLab
CI with various languages. CI with various languages.
Awesome! You started using CI in GitLab!
[runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#install-gitlab-runner [runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#install-gitlab-runner
[blog-ci]: https://about.gitlab.com/2015/05/06/why-were-replacing-gitlab-ci-jobs-with-gitlab-ci-dot-yml/ [blog-ci]: https://about.gitlab.com/2015/05/06/why-were-replacing-gitlab-ci-jobs-with-gitlab-ci-dot-yml/
[examples]: ../examples/README.md [examples]: ../examples/README.md
......
...@@ -26,6 +26,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps ...@@ -26,6 +26,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
end end
step 'I see prefilled new Merge Request page' do step 'I see prefilled new Merge Request page' do
expect(page).to have_selector('.merge-request-form')
expect(current_path).to eq new_namespace_project_merge_request_path(@project.namespace, @project) expect(current_path).to eq new_namespace_project_merge_request_path(@project.namespace, @project)
expect(find("#merge_request_target_project_id").value).to eq @project.id.to_s expect(find("#merge_request_target_project_id").value).to eq @project.id.to_s
expect(find("input#merge_request_source_branch").value).to eq "fix" expect(find("input#merge_request_source_branch").value).to eq "fix"
......
class Spinach::Features::EventFilters < Spinach::FeatureSteps class Spinach::Features::EventFilters < Spinach::FeatureSteps
include WaitForAjax
include SharedAuthentication include SharedAuthentication
include SharedPaths include SharedPaths
include SharedProject include SharedProject
...@@ -72,14 +73,20 @@ class Spinach::Features::EventFilters < Spinach::FeatureSteps ...@@ -72,14 +73,20 @@ class Spinach::Features::EventFilters < Spinach::FeatureSteps
end end
When 'I click "push" event filter' do When 'I click "push" event filter' do
click_link("push_event_filter") wait_for_ajax
click_link("Push events")
wait_for_ajax
end end
When 'I click "team" event filter' do When 'I click "team" event filter' do
click_link("team_event_filter") wait_for_ajax
click_link("Team")
wait_for_ajax
end end
When 'I click "merge" event filter' do When 'I click "merge" event filter' do
click_link("merged_event_filter") wait_for_ajax
click_link("Merge events")
wait_for_ajax
end end
end end
...@@ -43,9 +43,14 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps ...@@ -43,9 +43,14 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
step 'I click "All" link' do step 'I click "All" link' do
find(".js-author-search").click find(".js-author-search").click
expect(page).to have_selector(".dropdown-menu-author li a")
find(".dropdown-menu-author li a", match: :first).click find(".dropdown-menu-author li a", match: :first).click
expect(page).not_to have_selector(".dropdown-menu-author li a")
find(".js-assignee-search").click find(".js-assignee-search").click
expect(page).to have_selector(".dropdown-menu-assignee li a")
find(".dropdown-menu-assignee li a", match: :first).click find(".dropdown-menu-assignee li a", match: :first).click
expect(page).not_to have_selector(".dropdown-menu-assignee li a")
end end
def should_see(issue) def should_see(issue)
......
...@@ -47,9 +47,14 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps ...@@ -47,9 +47,14 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
step 'I click "All" link' do step 'I click "All" link' do
find(".js-author-search").click find(".js-author-search").click
expect(page).to have_selector(".dropdown-menu-author li a")
find(".dropdown-menu-author li a", match: :first).click find(".dropdown-menu-author li a", match: :first).click
expect(page).not_to have_selector(".dropdown-menu-author li a")
find(".js-assignee-search").click find(".js-assignee-search").click
expect(page).to have_selector(".dropdown-menu-assignee li a")
find(".dropdown-menu-assignee li a", match: :first).click find(".dropdown-menu-assignee li a", match: :first).click
expect(page).not_to have_selector(".dropdown-menu-assignee li a")
end end
def should_see(merge_request) def should_see(merge_request)
......
...@@ -29,6 +29,7 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps ...@@ -29,6 +29,7 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
end end
step 'I am redirected to the GitHub import page' do step 'I am redirected to the GitHub import page' do
expect(page).to have_content('Import Projects from GitHub')
expect(current_path).to eq new_import_github_path expect(current_path).to eq new_import_github_path
end end
...@@ -47,6 +48,7 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps ...@@ -47,6 +48,7 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
end end
step 'I redirected to Google Code import page' do step 'I redirected to Google Code import page' do
expect(page).to have_content('Import projects from Google Code')
expect(current_path).to eq new_import_google_code_path expect(current_path).to eq new_import_google_code_path
end end
end end
...@@ -10,6 +10,7 @@ class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps ...@@ -10,6 +10,7 @@ class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps
step 'I click artifacts browse button' do step 'I click artifacts browse button' do
click_link 'Browse' click_link 'Browse'
expect(page).not_to have_selector('.build-sidebar')
end end
step 'I should see content of artifacts archive' do step 'I should see content of artifacts archive' do
......
...@@ -34,6 +34,9 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps ...@@ -34,6 +34,9 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end end
step 'I fill out a "Merge Request On Forked Project" merge request' do step 'I fill out a "Merge Request On Forked Project" merge request' do
expect(page).to have_content('Source branch')
expect(page).to have_content('Target branch')
first('.js-source-project').click first('.js-source-project').click
first('.dropdown-source-project a', text: @forked_project.path_with_namespace) first('.dropdown-source-project a', text: @forked_project.path_with_namespace)
......
...@@ -354,6 +354,8 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps ...@@ -354,6 +354,8 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end end
def filter_issue(text) def filter_issue(text)
sleep 1
fill_in 'issue_search', with: text fill_in 'issue_search', with: text
sleep 1
end end
end end
...@@ -489,10 +489,12 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -489,10 +489,12 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end end
step 'I fill in merge request search with "Fe"' do step 'I fill in merge request search with "Fe"' do
sleep 1
fill_in 'issue_search', with: "Fe" fill_in 'issue_search', with: "Fe"
end end
step 'I click the "Target branch" dropdown' do step 'I click the "Target branch" dropdown' do
expect(page).to have_content('Target branch')
first('.target_branch').click first('.target_branch').click
end end
......
...@@ -69,6 +69,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -69,6 +69,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end end
step 'I edit code' do step 'I edit code' do
expect(page).to have_selector('.file-editor')
set_new_content set_new_content
end end
...@@ -131,6 +132,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -131,6 +132,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I click on "New file" link in repo' do step 'I click on "New file" link in repo' do
find('.add-to-tree').click find('.add-to-tree').click
click_link 'New file' click_link 'New file'
expect(page).to have_selector('.file-editor')
end end
step 'I click on "Upload file" link in repo' do step 'I click on "Upload file" link in repo' do
......
...@@ -142,7 +142,9 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps ...@@ -142,7 +142,9 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end end
step 'I edit the Wiki page with a path' do step 'I edit the Wiki page with a path' do
expect(page).to have_content('three')
click_on 'three' click_on 'three'
expect(find('.nav-text')).to have_content('Three')
click_on 'Edit' click_on 'Edit'
end end
......
...@@ -133,9 +133,7 @@ module SharedIssuable ...@@ -133,9 +133,7 @@ module SharedIssuable
end end
step 'The list should be sorted by "Oldest updated"' do step 'The list should be sorted by "Oldest updated"' do
page.within('.content div.dropdown.inline.prepend-left-10') do expect(find('.issues-filters')).to have_content('Oldest updated')
expect(page.find('button.dropdown-toggle.btn')).to have_content('Oldest updated')
end
end end
step 'I click link "Next" in the sidebar' do step 'I click link "Next" in the sidebar' do
......
module WaitForAjax
def wait_for_ajax
Timeout.timeout(Capybara.default_max_wait_time) do
loop until finished_all_ajax_requests?
end
end
def finished_all_ajax_requests?
page.evaluate_script('jQuery.active').zero?
end
end
...@@ -61,9 +61,9 @@ module API ...@@ -61,9 +61,9 @@ module API
# #
delete ':id' do delete ':id' do
todo = current_user.todos.find(params[:id]) todo = current_user.todos.find(params[:id])
todo.done TodoService.new.mark_todos_as_done([todo], current_user)
present todo, with: Entities::Todo, current_user: current_user present todo.reload, with: Entities::Todo, current_user: current_user
end end
# Mark all todos as done # Mark all todos as done
...@@ -73,9 +73,7 @@ module API ...@@ -73,9 +73,7 @@ module API
# #
delete do delete do
todos = find_todos todos = find_todos
todos.each(&:done) TodoService.new.mark_todos_as_done(todos, current_user)
todos.length
end end
end end
end end
......
...@@ -7,7 +7,7 @@ module Banzai ...@@ -7,7 +7,7 @@ module Banzai
UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
def whitelist def whitelist
whitelist = super whitelist = super.dup
customize_whitelist(whitelist) customize_whitelist(whitelist)
...@@ -42,6 +42,8 @@ module Banzai ...@@ -42,6 +42,8 @@ module Banzai
# Allow any protocol in `a` elements... # Allow any protocol in `a` elements...
whitelist[:protocols].delete('a') whitelist[:protocols].delete('a')
whitelist[:transformers] = whitelist[:transformers].dup
# ...but then remove links with unsafe protocols # ...but then remove links with unsafe protocols
whitelist[:transformers].push(remove_unsafe_links) whitelist[:transformers].push(remove_unsafe_links)
......
module Gitlab
module Badge
class Base
def entity
raise NotImplementedError
end
def status
raise NotImplementedError
end
def metadata
raise NotImplementedError
end
def template
raise NotImplementedError
end
end
end
end
module Gitlab
module Badge
##
# Build badge
#
class Build
delegate :key_text, :value_text, to: :template
def initialize(project, ref)
@project = project
@ref = ref
@sha = @project.commit(@ref).try(:sha)
end
def status
@project.pipelines
.where(sha: @sha, ref: @ref)
.status || 'unknown'
end
def metadata
@metadata ||= Build::Metadata.new(@project, @ref)
end
def template
@template ||= Build::Template.new(status)
end
end
end
end
module Gitlab module Gitlab
module Badge module Badge
class Build module Build
## ##
# Class that describes build badge metadata # Class that describes build badge metadata
# #
class Metadata class Metadata < Badge::Metadata
include Gitlab::Application.routes.url_helpers def initialize(badge)
include ActionView::Helpers::AssetTagHelper @project = badge.project
include ActionView::Helpers::UrlHelper @ref = badge.ref
def initialize(project, ref)
@project = project
@ref = ref
end
def to_html
link_to(image_tag(image_url, alt: 'build status'), link_url)
end end
def to_markdown def title
"[![build status](#{image_url})](#{link_url})" 'build status'
end end
def image_url def image_url
......
module Gitlab
module Badge
module Build
##
# Build status badge
#
class Status < Badge::Base
attr_reader :project, :ref
def initialize(project, ref)
@project = project
@ref = ref
@sha = @project.commit(@ref).try(:sha)
end
def entity
'build'
end
def status
@project.pipelines
.where(sha: @sha, ref: @ref)
.status || 'unknown'
end
def metadata
@metadata ||= Build::Metadata.new(self)
end
def template
@template ||= Build::Template.new(self)
end
end
end
end
end
module Gitlab module Gitlab
module Badge module Badge
class Build module Build
## ##
# Class that represents a build badge template. # Class that represents a build badge template.
# #
# Template object will be passed to badge.svg.erb template. # Template object will be passed to badge.svg.erb template.
# #
class Template class Template < Badge::Template
STATUS_COLOR = { STATUS_COLOR = {
success: '#4c1', success: '#4c1',
failed: '#e05d44', failed: '#e05d44',
...@@ -17,16 +17,17 @@ module Gitlab ...@@ -17,16 +17,17 @@ module Gitlab
unknown: '#9f9f9f' unknown: '#9f9f9f'
} }
def initialize(status) def initialize(badge)
@status = status @entity = badge.entity
@status = badge.status
end end
def key_text def key_text
'build' @entity.to_s
end end
def value_text def value_text
@status @status.to_s
end end
def key_width def key_width
...@@ -37,25 +38,8 @@ module Gitlab ...@@ -37,25 +38,8 @@ module Gitlab
54 54
end end
def key_color
'#555'
end
def value_color def value_color
STATUS_COLOR[@status.to_sym] || STATUS_COLOR[@status.to_sym] || STATUS_COLOR[:unknown]
STATUS_COLOR[:unknown]
end
def key_text_anchor
key_width / 2
end
def value_text_anchor
key_width + (value_width / 2)
end
def width
key_width + value_width
end end
end end
end end
......
module Gitlab
module Badge
module Coverage
##
# Class that describes coverage badge metadata
#
class Metadata < Badge::Metadata
def initialize(badge)
@project = badge.project
@ref = badge.ref
@job = badge.job
end
def title
'coverage report'
end
def image_url
coverage_namespace_project_badges_url(@project.namespace,
@project, @ref,
format: :svg)
end
def link_url
namespace_project_commits_url(@project.namespace, @project, id: @ref)
end
end
end
end
end
module Gitlab
module Badge
module Coverage
##
# Test coverage report badge
#
class Report < Badge::Base
attr_reader :project, :ref, :job
def initialize(project, ref, job = nil)
@project = project
@ref = ref
@job = job
@pipeline = @project.pipelines
.where(ref: @ref)
.where(sha: @project.commit(@ref).try(:sha))
.first
end
def entity
'coverage'
end
def status
@coverage ||= raw_coverage
return unless @coverage
@coverage.to_i
end
def metadata
@metadata ||= Coverage::Metadata.new(self)
end
def template
@template ||= Coverage::Template.new(self)
end
private
def raw_coverage
return unless @pipeline
if @job.blank?
@pipeline.coverage
else
@pipeline.builds
.find_by(name: @job)
.try(:coverage)
end
end
end
end
end
end
module Gitlab
module Badge
module Coverage
##
# Class that represents a coverage badge template.
#
# Template object will be passed to badge.svg.erb template.
#
class Template < Badge::Template
STATUS_COLOR = {
good: '#4c1',
acceptable: '#a3c51c',
medium: '#dfb317',
low: '#e05d44',
unknown: '#9f9f9f'
}
def initialize(badge)
@entity = badge.entity
@status = badge.status
end
def key_text
@entity.to_s
end
def value_text
@status ? "#{@status}%" : 'unknown'
end
def key_width
62
end
def value_width
@status ? 36 : 58
end
def value_color
case @status
when 95..100 then STATUS_COLOR[:good]
when 90..95 then STATUS_COLOR[:acceptable]
when 75..90 then STATUS_COLOR[:medium]
when 0..75 then STATUS_COLOR[:low]
else
STATUS_COLOR[:unknown]
end
end
end
end
end
end
module Gitlab
module Badge
##
# Abstract class for badge metadata
#
class Metadata
include Gitlab::Application.routes.url_helpers
include ActionView::Helpers::AssetTagHelper
include ActionView::Helpers::UrlHelper
def initialize(badge)
@badge = badge
end
def to_html
link_to(image_tag(image_url, alt: title), link_url)
end
def to_markdown
"[![#{title}](#{image_url})](#{link_url})"
end
def title
raise NotImplementedError
end
def image_url
raise NotImplementedError
end
def link_url
raise NotImplementedError
end
end
end
end
module Gitlab
module Badge
##
# Abstract template class for badges
#
class Template
def initialize(badge)
@entity = badge.entity
@status = badge.status
end
def key_text
raise NotImplementedError
end
def value_text
raise NotImplementedError
end
def key_width
raise NotImplementedError
end
def value_width
raise NotImplementedError
end
def value_color
raise NotImplementedError
end
def key_color
'#555'
end
def key_text_anchor
key_width / 2
end
def value_text_anchor
key_width + (value_width / 2)
end
def width
key_width + value_width
end
end
end
end
...@@ -11,7 +11,7 @@ module Gitlab ...@@ -11,7 +11,7 @@ module Gitlab
end end
def exec def exec
error = protected_branch_checks || tag_checks || push_checks error = push_checks || tag_checks || protected_branch_checks
if error if error
GitAccessStatus.new(false, error) GitAccessStatus.new(false, error)
......
...@@ -20,10 +20,11 @@ if [ -f /.dockerenv ] || [ -f ./dockerinit ]; then ...@@ -20,10 +20,11 @@ if [ -f /.dockerenv ] || [ -f ./dockerinit ]; then
# Install phantomjs package # Install phantomjs package
pushd vendor/apt pushd vendor/apt
if [ ! -e phantomjs_1.9.8-0jessie_amd64.deb ]; then PHANTOMJS_FILE="phantomjs-$PHANTOMJS_VERSION-linux-x86_64"
wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb if [ ! -d "$PHANTOMJS_FILE" ]; then
curl -q -L "https://s3.amazonaws.com/gitlab-build-helpers/$PHANTOMJS_FILE.tar.bz2" | tar jx
fi fi
dpkg -i phantomjs_1.9.8-0jessie_amd64.deb cp "$PHANTOMJS_FILE/bin/phantomjs" "/usr/bin/"
popd popd
# Try to install packages # Try to install packages
......
...@@ -7,12 +7,13 @@ describe Admin::GroupsController do ...@@ -7,12 +7,13 @@ describe Admin::GroupsController do
before do before do
sign_in(admin) sign_in(admin)
Sidekiq::Testing.fake!
end end
describe 'DELETE #destroy' do describe 'DELETE #destroy' do
it 'schedules a group destroy' do it 'schedules a group destroy' do
expect { delete :destroy, id: project.group.path }.to change(GroupDestroyWorker.jobs, :size).by(1) Sidekiq::Testing.fake! do
expect { delete :destroy, id: project.group.path }.to change(GroupDestroyWorker.jobs, :size).by(1)
end
end end
it 'redirects to the admin group path' do it 'redirects to the admin group path' do
......
...@@ -89,12 +89,13 @@ describe GroupsController do ...@@ -89,12 +89,13 @@ describe GroupsController do
context 'as the group owner' do context 'as the group owner' do
before do before do
Sidekiq::Testing.fake!
sign_in(user) sign_in(user)
end end
it 'schedules a group destroy' do it 'schedules a group destroy' do
expect { delete :destroy, id: group.path }.to change(GroupDestroyWorker.jobs, :size).by(1) Sidekiq::Testing.fake! do
expect { delete :destroy, id: group.path }.to change(GroupDestroyWorker.jobs, :size).by(1)
end
end end
it 'redirects to the root path' do it 'redirects to the root path' do
......
...@@ -55,7 +55,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do ...@@ -55,7 +55,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last updated"' do it 'is "last updated"' do
visit_merge_requests_with_state(project, 'merged') visit_merge_requests_with_state(project, 'merged')
expect(selected_sort_order).to eq('last updated') expect(find('.issues-other-filters')).to have_content('Last updated')
expect(first_merge_request).to include(last_updated_issuable.title) expect(first_merge_request).to include(last_updated_issuable.title)
expect(last_merge_request).to include(first_updated_issuable.title) expect(last_merge_request).to include(first_updated_issuable.title)
end end
...@@ -67,7 +67,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do ...@@ -67,7 +67,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last updated"' do it 'is "last updated"' do
visit_merge_requests_with_state(project, 'closed') visit_merge_requests_with_state(project, 'closed')
expect(selected_sort_order).to eq('last updated') expect(find('.issues-other-filters')).to have_content('Last updated')
expect(first_merge_request).to include(last_updated_issuable.title) expect(first_merge_request).to include(last_updated_issuable.title)
expect(last_merge_request).to include(first_updated_issuable.title) expect(last_merge_request).to include(first_updated_issuable.title)
end end
...@@ -79,7 +79,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do ...@@ -79,7 +79,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last created"' do it 'is "last created"' do
visit_merge_requests_with_state(project, 'all') visit_merge_requests_with_state(project, 'all')
expect(selected_sort_order).to eq('last created') expect(find('.issues-other-filters')).to have_content('Last created')
expect(first_merge_request).to include(last_created_issuable.title) expect(first_merge_request).to include(last_created_issuable.title)
expect(last_merge_request).to include(first_created_issuable.title) expect(last_merge_request).to include(first_created_issuable.title)
end end
...@@ -108,7 +108,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do ...@@ -108,7 +108,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last created"' do it 'is "last created"' do
visit_issues project visit_issues project
expect(selected_sort_order).to eq('last created') expect(find('.issues-other-filters')).to have_content('Last created')
expect(first_issue).to include(last_created_issuable.title) expect(first_issue).to include(last_created_issuable.title)
expect(last_issue).to include(first_created_issuable.title) expect(last_issue).to include(first_created_issuable.title)
end end
...@@ -120,7 +120,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do ...@@ -120,7 +120,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last created"' do it 'is "last created"' do
visit_issues_with_state(project, 'open') visit_issues_with_state(project, 'open')
expect(selected_sort_order).to eq('last created') expect(find('.issues-other-filters')).to have_content('Last created')
expect(first_issue).to include(last_created_issuable.title) expect(first_issue).to include(last_created_issuable.title)
expect(last_issue).to include(first_created_issuable.title) expect(last_issue).to include(first_created_issuable.title)
end end
...@@ -132,7 +132,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do ...@@ -132,7 +132,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last updated"' do it 'is "last updated"' do
visit_issues_with_state(project, 'closed') visit_issues_with_state(project, 'closed')
expect(selected_sort_order).to eq('last updated') expect(find('.issues-other-filters')).to have_content('Last updated')
expect(first_issue).to include(last_updated_issuable.title) expect(first_issue).to include(last_updated_issuable.title)
expect(last_issue).to include(first_updated_issuable.title) expect(last_issue).to include(first_updated_issuable.title)
end end
...@@ -144,7 +144,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do ...@@ -144,7 +144,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last created"' do it 'is "last created"' do
visit_issues_with_state(project, 'all') visit_issues_with_state(project, 'all')
expect(selected_sort_order).to eq('last created') expect(find('.issues-other-filters')).to have_content('Last created')
expect(first_issue).to include(last_created_issuable.title) expect(first_issue).to include(last_created_issuable.title)
expect(last_issue).to include(first_created_issuable.title) expect(last_issue).to include(first_created_issuable.title)
end end
......
...@@ -117,7 +117,7 @@ describe 'Filter issues', feature: true do ...@@ -117,7 +117,7 @@ describe 'Filter issues', feature: true do
find('.dropdown-menu-user-link', text: user.username).click find('.dropdown-menu-user-link', text: user.username).click
wait_for_ajax expect(page).not_to have_selector('.issues-list .issue')
find('.js-label-select').click find('.js-label-select').click
......
...@@ -13,6 +13,8 @@ feature 'Create New Merge Request', feature: true, js: true do ...@@ -13,6 +13,8 @@ feature 'Create New Merge Request', feature: true, js: true do
it 'generates a diff for an orphaned branch' do it 'generates a diff for an orphaned branch' do
click_link 'New Merge Request' click_link 'New Merge Request'
expect(page).to have_content('Source branch')
expect(page).to have_content('Target branch')
first('.js-source-branch').click first('.js-source-branch').click
first('.dropdown-source-branch .dropdown-content a', text: 'orphaned-branch').click first('.dropdown-source-branch .dropdown-content a', text: 'orphaned-branch').click
......
...@@ -68,10 +68,14 @@ describe 'Profile > Preferences', feature: true do ...@@ -68,10 +68,14 @@ describe 'Profile > Preferences', feature: true do
allowing_for_delay do allowing_for_delay do
find('#logo').click find('#logo').click
expect(page).to have_content("You don't have starred projects yet")
expect(page.current_path).to eq starred_dashboard_projects_path expect(page.current_path).to eq starred_dashboard_projects_path
end end
click_link 'Your Projects' click_link 'Your Projects'
expect(page).not_to have_content("You don't have starred projects yet")
expect(page.current_path).to eq dashboard_projects_path expect(page.current_path).to eq dashboard_projects_path
end end
end end
......
require 'spec_helper'
feature 'test coverage badge' do
given!(:user) { create(:user) }
given!(:project) { create(:project, :private) }
given!(:pipeline) do
create(:ci_pipeline, project: project,
ref: 'master',
sha: project.commit.id)
end
context 'when user has access to view badge' do
background do
project.team << [user, :developer]
login_as(user)
end
scenario 'user requests coverage badge image for pipeline' do
create_job(coverage: 100, name: 'test:1')
create_job(coverage: 90, name: 'test:2')
show_test_coverage_badge
expect_coverage_badge('95%')
end
scenario 'user requests coverage badge for specific job' do
create_job(coverage: 50, name: 'test:1')
create_job(coverage: 50, name: 'test:2')
create_job(coverage: 85, name: 'coverage')
show_test_coverage_badge(job: 'coverage')
expect_coverage_badge('85%')
end
scenario 'user requests coverage badge for pipeline without coverage' do
create_job(coverage: nil, name: 'test')
show_test_coverage_badge
expect_coverage_badge('unknown')
end
end
context 'when user does not have access to view badge' do
background { login_as(user) }
scenario 'user requests test coverage badge image' do
show_test_coverage_badge
expect(page).to have_http_status(404)
end
end
def create_job(coverage:, name:)
create(:ci_build, name: name,
coverage: coverage,
pipeline: pipeline)
end
def show_test_coverage_badge(job: nil)
visit coverage_namespace_project_badges_path(
project.namespace, project, ref: :master, job: job, format: :svg)
end
def expect_coverage_badge(coverage)
svg = Nokogiri::XML.parse(page.body)
expect(page.response_headers['Content-Type']).to include('image/svg+xml')
expect(svg.at(%Q{text:contains("#{coverage}")})).to be_truthy
end
end
...@@ -9,25 +9,43 @@ feature 'list of badges' do ...@@ -9,25 +9,43 @@ feature 'list of badges' do
visit namespace_project_pipelines_settings_path(project.namespace, project) visit namespace_project_pipelines_settings_path(project.namespace, project)
end end
scenario 'user displays list of badges' do scenario 'user wants to see build status badge' do
expect(page).to have_content 'build status' page.within('.build-status') do
expect(page).to have_content 'Markdown' expect(page).to have_content 'build status'
expect(page).to have_content 'HTML' expect(page).to have_content 'Markdown'
expect(page).to have_css('.highlight', count: 2) expect(page).to have_content 'HTML'
expect(page).to have_xpath("//img[@alt='build status']") expect(page).to have_css('.highlight', count: 2)
expect(page).to have_xpath("//img[@alt='build status']")
page.within('.highlight', match: :first) do
expect(page).to have_content 'badges/master/build.svg' page.within('.highlight', match: :first) do
expect(page).to have_content 'badges/master/build.svg'
end
end end
end end
scenario 'user changes current ref on badges list page', js: true do scenario 'user wants to see coverage report badge' do
first('.js-project-refs-dropdown').click page.within('.coverage-report') do
expect(page).to have_content 'coverage report'
expect(page).to have_content 'Markdown'
expect(page).to have_content 'HTML'
expect(page).to have_css('.highlight', count: 2)
expect(page).to have_xpath("//img[@alt='coverage report']")
page.within '.project-refs-form' do page.within('.highlight', match: :first) do
click_link 'improve/awesome' expect(page).to have_content 'badges/master/coverage.svg'
end
end end
end
scenario 'user changes current ref of build status badge', js: true do
page.within('.build-status') do
first('.js-project-refs-dropdown').click
expect(page).to have_content 'badges/improve/awesome/build.svg' page.within '.project-refs-form' do
click_link 'improve/awesome'
end
expect(page).to have_content 'badges/improve/awesome/build.svg'
end
end end
end end
require 'spec_helper'
feature 'User wants to edit a file', feature: true do
include WaitForAjax
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:commit_params) do
{
source_branch: project.default_branch,
target_branch: project.default_branch,
commit_message: "Committing First Update",
file_path: ".gitignore",
file_content: "First Update",
last_commit_sha: Gitlab::Git::Commit.last_for_path(project.repository, project.default_branch,
".gitignore").sha
}
end
background do
project.team << [user, :master]
login_as user
visit namespace_project_edit_blob_path(project.namespace, project,
File.join(project.default_branch, '.gitignore'))
end
scenario 'file has been updated since the user opened the edit page' do
Files::UpdateService.new(project, user, commit_params).execute
click_button 'Commit Changes'
expect(page).to have_content 'Someone edited the file the same time you did.'
end
end
...@@ -39,6 +39,7 @@ feature 'project owner creates a license file', feature: true, js: true do ...@@ -39,6 +39,7 @@ feature 'project owner creates a license file', feature: true, js: true do
scenario 'project master creates a license file from the "Add license" link' do scenario 'project master creates a license file from the "Add license" link' do
click_link 'Add License' click_link 'Add License'
expect(page).to have_content('New File')
expect(current_path).to eq( expect(current_path).to eq(
namespace_project_new_blob_path(project.namespace, project, 'master')) namespace_project_new_blob_path(project.namespace, project, 'master'))
expect(find('#file_name').value).to eq('LICENSE') expect(find('#file_name').value).to eq('LICENSE')
......
...@@ -14,6 +14,7 @@ feature 'project owner sees a link to create a license file in empty project', f ...@@ -14,6 +14,7 @@ feature 'project owner sees a link to create a license file in empty project', f
visit namespace_project_path(project.namespace, project) visit namespace_project_path(project.namespace, project)
click_link 'Create empty bare repository' click_link 'Create empty bare repository'
click_on 'LICENSE' click_on 'LICENSE'
expect(page).to have_content('New File')
expect(current_path).to eq( expect(current_path).to eq(
namespace_project_new_blob_path(project.namespace, project, 'master')) namespace_project_new_blob_path(project.namespace, project, 'master'))
......
require 'spec_helper' require 'spec_helper'
feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: true, js: true do feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: true, js: true do
include WaitForAjax
before { allow_any_instance_of(U2fHelper).to receive(:inject_u2f_api?).and_return(true) } before { allow_any_instance_of(U2fHelper).to receive(:inject_u2f_api?).and_return(true) }
def manage_two_factor_authentication
click_on 'Manage Two-Factor Authentication'
expect(page).to have_content("Setup New U2F Device")
wait_for_ajax
end
def register_u2f_device(u2f_device = nil) def register_u2f_device(u2f_device = nil)
u2f_device ||= FakeU2fDevice.new(page) u2f_device ||= FakeU2fDevice.new(page)
u2f_device.respond_to_u2f_registration u2f_device.respond_to_u2f_registration
...@@ -34,7 +42,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: ...@@ -34,7 +42,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
describe 'when 2FA via OTP is enabled' do describe 'when 2FA via OTP is enabled' do
it 'allows registering a new device' do it 'allows registering a new device' do
visit profile_account_path visit profile_account_path
click_on 'Manage Two-Factor Authentication' manage_two_factor_authentication
expect(page.body).to match("You've already enabled two-factor authentication using mobile") expect(page.body).to match("You've already enabled two-factor authentication using mobile")
register_u2f_device register_u2f_device
...@@ -46,15 +54,15 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: ...@@ -46,15 +54,15 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
visit profile_account_path visit profile_account_path
# First device # First device
click_on 'Manage Two-Factor Authentication' manage_two_factor_authentication
register_u2f_device register_u2f_device
expect(page.body).to match('Your U2F device was registered') expect(page.body).to match('Your U2F device was registered')
# Second device # Second device
click_on 'Manage Two-Factor Authentication' manage_two_factor_authentication
register_u2f_device register_u2f_device
expect(page.body).to match('Your U2F device was registered') expect(page.body).to match('Your U2F device was registered')
click_on 'Manage Two-Factor Authentication' manage_two_factor_authentication
expect(page.body).to match('You have 2 U2F devices registered') expect(page.body).to match('You have 2 U2F devices registered')
end end
end end
...@@ -62,7 +70,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: ...@@ -62,7 +70,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
it 'allows the same device to be registered for multiple users' do it 'allows the same device to be registered for multiple users' do
# First user # First user
visit profile_account_path visit profile_account_path
click_on 'Manage Two-Factor Authentication' manage_two_factor_authentication
u2f_device = register_u2f_device u2f_device = register_u2f_device
expect(page.body).to match('Your U2F device was registered') expect(page.body).to match('Your U2F device was registered')
logout logout
...@@ -71,7 +79,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: ...@@ -71,7 +79,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
user = login_as(:user) user = login_as(:user)
user.update_attribute(:otp_required_for_login, true) user.update_attribute(:otp_required_for_login, true)
visit profile_account_path visit profile_account_path
click_on 'Manage Two-Factor Authentication' manage_two_factor_authentication
register_u2f_device(u2f_device) register_u2f_device(u2f_device)
expect(page.body).to match('Your U2F device was registered') expect(page.body).to match('Your U2F device was registered')
...@@ -81,7 +89,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: ...@@ -81,7 +89,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
context "when there are form errors" do context "when there are form errors" do
it "doesn't register the device if there are errors" do it "doesn't register the device if there are errors" do
visit profile_account_path visit profile_account_path
click_on 'Manage Two-Factor Authentication' manage_two_factor_authentication
# Have the "u2f device" respond with bad data # Have the "u2f device" respond with bad data
page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };") page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };")
...@@ -96,7 +104,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: ...@@ -96,7 +104,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
it "allows retrying registration" do it "allows retrying registration" do
visit profile_account_path visit profile_account_path
click_on 'Manage Two-Factor Authentication' manage_two_factor_authentication
# Failed registration # Failed registration
page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };") page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };")
...@@ -122,7 +130,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: ...@@ -122,7 +130,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
login_as(user) login_as(user)
user.update_attribute(:otp_required_for_login, true) user.update_attribute(:otp_required_for_login, true)
visit profile_account_path visit profile_account_path
click_on 'Manage Two-Factor Authentication' manage_two_factor_authentication
@u2f_device = register_u2f_device @u2f_device = register_u2f_device
logout logout
end end
...@@ -161,7 +169,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: ...@@ -161,7 +169,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
current_user = login_as(:user) current_user = login_as(:user)
current_user.update_attribute(:otp_required_for_login, true) current_user.update_attribute(:otp_required_for_login, true)
visit profile_account_path visit profile_account_path
click_on 'Manage Two-Factor Authentication' manage_two_factor_authentication
register_u2f_device register_u2f_device
logout logout
...@@ -182,7 +190,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: ...@@ -182,7 +190,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
current_user = login_as(:user) current_user = login_as(:user)
current_user.update_attribute(:otp_required_for_login, true) current_user.update_attribute(:otp_required_for_login, true)
visit profile_account_path visit profile_account_path
click_on 'Manage Two-Factor Authentication' manage_two_factor_authentication
register_u2f_device(@u2f_device) register_u2f_device(@u2f_device)
logout logout
...@@ -248,7 +256,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: ...@@ -248,7 +256,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
user = login_as(:user) user = login_as(:user)
user.update_attribute(:otp_required_for_login, true) user.update_attribute(:otp_required_for_login, true)
visit profile_account_path visit profile_account_path
click_on 'Manage Two-Factor Authentication' manage_two_factor_authentication
expect(page).to have_content("Your U2F device needs to be set up.") expect(page).to have_content("Your U2F device needs to be set up.")
register_u2f_device register_u2f_device
end end
......
...@@ -42,6 +42,7 @@ describe 'Project variables', js: true do ...@@ -42,6 +42,7 @@ describe 'Project variables', js: true do
find('.btn-variable-edit').click find('.btn-variable-edit').click
end end
expect(page).to have_content('Update variable')
fill_in('variable_key', with: 'key') fill_in('variable_key', with: 'key')
fill_in('variable_value', with: 'key value') fill_in('variable_value', with: 'key value')
click_button('Save variable') click_button('Save variable')
......
...@@ -23,73 +23,36 @@ describe ProjectsFinder do ...@@ -23,73 +23,36 @@ describe ProjectsFinder do
let(:finder) { described_class.new } let(:finder) { described_class.new }
describe 'without a group' do describe 'without a user' do
describe 'without a user' do subject { finder.execute }
subject { finder.execute }
it { is_expected.to eq([public_project]) } it { is_expected.to eq([public_project]) }
end
describe 'with a user' do
subject { finder.execute(user) }
describe 'without private projects' do
it { is_expected.to eq([public_project, internal_project]) }
end
describe 'with private projects' do
before do
private_project.team.add_user(user, Gitlab::Access::MASTER)
end
it do
is_expected.to eq([public_project, internal_project,
private_project])
end
end
end
end end
describe 'with a group' do describe 'with a user' do
describe 'without a user' do subject { finder.execute(user) }
subject { finder.execute(nil, group: group) }
it { is_expected.to eq([public_project]) } describe 'without private projects' do
it { is_expected.to eq([public_project, internal_project]) }
end end
describe 'with a user' do describe 'with private projects' do
subject { finder.execute(user, group: group) } before do
private_project.team.add_user(user, Gitlab::Access::MASTER)
describe 'without shared projects' do
it { is_expected.to eq([public_project, internal_project]) }
end end
describe 'with shared projects and group membership' do it do
before do is_expected.to eq([public_project, internal_project, private_project])
group.add_user(user, Gitlab::Access::DEVELOPER)
shared_project.project_group_links.
create(group_access: Gitlab::Access::MASTER, group: group)
end
it do
is_expected.to eq([shared_project, public_project, internal_project])
end
end end
end
end
describe 'with shared projects and project membership' do describe 'with project_ids_relation' do
before do let(:project_ids_relation) { Project.where(id: internal_project.id) }
shared_project.team.add_user(user, Gitlab::Access::DEVELOPER)
shared_project.project_group_links. subject { finder.execute(user, project_ids_relation) }
create(group_access: Gitlab::Access::MASTER, group: group)
end
it do it { is_expected.to eq([internal_project]) }
is_expected.to eq([shared_project, public_project, internal_project])
end
end
end
end end
end end
end end
require 'spec_helper' require 'spec_helper'
require 'lib/gitlab/badge/shared/metadata'
describe Gitlab::Badge::Build::Metadata do describe Gitlab::Badge::Build::Metadata do
let(:project) { create(:project) } let(:badge) { double(project: create(:project), ref: 'feature') }
let(:branch) { 'master' } let(:metadata) { described_class.new(badge) }
let(:badge) { described_class.new(project, branch) }
describe '#to_html' do it_behaves_like 'badge metadata'
let(:html) { Nokogiri::HTML.parse(badge.to_html) }
let(:a_href) { html.at('a') }
it 'points to link' do describe '#title' do
expect(a_href[:href]).to eq badge.link_url it 'returns build status title' do
end expect(metadata.title).to eq 'build status'
it 'contains clickable image' do
expect(a_href.children.first.name).to eq 'img'
end end
end end
describe '#to_markdown' do
subject { badge.to_markdown }
it { is_expected.to include badge.image_url }
it { is_expected.to include badge.link_url }
end
describe '#image_url' do describe '#image_url' do
subject { badge.image_url } it 'returns valid url' do
it { is_expected.to include "badges/#{branch}/build.svg" } expect(metadata.image_url).to include 'badges/feature/build.svg'
end
end end
describe '#link_url' do describe '#link_url' do
subject { badge.link_url } it 'returns valid link' do
it { is_expected.to include "commits/#{branch}" } expect(metadata.link_url).to include 'commits/feature'
end
end end
end end
require 'spec_helper' require 'spec_helper'
describe Gitlab::Badge::Build do describe Gitlab::Badge::Build::Status do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:sha) { project.commit.sha } let(:sha) { project.commit.sha }
let(:branch) { 'master' } let(:branch) { 'master' }
let(:badge) { described_class.new(project, branch) } let(:badge) { described_class.new(project, branch) }
describe '#entity' do
it 'always says build' do
expect(badge.entity).to eq 'build'
end
end
describe '#template' do
it 'returns badge template' do
expect(badge.template.key_text).to eq 'build'
end
end
describe '#metadata' do describe '#metadata' do
it 'returns badge metadata' do it 'returns badge metadata' do
expect(badge.metadata.image_url) expect(badge.metadata.image_url)
...@@ -13,12 +25,6 @@ describe Gitlab::Badge::Build do ...@@ -13,12 +25,6 @@ describe Gitlab::Badge::Build do
end end
end end
describe '#key_text' do
it 'always says build' do
expect(badge.key_text).to eq 'build'
end
end
context 'build exists' do context 'build exists' do
let!(:build) { create_build(project, sha, branch) } let!(:build) { create_build(project, sha, branch) }
...@@ -30,12 +36,6 @@ describe Gitlab::Badge::Build do ...@@ -30,12 +36,6 @@ describe Gitlab::Badge::Build do
expect(badge.status).to eq 'success' expect(badge.status).to eq 'success'
end end
end end
describe '#value_text' do
it 'returns correct value text' do
expect(badge.value_text).to eq 'success'
end
end
end end
context 'build failed' do context 'build failed' do
...@@ -46,12 +46,6 @@ describe Gitlab::Badge::Build do ...@@ -46,12 +46,6 @@ describe Gitlab::Badge::Build do
expect(badge.status).to eq 'failed' expect(badge.status).to eq 'failed'
end end
end end
describe '#value_text' do
it 'has correct value text' do
expect(badge.value_text).to eq 'failed'
end
end
end end
context 'when outdated pipeline for given ref exists' do context 'when outdated pipeline for given ref exists' do
...@@ -87,12 +81,6 @@ describe Gitlab::Badge::Build do ...@@ -87,12 +81,6 @@ describe Gitlab::Badge::Build do
expect(badge.status).to eq 'unknown' expect(badge.status).to eq 'unknown'
end end
end end
describe '#value_text' do
it 'has correct value text' do
expect(badge.value_text).to eq 'unknown'
end
end
end end
def create_build(project, sha, branch) def create_build(project, sha, branch)
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::Badge::Build::Template do describe Gitlab::Badge::Build::Template do
let(:status) { 'success' } let(:badge) { double(entity: 'build', status: 'success') }
let(:template) { described_class.new(status) } let(:template) { described_class.new(badge) }
describe '#key_text' do describe '#key_text' do
it 'is always says build' do it 'is always says build' do
...@@ -34,15 +34,15 @@ describe Gitlab::Badge::Build::Template do ...@@ -34,15 +34,15 @@ describe Gitlab::Badge::Build::Template do
describe '#value_color' do describe '#value_color' do
context 'when status is success' do context 'when status is success' do
let(:status) { 'success' }
it 'has expected color' do it 'has expected color' do
expect(template.value_color).to eq '#4c1' expect(template.value_color).to eq '#4c1'
end end
end end
context 'when status is failed' do context 'when status is failed' do
let(:status) { 'failed' } before do
allow(badge).to receive(:status).and_return('failed')
end
it 'has expected color' do it 'has expected color' do
expect(template.value_color).to eq '#e05d44' expect(template.value_color).to eq '#e05d44'
...@@ -50,7 +50,9 @@ describe Gitlab::Badge::Build::Template do ...@@ -50,7 +50,9 @@ describe Gitlab::Badge::Build::Template do
end end
context 'when status is running' do context 'when status is running' do
let(:status) { 'running' } before do
allow(badge).to receive(:status).and_return('running')
end
it 'has expected color' do it 'has expected color' do
expect(template.value_color).to eq '#dfb317' expect(template.value_color).to eq '#dfb317'
...@@ -58,7 +60,9 @@ describe Gitlab::Badge::Build::Template do ...@@ -58,7 +60,9 @@ describe Gitlab::Badge::Build::Template do
end end
context 'when status is unknown' do context 'when status is unknown' do
let(:status) { 'unknown' } before do
allow(badge).to receive(:status).and_return('unknown')
end
it 'has expected color' do it 'has expected color' do
expect(template.value_color).to eq '#9f9f9f' expect(template.value_color).to eq '#9f9f9f'
...@@ -66,7 +70,9 @@ describe Gitlab::Badge::Build::Template do ...@@ -66,7 +70,9 @@ describe Gitlab::Badge::Build::Template do
end end
context 'when status does not match any known statuses' do context 'when status does not match any known statuses' do
let(:status) { 'invalid status' } before do
allow(badge).to receive(:status).and_return('invalid')
end
it 'has expected color' do it 'has expected color' do
expect(template.value_color).to eq '#9f9f9f' expect(template.value_color).to eq '#9f9f9f'
......
require 'spec_helper'
require 'lib/gitlab/badge/shared/metadata'
describe Gitlab::Badge::Coverage::Metadata do
let(:badge) do
double(project: create(:project), ref: 'feature', job: 'test')
end
let(:metadata) { described_class.new(badge) }
it_behaves_like 'badge metadata'
describe '#title' do
it 'returns coverage report title' do
expect(metadata.title).to eq 'coverage report'
end
end
describe '#image_url' do
it 'returns valid url' do
expect(metadata.image_url).to include 'badges/feature/coverage.svg'
end
end
describe '#link_url' do
it 'returns valid link' do
expect(metadata.link_url).to include 'commits/feature'
end
end
end
require 'spec_helper'
describe Gitlab::Badge::Coverage::Report do
let(:project) { create(:project) }
let(:job_name) { nil }
let(:badge) do
described_class.new(project, 'master', job_name)
end
describe '#entity' do
it 'describes a coverage' do
expect(badge.entity).to eq 'coverage'
end
end
describe '#metadata' do
it 'returns correct metadata' do
expect(badge.metadata.image_url).to include 'coverage.svg'
end
end
describe '#template' do
it 'returns correct template' do
expect(badge.template.key_text).to eq 'coverage'
end
end
shared_examples 'unknown coverage report' do
context 'particular job specified' do
let(:job_name) { '' }
it 'returns nil' do
expect(badge.status).to be_nil
end
end
context 'particular job not specified' do
let(:job_name) { nil }
it 'returns nil' do
expect(badge.status).to be_nil
end
end
end
context 'pipeline exists' do
let!(:pipeline) do
create(:ci_pipeline, project: project,
sha: project.commit.id,
ref: 'master')
end
context 'builds exist' do
before do
create(:ci_build, name: 'first', pipeline: pipeline, coverage: 40)
create(:ci_build, pipeline: pipeline, coverage: 60)
end
context 'particular job specified' do
let(:job_name) { 'first' }
it 'returns coverage for the particular job' do
expect(badge.status).to eq 40
end
end
context 'particular job not specified' do
let(:job_name) { '' }
it 'returns arithemetic mean for the pipeline' do
expect(badge.status).to eq 50
end
end
end
context 'builds do not exist' do
it_behaves_like 'unknown coverage report'
context 'particular job specified' do
let(:job_name) { 'nonexistent' }
it 'retruns nil' do
expect(badge.status).to be_nil
end
end
end
end
context 'pipeline does not exist' do
it_behaves_like 'unknown coverage report'
end
end
require 'spec_helper'
describe Gitlab::Badge::Coverage::Template do
let(:badge) { double(entity: 'coverage', status: 90) }
let(:template) { described_class.new(badge) }
describe '#key_text' do
it 'is always says coverage' do
expect(template.key_text).to eq 'coverage'
end
end
describe '#value_text' do
context 'when coverage is known' do
it 'returns coverage percentage' do
expect(template.value_text).to eq '90%'
end
end
context 'when coverage is unknown' do
before do
allow(badge).to receive(:status).and_return(nil)
end
it 'returns string that says coverage is unknown' do
expect(template.value_text).to eq 'unknown'
end
end
end
describe '#key_width' do
it 'has a fixed key width' do
expect(template.key_width).to eq 62
end
end
describe '#value_width' do
context 'when coverage is known' do
it 'is narrower when coverage is known' do
expect(template.value_width).to eq 36
end
end
context 'when coverage is unknown' do
before do
allow(badge).to receive(:status).and_return(nil)
end
it 'is wider when coverage is unknown to fit text' do
expect(template.value_width).to eq 58
end
end
end
describe '#key_color' do
it 'always has the same color' do
expect(template.key_color).to eq '#555'
end
end
describe '#value_color' do
context 'when coverage is good' do
before do
allow(badge).to receive(:status).and_return(98)
end
it 'is green' do
expect(template.value_color).to eq '#4c1'
end
end
context 'when coverage is acceptable' do
before do
allow(badge).to receive(:status).and_return(90)
end
it 'is green-orange' do
expect(template.value_color).to eq '#a3c51c'
end
end
context 'when coverage is medium' do
before do
allow(badge).to receive(:status).and_return(75)
end
it 'is orange-yellow' do
expect(template.value_color).to eq '#dfb317'
end
end
context 'when coverage is low' do
before do
allow(badge).to receive(:status).and_return(50)
end
it 'is red' do
expect(template.value_color).to eq '#e05d44'
end
end
context 'when coverage is unknown' do
before do
allow(badge).to receive(:status).and_return(nil)
end
it 'is grey' do
expect(template.value_color).to eq '#9f9f9f'
end
end
end
describe '#width' do
context 'when coverage is known' do
it 'returns the key width plus value width' do
expect(template.width).to eq 98
end
end
context 'when coverage is unknown' do
before do
allow(badge).to receive(:status).and_return(nil)
end
it 'returns key width plus wider value width' do
expect(template.width).to eq 120
end
end
end
end
shared_examples 'badge metadata' do
describe '#to_html' do
let(:html) { Nokogiri::HTML.parse(metadata.to_html) }
let(:a_href) { html.at('a') }
it 'points to link' do
expect(a_href[:href]).to eq metadata.link_url
end
it 'contains clickable image' do
expect(a_href.children.first.name).to eq 'img'
end
end
describe '#to_markdown' do
subject { metadata.to_markdown }
it { is_expected.to include metadata.image_url }
it { is_expected.to include metadata.link_url }
end
end
require 'spec_helper'
describe Gitlab::Checks::ChangeAccess, lib: true do
describe '#exec' do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:user_access) { Gitlab::UserAccess.new(user, project: project) }
let(:changes) do
{
oldrev: 'be93687618e4b132087f430a4d8fc3a609c9b77c',
newrev: '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51',
ref: 'refs/heads/master'
}
end
subject { described_class.new(changes, project: project, user_access: user_access).exec }
before { allow(user_access).to receive(:can_do_action?).with(:push_code).and_return(true) }
context 'without failed checks' do
it "doesn't return any error" do
expect(subject.status).to be(true)
end
end
context 'when the user is not allowed to push code' do
it 'returns an error' do
expect(user_access).to receive(:can_do_action?).with(:push_code).and_return(false)
expect(subject.status).to be(false)
expect(subject.message).to eq('You are not allowed to push code to this project.')
end
end
context 'tags check' do
let(:changes) do
{
oldrev: 'be93687618e4b132087f430a4d8fc3a609c9b77c',
newrev: '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51',
ref: 'refs/tags/v1.0.0'
}
end
it 'returns an error if the user is not allowed to update tags' do
expect(user_access).to receive(:can_do_action?).with(:admin_project).and_return(false)
expect(subject.status).to be(false)
expect(subject.message).to eq('You are not allowed to change existing tags on this project.')
end
end
context 'protected branches check' do
before do
allow(project).to receive(:protected_branch?).with('master').and_return(true)
end
it 'returns an error if the user is not allowed to do forced pushes to protected branches' do
expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true)
expect(user_access).to receive(:can_do_action?).with(:force_push_code_to_protected_branches).and_return(false)
expect(subject.status).to be(false)
expect(subject.message).to eq('You are not allowed to force push code to a protected branch on this project.')
end
it 'returns an error if the user is not allowed to merge to protected branches' do
expect_any_instance_of(Gitlab::Checks::MatchingMergeRequest).to receive(:match?).and_return(true)
expect(user_access).to receive(:can_merge_to_branch?).and_return(false)
expect(user_access).to receive(:can_push_to_branch?).and_return(false)
expect(subject.status).to be(false)
expect(subject.message).to eq('You are not allowed to merge code into protected branches on this project.')
end
it 'returns an error if the user is not allowed to push to protected branches' do
expect(user_access).to receive(:can_push_to_branch?).and_return(false)
expect(subject.status).to be(false)
expect(subject.message).to eq('You are not allowed to push code to protected branches on this project.')
end
context 'branch deletion' do
let(:changes) do
{
oldrev: 'be93687618e4b132087f430a4d8fc3a609c9b77c',
newrev: '0000000000000000000000000000000000000000',
ref: 'refs/heads/master'
}
end
it 'returns an error if the user is not allowed to delete protected branches' do
expect(user_access).to receive(:can_do_action?).with(:remove_protected_branches).and_return(false)
expect(subject.status).to be(false)
expect(subject.message).to eq('You are not allowed to delete protected branches from this project.')
end
end
end
end
end
...@@ -94,4 +94,26 @@ describe Blob do ...@@ -94,4 +94,26 @@ describe Blob do
expect(blob.to_partial_path).to eq 'download' expect(blob.to_partial_path).to eq 'download'
end end
end end
describe '#size_within_svg_limits?' do
let(:blob) { described_class.decorate(double(:blob)) }
it 'returns true when the blob size is smaller than the SVG limit' do
expect(blob).to receive(:size).and_return(42)
expect(blob.size_within_svg_limits?).to eq(true)
end
it 'returns true when the blob size is equal to the SVG limit' do
expect(blob).to receive(:size).and_return(Blob::MAXIMUM_SVG_SIZE)
expect(blob.size_within_svg_limits?).to eq(true)
end
it 'returns false when the blob size is larger than the SVG limit' do
expect(blob).to receive(:size).and_return(1.terabyte)
expect(blob.size_within_svg_limits?).to eq(false)
end
end
end end
...@@ -15,4 +15,28 @@ describe Deployment, models: true do ...@@ -15,4 +15,28 @@ describe Deployment, models: true do
it { is_expected.to validate_presence_of(:ref) } it { is_expected.to validate_presence_of(:ref) }
it { is_expected.to validate_presence_of(:sha) } it { is_expected.to validate_presence_of(:sha) }
describe '#includes_commit?' do
let(:project) { create(:project) }
let(:environment) { create(:environment, project: project) }
let(:deployment) do
create(:deployment, environment: environment, sha: project.commit.id)
end
context 'when there is no project commit' do
it 'returns false' do
commit = project.commit('feature')
expect(deployment.includes_commit?(commit)).to be false
end
end
context 'when they share the same tree branch' do
it 'returns true' do
commit = project.commit
expect(deployment.includes_commit?(commit)).to be true
end
end
end
end end
...@@ -30,4 +30,37 @@ describe Environment, models: true do ...@@ -30,4 +30,37 @@ describe Environment, models: true do
expect(env.external_url).to be_nil expect(env.external_url).to be_nil
end end
end end
describe '#includes_commit?' do
context 'without a last deployment' do
it "returns false" do
expect(environment.includes_commit?('HEAD')).to be false
end
end
context 'with a last deployment' do
let(:project) { create(:project) }
let(:environment) { create(:environment, project: project) }
let!(:deployment) do
create(:deployment, environment: environment, sha: project.commit('master').id)
end
context 'in the same branch' do
it 'returns true' do
expect(environment.includes_commit?(RepoHelpers.sample_commit)).to be true
end
end
context 'not in the same branch' do
before do
deployment.update(sha: project.commit('feature').id)
end
it 'returns false' do
expect(environment.includes_commit?(RepoHelpers.sample_commit)).to be false
end
end
end
end
end end
...@@ -674,6 +674,21 @@ describe MergeRequest, models: true do ...@@ -674,6 +674,21 @@ describe MergeRequest, models: true do
end end
end end
describe "#environments" do
let(:project) { create(:project) }
let!(:environment) { create(:environment, project: project) }
let!(:environment1) { create(:environment, project: project) }
let!(:environment2) { create(:environment, project: project) }
let(:merge_request) { create(:merge_request, source_project: project) }
it 'selects deployed environments' do
create(:deployment, environment: environment, sha: project.commit('master').id)
create(:deployment, environment: environment1, sha: project.commit('feature').id)
expect(merge_request.environments).to eq [environment]
end
end
describe "#reload_diff" do describe "#reload_diff" do
let(:note) { create(:diff_note_on_merge_request, project: subject.project, noteable: subject) } let(:note) { create(:diff_note_on_merge_request, project: subject.project, noteable: subject) }
......
...@@ -52,19 +52,20 @@ describe IrkerService, models: true do ...@@ -52,19 +52,20 @@ describe IrkerService, models: true do
let(:colorize_messages) { '1' } let(:colorize_messages) { '1' }
before do before do
@irker_server = TCPServer.new 'localhost', 0
allow(irker).to receive_messages( allow(irker).to receive_messages(
active: true, active: true,
project: project, project: project,
project_id: project.id, project_id: project.id,
service_hook: true, service_hook: true,
server_host: 'localhost', server_host: @irker_server.addr[2],
server_port: 6659, server_port: @irker_server.addr[1],
default_irc_uri: 'irc://chat.freenode.net/', default_irc_uri: 'irc://chat.freenode.net/',
recipients: recipients, recipients: recipients,
colorize_messages: colorize_messages) colorize_messages: colorize_messages)
irker.valid? irker.valid?
@irker_server = TCPServer.new 'localhost', 6659
end end
after do after do
......
...@@ -39,4 +39,75 @@ describe PivotaltrackerService, models: true do ...@@ -39,4 +39,75 @@ describe PivotaltrackerService, models: true do
it { is_expected.not_to validate_presence_of(:token) } it { is_expected.not_to validate_presence_of(:token) }
end end
end end
describe 'Execute' do
let(:service) do
PivotaltrackerService.new.tap do |service|
service.token = 'secret_api_token'
end
end
let(:url) { PivotaltrackerService::API_ENDPOINT }
def push_data(branch: 'master')
{
object_kind: 'push',
ref: "refs/heads/#{branch}",
commits: [
{
id: '21c12ea',
author: {
name: 'Some User'
},
url: 'https://example.com/commit',
message: 'commit message',
}
]
}
end
before do
WebMock.stub_request(:post, url)
end
it 'should post correct message' do
service.execute(push_data)
expect(WebMock).to have_requested(:post, url).with(
body: {
'source_commit' => {
'commit_id' => '21c12ea',
'author' => 'Some User',
'url' => 'https://example.com/commit',
'message' => 'commit message'
}
},
headers: {
'Content-Type' => 'application/json',
'X-TrackerToken' => 'secret_api_token'
}
).once
end
context 'when allowed branches is specified' do
let(:service) do
super().tap do |service|
service.restrict_to_branch = 'master,v10'
end
end
it 'should post message if branch is in the list' do
service.execute(push_data(branch: 'master'))
service.execute(push_data(branch: 'v10'))
expect(WebMock).to have_requested(:post, url).twice
end
it 'should not post message if branch is not in the list' do
service.execute(push_data(branch: 'mas'))
service.execute(push_data(branch: 'v11'))
expect(WebMock).not_to have_requested(:post, url)
end
end
end
end end
...@@ -117,6 +117,12 @@ describe API::Todos, api: true do ...@@ -117,6 +117,12 @@ describe API::Todos, api: true do
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(pending_1.reload).to be_done expect(pending_1.reload).to be_done
end end
it 'updates todos cache' do
expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original
delete api("/todos/#{pending_1.id}", john_doe)
end
end end
end end
...@@ -139,6 +145,12 @@ describe API::Todos, api: true do ...@@ -139,6 +145,12 @@ describe API::Todos, api: true do
expect(pending_2.reload).to be_done expect(pending_2.reload).to be_done
expect(pending_3.reload).to be_done expect(pending_3.reload).to be_done
end end
it 'updates todos cache' do
expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original
delete api("/todos", john_doe)
end
end end
end end
......
require "spec_helper"
describe Files::UpdateService do
subject { described_class.new(project, user, commit_params) }
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:file_path) { 'files/ruby/popen.rb' }
let(:new_contents) { "New Content" }
let(:commit_params) do
{
file_path: file_path,
commit_message: "Update File",
file_content: new_contents,
file_content_encoding: "text",
last_commit_sha: last_commit_sha,
source_project: project,
source_branch: project.default_branch,
target_branch: project.default_branch,
}
end
before do
project.team << [user, :master]
end
describe "#execute" do
context "when the file's last commit sha does not match the supplied last_commit_sha" do
let(:last_commit_sha) { "foo" }
it "returns a hash with the correct error message and a :error status " do
expect { subject.execute }.
to raise_error(Files::UpdateService::FileChangedError,
"You are attempting to update a file that has changed since you started editing it.")
end
end
context "when the file's last commit sha does match the supplied last_commit_sha" do
let(:last_commit_sha) { Gitlab::Git::Commit.last_for_path(project.repository, project.default_branch, file_path).sha }
it "returns a hash with the :success status " do
results = subject.execute
expect(results).to match({ status: :success })
end
it "updates the file with the new contents" do
subject.execute
results = project.repository.blob_at_branch(project.default_branch, file_path)
expect(results.data).to eq(new_contents)
end
end
context "when the last_commit_sha is not supplied" do
let(:commit_params) do
{
file_path: file_path,
commit_message: "Update File",
file_content: new_contents,
file_content_encoding: "text",
source_project: project,
source_branch: project.default_branch,
target_branch: project.default_branch,
}
end
it "returns a hash with the :success status " do
results = subject.execute
expect(results).to match({ status: :success })
end
it "updates the file with the new contents" do
subject.execute
results = project.repository.blob_at_branch(project.default_branch, file_path)
expect(results.data).to eq(new_contents)
end
end
end
end
...@@ -7,7 +7,9 @@ describe MergeRequests::GetUrlsService do ...@@ -7,7 +7,9 @@ describe MergeRequests::GetUrlsService do
let(:new_merge_request_url) { "http://localhost/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{source_branch}" } let(:new_merge_request_url) { "http://localhost/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{source_branch}" }
let(:show_merge_request_url) { "http://localhost/#{project.namespace.name}/#{project.path}/merge_requests/#{merge_request.iid}" } let(:show_merge_request_url) { "http://localhost/#{project.namespace.name}/#{project.path}/merge_requests/#{merge_request.iid}" }
let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" } let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
let(:deleted_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 #{Gitlab::Git::BLANK_SHA} refs/heads/#{source_branch}" }
let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" } let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
let(:default_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master" }
describe "#execute" do describe "#execute" do
shared_examples 'new_merge_request_link' do shared_examples 'new_merge_request_link' do
...@@ -32,6 +34,28 @@ describe MergeRequests::GetUrlsService do ...@@ -32,6 +34,28 @@ describe MergeRequests::GetUrlsService do
end end
end end
shared_examples 'no_merge_request_url' do
it 'returns no URL' do
result = service.execute(changes)
expect(result).to be_empty
end
end
context 'pushing to default branch' do
let(:changes) { default_branch_changes }
it_behaves_like 'no_merge_request_url'
end
context 'pushing to project with MRs disabled' do
let(:changes) { new_branch_changes }
before do
project.merge_requests_enabled = false
end
it_behaves_like 'no_merge_request_url'
end
context 'pushing one completely new branch' do context 'pushing one completely new branch' do
let(:changes) { new_branch_changes } let(:changes) { new_branch_changes }
it_behaves_like 'new_merge_request_link' it_behaves_like 'new_merge_request_link'
...@@ -42,6 +66,11 @@ describe MergeRequests::GetUrlsService do ...@@ -42,6 +66,11 @@ describe MergeRequests::GetUrlsService do
it_behaves_like 'new_merge_request_link' it_behaves_like 'new_merge_request_link'
end end
context 'pushing to deleted branch' do
let(:changes) { deleted_branch_changes }
it_behaves_like 'no_merge_request_url'
end
context 'pushing to existing branch and merge request opened' do context 'pushing to existing branch and merge request opened' do
let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch) } let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch) }
let(:changes) { existing_branch_changes } let(:changes) { existing_branch_changes }
...@@ -61,6 +90,11 @@ describe MergeRequests::GetUrlsService do ...@@ -61,6 +90,11 @@ describe MergeRequests::GetUrlsService do
let(:changes) { existing_branch_changes } let(:changes) { existing_branch_changes }
# Source project is now the forked one # Source project is now the forked one
let(:service) { MergeRequests::GetUrlsService.new(forked_project) } let(:service) { MergeRequests::GetUrlsService.new(forked_project) }
before do
allow(forked_project).to receive(:empty_repo?).and_return(false)
end
it_behaves_like 'show_merge_request_url' it_behaves_like 'show_merge_request_url'
end end
......
...@@ -472,6 +472,42 @@ describe TodoService, services: true do ...@@ -472,6 +472,42 @@ describe TodoService, services: true do
expect(john_doe.todos_pending_count).to eq(1) expect(john_doe.todos_pending_count).to eq(1)
end end
describe '#mark_todos_as_done' do
let(:issue) { create(:issue, project: project, author: author, assignee: john_doe) }
it 'marks a relation of todos as done' do
create(:todo, :mentioned, user: john_doe, target: issue, project: project)
todos = TodosFinder.new(john_doe, {}).execute
expect { TodoService.new.mark_todos_as_done(todos, john_doe) }
.to change { john_doe.todos.done.count }.from(0).to(1)
end
it 'marks an array of todos as done' do
todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
expect { TodoService.new.mark_todos_as_done([todo], john_doe) }
.to change { todo.reload.state }.from('pending').to('done')
end
it 'returns the number of updated todos' do # Needed on API
todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
expect(TodoService.new.mark_todos_as_done([todo], john_doe)).to eq(1)
end
it 'caches the number of todos of a user', :caching do
create(:todo, :mentioned, user: john_doe, target: issue, project: project)
todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
TodoService.new.mark_todos_as_done([todo], john_doe)
expect_any_instance_of(TodosFinder).not_to receive(:execute)
expect(john_doe.todos_done_count).to eq(1)
expect(john_doe.todos_pending_count).to eq(1)
end
end
def should_create_todo(attributes = {}) def should_create_todo(attributes = {})
attributes.reverse_merge!( attributes.reverse_merge!(
project: project, project: project,
......
require 'spec_helper'
describe 'projects/merge_requests/widget/_heading' do
include Devise::TestHelpers
context 'when released to an environment' do
let(:project) { merge_request.target_project }
let(:merge_request) { create(:merge_request, :merged) }
let(:environment) { create(:environment, project: project) }
let!(:deployment) do
create(:deployment, environment: environment, sha: project.commit('master').id)
end
before do
assign(:merge_request, merge_request)
assign(:project, project)
render
end
it 'displays that the environment is deployed' do
expect(rendered).to match("Deployed to")
expect(rendered).to match("#{environment.name}")
end
end
end
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