summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml1
-rw-r--r--CHANGELOG9
-rw-r--r--app/assets/javascripts/issuable.js11
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss5
-rw-r--r--app/controllers/dashboard/todos_controller.rb4
-rw-r--r--app/controllers/projects/badges_controller.rb17
-rw-r--r--app/controllers/projects/blob_controller.rb14
-rw-r--r--app/controllers/projects/pipelines_settings_controller.rb8
-rw-r--r--app/finders/projects_finder.rb3
-rw-r--r--app/finders/todos_finder.rb20
-rw-r--r--app/helpers/sorting_helper.rb8
-rw-r--r--app/helpers/todos_helper.rb4
-rw-r--r--app/models/blob.rb7
-rw-r--r--app/models/deployment.rb6
-rw-r--r--app/models/environment.rb6
-rw-r--r--app/models/merge_request.rb8
-rw-r--r--app/models/project_services/pivotaltracker_service.rb31
-rw-r--r--app/models/project_wiki.rb4
-rw-r--r--app/models/user.rb4
-rw-r--r--app/services/files/base_service.rb1
-rw-r--r--app/services/files/update_service.rb23
-rw-r--r--app/services/merge_requests/get_urls_service.rb13
-rw-r--r--app/services/todo_service.rb3
-rw-r--r--app/views/projects/blob/_image.html.haml16
-rw-r--r--app/views/projects/blob/edit.html.haml9
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml13
-rw-r--r--app/views/projects/pipelines_settings/_badge.html.haml27
-rw-r--r--app/views/projects/pipelines_settings/show.html.haml25
-rw-r--r--app/views/shared/projects/_project.html.haml2
-rw-r--r--config/routes.rb5
-rw-r--r--db/fixtures/development/14_builds.rb59
-rw-r--r--db/schema.rb2
-rw-r--r--doc/api/services.md4
-rw-r--r--doc/ci/pipelines.md35
-rw-r--r--doc/ci/quick_start/README.md12
-rw-r--r--features/steps/dashboard/dashboard.rb1
-rw-r--r--features/steps/dashboard/event_filters.rb13
-rw-r--r--features/steps/dashboard/issues.rb5
-rw-r--r--features/steps/dashboard/merge_requests.rb5
-rw-r--r--features/steps/dashboard/new_project.rb2
-rw-r--r--features/steps/project/builds/artifacts.rb1
-rw-r--r--features/steps/project/forked_merge_requests.rb3
-rw-r--r--features/steps/project/issues/issues.rb2
-rw-r--r--features/steps/project/merge_requests.rb2
-rw-r--r--features/steps/project/source/browse_files.rb2
-rw-r--r--features/steps/project/wiki.rb2
-rw-r--r--features/steps/shared/issuable.rb4
-rw-r--r--features/support/wait_for_ajax.rb11
-rw-r--r--lib/api/todos.rb8
-rw-r--r--lib/banzai/filter/sanitization_filter.rb4
-rw-r--r--lib/gitlab/badge/base.rb21
-rw-r--r--lib/gitlab/badge/build.rb30
-rw-r--r--lib/gitlab/badge/build/metadata.rb22
-rw-r--r--lib/gitlab/badge/build/status.rb37
-rw-r--r--lib/gitlab/badge/build/template.rb32
-rw-r--r--lib/gitlab/badge/coverage/metadata.rb30
-rw-r--r--lib/gitlab/badge/coverage/report.rb56
-rw-r--r--lib/gitlab/badge/coverage/template.rb52
-rw-r--r--lib/gitlab/badge/metadata.rb36
-rw-r--r--lib/gitlab/badge/template.rb49
-rw-r--r--lib/gitlab/checks/change_access.rb2
-rwxr-xr-xscripts/prepare_build.sh7
-rw-r--r--spec/controllers/admin/groups_controller_spec.rb5
-rw-r--r--spec/controllers/groups_controller_spec.rb5
-rw-r--r--spec/features/issuables/default_sort_order_spec.rb14
-rw-r--r--spec/features/issues/filter_issues_spec.rb2
-rw-r--r--spec/features/merge_requests/create_new_mr_spec.rb2
-rw-r--r--spec/features/profiles/preferences_spec.rb4
-rw-r--r--spec/features/projects/badges/coverage_spec.rb73
-rw-r--r--spec/features/projects/badges/list_spec.rb46
-rw-r--r--spec/features/projects/files/editing_a_file_spec.rb34
-rw-r--r--spec/features/projects/files/project_owner_creates_license_file_spec.rb1
-rw-r--r--spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb1
-rw-r--r--spec/features/u2f_spec.rb32
-rw-r--r--spec/features/variables_spec.rb1
-rw-r--r--spec/finders/projects_finder_spec.rb73
-rw-r--r--spec/lib/gitlab/badge/build/metadata_spec.rb36
-rw-r--r--spec/lib/gitlab/badge/build/status_spec.rb (renamed from spec/lib/gitlab/badge/build_spec.rb)38
-rw-r--r--spec/lib/gitlab/badge/build/template_spec.rb22
-rw-r--r--spec/lib/gitlab/badge/coverage/metadata_spec.rb30
-rw-r--r--spec/lib/gitlab/badge/coverage/report_spec.rb93
-rw-r--r--spec/lib/gitlab/badge/coverage/template_spec.rb130
-rw-r--r--spec/lib/gitlab/badge/shared/metadata.rb21
-rw-r--r--spec/lib/gitlab/checks/change_access_spec.rb99
-rw-r--r--spec/models/blob_spec.rb22
-rw-r--r--spec/models/deployment_spec.rb24
-rw-r--r--spec/models/environment_spec.rb33
-rw-r--r--spec/models/merge_request_spec.rb15
-rw-r--r--spec/models/project_services/irker_service_spec.rb7
-rw-r--r--spec/models/project_services/pivotaltracker_service_spec.rb71
-rw-r--r--spec/requests/api/todos_spec.rb12
-rw-r--r--spec/services/files/update_service_spec.rb84
-rw-r--r--spec/services/merge_requests/get_urls_service_spec.rb34
-rw-r--r--spec/services/todo_service_spec.rb36
-rw-r--r--spec/views/projects/merge_requests/_heading.html.haml_spec.rb26
95 files changed, 1634 insertions, 350 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e8d54e768d3..be5614520a5 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -15,6 +15,7 @@ variables:
USE_DB: "true"
USE_BUNDLE_INSTALL: "true"
GIT_DEPTH: "20"
+ PHANTOMJS_VERSION: "2.1.1"
before_script:
- source ./scripts/prepare_build.sh
diff --git a/CHANGELOG b/CHANGELOG
index 96965a20f69..616f006b29e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,7 +1,9 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.11.0 (unreleased)
+ - Add test coverage report badge. !5708
- 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 rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres)
- Fix the title of the toggle dropdown button. !5515 (herminiotorres)
@@ -32,12 +34,14 @@ v 8.11.0 (unreleased)
- Add "No one can push" as an option for protected branches. !5081
- Improve performance of AutolinkFilter#text_parse by using XPath
- 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)
- Environments have an url to link to
- Various redundant database indexes have been removed
- Update `timeago` plugin to use multiple string/locale settings
- Remove unused images (ClemMakesApps)
- 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)
- 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)
@@ -54,6 +58,7 @@ v 8.11.0 (unreleased)
- 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
- Nokogiri's various parsing methods are now instrumented
+ - Add archived badge to project list !5798
- Add simple identifier to public SSH keys (muteor)
- 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
@@ -107,6 +112,9 @@ v 8.11.0 (unreleased)
- Sort folders with submodules in Files view !5521
- 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)
+ - 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
- Add a data migration to fix some missing timestamps in the members table. !5670
@@ -271,6 +279,7 @@ v 8.10.0
- Fix new snippet style bug (elliotec)
- Instrument Rinku usage
- Be explicit to define merge request discussion variables
+ - Use cache for todos counter calling TodoService
- Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab
- 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)
diff --git a/app/assets/javascripts/issuable.js b/app/assets/javascripts/issuable.js
index f27f1bad1f7..d0305c6c6a1 100644
--- a/app/assets/javascripts/issuable.js
+++ b/app/assets/javascripts/issuable.js
@@ -5,13 +5,10 @@
this.Issuable = {
init: function() {
- if (!issuable_created) {
- issuable_created = true;
- Issuable.initTemplates();
- Issuable.initSearch();
- Issuable.initChecks();
- return Issuable.initLabelFilterRemove();
- }
+ Issuable.initTemplates();
+ Issuable.initSearch();
+ Issuable.initChecks();
+ return Issuable.initLabelFilterRemove();
},
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> <% }); %>');
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 0a661e529f0..b4636269518 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -69,6 +69,10 @@
&.ci-success {
color: $gl-success;
+
+ a.environment {
+ color: inherit;
+ }
}
&.ci-success_with_warnings {
@@ -126,7 +130,6 @@
&.has-conflicts .fa-exclamation-triangle {
color: $gl-warning;
}
-
}
p:last-child {
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index 19a76a5b5d8..1243bb96d4d 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -37,8 +37,8 @@ class Dashboard::TodosController < Dashboard::ApplicationController
def todos_counts
{
- count: TodosFinder.new(current_user, state: :pending).execute.count,
- done_count: TodosFinder.new(current_user, state: :done).execute.count
+ count: current_user.todos_pending_count,
+ done_count: current_user.todos_done_count
}
end
end
diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb
index d0f5071d2cc..6c25cd83a24 100644
--- a/app/controllers/projects/badges_controller.rb
+++ b/app/controllers/projects/badges_controller.rb
@@ -4,11 +4,24 @@ class Projects::BadgesController < Projects::ApplicationController
before_action :no_cache_headers, except: [:index]
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|
format.html { render_404 }
-
format.svg do
render 'badge', locals: { badge: badge.template }
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 19d051720e9..cdf9a04bacf 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -17,6 +17,7 @@ class Projects::BlobController < Projects::ApplicationController
before_action :require_branch_head, only: [:edit, :update]
before_action :editor_variables, except: [:show, :preview, :diff]
before_action :validate_diff_params, only: :diff
+ before_action :set_last_commit_sha, only: [:edit, :update]
def new
commit unless @repository.empty?
@@ -33,7 +34,6 @@ class Projects::BlobController < Projects::ApplicationController
end
def edit
- @last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
blob.load_all_data!(@repository)
end
@@ -55,6 +55,10 @@ class Projects::BlobController < Projects::ApplicationController
create_commit(Files::UpdateService, success_path: after_edit_path,
failure_view: :edit,
failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
+
+ rescue Files::UpdateService::FileChangedError
+ @conflict = true
+ render :edit
end
def preview
@@ -152,7 +156,8 @@ class Projects::BlobController < Projects::ApplicationController
file_path: @file_path,
commit_message: params[:commit_message],
file_content: params[:content],
- file_content_encoding: params[:encoding]
+ file_content_encoding: params[:encoding],
+ last_commit_sha: params[:last_commit_sha]
}
end
@@ -161,4 +166,9 @@ class Projects::BlobController < Projects::ApplicationController
render nothing: true
end
end
+
+ def set_last_commit_sha
+ @last_commit_sha = Gitlab::Git::Commit.
+ last_for_path(@repository, @ref, @path).sha
+ end
end
diff --git a/app/controllers/projects/pipelines_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb
index 75dd3648e45..9136633b87a 100644
--- a/app/controllers/projects/pipelines_settings_controller.rb
+++ b/app/controllers/projects/pipelines_settings_controller.rb
@@ -3,7 +3,13 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
def show
@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
def update
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index 2f0a9659d15..c7911736812 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -1,6 +1,7 @@
class ProjectsFinder < UnionFinder
- def execute(current_user = nil, options = {})
+ def execute(current_user = nil, project_ids_relation = nil)
segments = all_projects(current_user)
+ segments.map! { |s| s.where(id: project_ids_relation) } if project_ids_relation
find_union(segments, Project)
end
diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb
index ff866c2faa5..4fe0070552e 100644
--- a/app/finders/todos_finder.rb
+++ b/app/finders/todos_finder.rb
@@ -27,9 +27,11 @@ class TodosFinder
items = by_action_id(items)
items = by_action(items)
items = by_author(items)
- items = by_project(items)
items = by_state(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)
end
@@ -91,14 +93,9 @@ class TodosFinder
@project
end
- def projects
- return @projects if defined?(@projects)
-
- if project?
- @projects = project
- else
- @projects = ProjectsFinder.new.execute(current_user)
- end
+ def projects(items)
+ item_project_ids = items.reorder(nil).select(:project_id)
+ ProjectsFinder.new.execute(current_user, item_project_ids)
end
def type?
@@ -136,8 +133,9 @@ class TodosFinder
def by_project(items)
if project?
items = items.where(project: project)
- elsif projects
- items = items.merge(projects).joins(:project)
+ else
+ item_projects = projects(items)
+ items = items.merge(item_projects).joins(:project)
end
items
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index e1c0b497550..8b138a8e69f 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -20,13 +20,19 @@ module SortingHelper
end
def projects_sort_options_hash
- {
+ options = {
sort_value_name => sort_title_name,
sort_value_recently_updated => sort_title_recently_updated,
sort_value_oldest_updated => sort_title_oldest_updated,
sort_value_recently_created => sort_title_recently_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
def sort_title_priority
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index e3a208f826a..0465327060e 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -1,10 +1,10 @@
module TodosHelper
def todos_pending_count
- @todos_pending_count ||= TodosFinder.new(current_user, state: :pending).execute.count
+ @todos_pending_count ||= current_user.todos_pending_count
end
def todos_done_count
- @todos_done_count ||= TodosFinder.new(current_user, state: :done).execute.count
+ @todos_done_count ||= current_user.todos_done_count
end
def todo_action_name(todo)
diff --git a/app/models/blob.rb b/app/models/blob.rb
index 0df2805e448..12cc5aaafba 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -3,6 +3,9 @@ class Blob < SimpleDelegator
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
+ # 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
#
# This method prevents the decorated object from evaluating to "truthy" when
@@ -31,6 +34,10 @@ class Blob < SimpleDelegator
text? && language && language.name == 'SVG'
end
+ def size_within_svg_limits?
+ size <= MAXIMUM_SVG_SIZE
+ end
+
def video?
UploaderHelper::VIDEO_EXT.include?(extname.downcase.delete('.'))
end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 1a7cd60817e..1e338889714 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -36,4 +36,10 @@ class Deployment < ActiveRecord::Base
def manual_actions
deployable.try(:other_actions)
end
+
+ def includes_commit?(commit)
+ return false unless commit
+
+ project.repository.is_ancestor?(commit.id, sha)
+ end
end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index baed106e8c8..75e6f869786 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -25,4 +25,10 @@ class Environment < ActiveRecord::Base
def nullify_external_url
self.external_url = nil if self.external_url.blank?
end
+
+ def includes_commit?(commit)
+ return false unless last_deployment
+
+ last_deployment.includes_commit?(commit)
+ end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index c1e95b1bfbf..b67e5d1148a 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -591,6 +591,14 @@ class MergeRequest < ActiveRecord::Base
!pipeline || pipeline.success?
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
if merged?
"Merged"
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index ad19b7795da..5301f9fa0ff 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -1,7 +1,9 @@
class PivotaltrackerService < Service
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?
def title
@@ -18,7 +20,17 @@ class PivotaltrackerService < Service
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
@@ -28,8 +40,8 @@ class PivotaltrackerService < Service
def execute(data)
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|
message = {
'source_commit' => {
@@ -40,7 +52,7 @@ class PivotaltrackerService < Service
}
}
PivotaltrackerService.post(
- url,
+ API_ENDPOINT,
body: message.to_json,
headers: {
'Content-Type' => 'application/json',
@@ -49,4 +61,15 @@ class PivotaltrackerService < Service
)
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
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index a255710f577..46f70da2452 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -56,6 +56,10 @@ class ProjectWiki
end
end
+ def repository_exists?
+ !!repository.exists?
+ end
+
def empty?
pages.empty?
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 73368be7b1b..87a2d999843 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -809,13 +809,13 @@ class User < ActiveRecord::Base
def todos_done_count(force: false)
Rails.cache.fetch(['users', id, 'todos_done_count'], force: force) do
- todos.done.count
+ TodosFinder.new(self, state: :done).execute.count
end
end
def todos_pending_count(force: false)
Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force) do
- todos.pending.count
+ TodosFinder.new(self, state: :pending).execute.count
end
end
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index c4a206f785e..ea94818713b 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -15,6 +15,7 @@ module Files
else
params[:file_content]
end
+ @last_commit_sha = params[:last_commit_sha]
# Validate parameters
validate
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index 8d2b5083179..4fc3b640799 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -2,11 +2,34 @@ require_relative "base_service"
module Files
class UpdateService < Files::BaseService
+ class FileChangedError < StandardError; end
+
def commit
repository.update_file(current_user, @file_path, @file_content,
branch: @target_branch,
previous_path: @previous_path,
message: @commit_message)
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
diff --git a/app/services/merge_requests/get_urls_service.rb b/app/services/merge_requests/get_urls_service.rb
index 501fd135e16..08c1f72d65a 100644
--- a/app/services/merge_requests/get_urls_service.rb
+++ b/app/services/merge_requests/get_urls_service.rb
@@ -30,10 +30,21 @@ module MergeRequests
end
def get_branches(changes)
+ return [] if project.empty_repo?
+ return [] unless project.merge_requests_enabled
+
changes_list = Gitlab::ChangesList.new(changes)
changes_list.map do |change|
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
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 6b48d68cccb..eb833dd82ac 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -144,8 +144,9 @@ class TodoService
def mark_todos_as_done(todos, current_user)
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
+ marked_todos
end
# When user marks an issue as todo
diff --git a/app/views/projects/blob/_image.html.haml b/app/views/projects/blob/_image.html.haml
index 18caddabd39..4c356d1f07f 100644
--- a/app/views/projects/blob/_image.html.haml
+++ b/app/views/projects/blob/_image.html.haml
@@ -1,9 +1,15 @@
.file-content.image_file
- if blob.svg?
- - # We need to scrub SVG but we cannot do so in the RawController: it would
- - # be wrong/strange if RawController modified the data.
- - blob.load_all_data!(@repository)
- - blob = sanitize_svg(blob)
- %img{src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"}
+ - if blob.size_within_svg_limits?
+ - # We need to scrub SVG but we cannot do so in the RawController: it would
+ - # be wrong/strange if RawController modified the data.
+ - blob.load_all_data!(@repository)
+ - 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
%img{src: namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, blob.path))}
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index b1c9895f43e..7b0621f9401 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -1,5 +1,11 @@
- 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
%ul.nav-links.no-bottom.js-edit-mode
%li.active
@@ -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
= render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
= render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
-
- = hidden_field_tag 'last_commit', @last_commit
+ = hidden_field_tag 'last_commit_sha', @last_commit_sha
= hidden_field_tag 'content', '', id: "file-content"
= 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)
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index 6ef640bb654..494695a03a5 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -42,3 +42,16 @@
.ci_widget.ci-error{style: "display:none"}
= icon("times-circle")
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)
diff --git a/app/views/projects/pipelines_settings/_badge.html.haml b/app/views/projects/pipelines_settings/_badge.html.haml
new file mode 100644
index 00000000000..7b7fa56d993
--- /dev/null
+++ b/app/views/projects/pipelines_settings/_badge.html.haml
@@ -0,0 +1,27 @@
+.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)
diff --git a/app/views/projects/pipelines_settings/show.html.haml b/app/views/projects/pipelines_settings/show.html.haml
index 228bad36ebd..8c7222bfe3d 100644
--- a/app/views/projects/pipelines_settings/show.html.haml
+++ b/app/views/projects/pipelines_settings/show.html.haml
@@ -77,27 +77,4 @@
%hr
.row.prepend-top-default
- .col-lg-3.profile-settings-sidebar
- %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)
+ = render partial: 'badge', collection: @badges
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 92803838d02..281ec728e41 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -12,6 +12,8 @@
%li.project-row{ class: css_class }
= cache(cache_key) do
.controls
+ - if project.archived
+ %span.label.label-warning archived
- if project.commit.try(:status)
%span
= render_commit_status(project.commit)
diff --git a/config/routes.rb b/config/routes.rb
index 93da084b64a..46a05d9a1a9 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -871,7 +871,10 @@ Rails.application.routes.draw do
resources :badges, only: [:index] do
collection 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
diff --git a/db/fixtures/development/14_builds.rb b/db/fixtures/development/14_builds.rb
index e65abe4ef77..6441a036e75 100644
--- a/db/fixtures/development/14_builds.rb
+++ b/db/fixtures/development/14_builds.rb
@@ -1,5 +1,21 @@
class Gitlab::Seeder::Builds
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)
@project = project
@@ -8,25 +24,7 @@ class Gitlab::Seeder::Builds
def seed!
pipelines.each do |pipeline|
begin
- build_create!(pipeline, name: 'build:linux', stage: 'build', status_event: :success)
- 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)
-
+ BUILDS.each { |opts| build_create!(pipeline, opts) }
commit_status_create!(pipeline, name: 'jenkins', status: :success)
print '.'
@@ -48,21 +46,22 @@ class Gitlab::Seeder::Builds
def build_create!(pipeline, opts = {})
attributes = build_attributes_for(pipeline, opts)
- build = Ci::Build.create!(attributes)
- if opts[:name].start_with?('build')
- artifacts_cache_file(artifacts_archive_path) do |file|
- build.artifacts_file = file
- end
+ Ci::Build.create!(attributes) do |build|
+ if opts[:name].start_with?('build')
+ artifacts_cache_file(artifacts_archive_path) do |file|
+ build.artifacts_file = file
+ end
- artifacts_cache_file(artifacts_metadata_path) do |file|
- build.artifacts_metadata = file
+ artifacts_cache_file(artifacts_metadata_path) do |file|
+ build.artifacts_metadata = file
+ end
end
- end
- if %w(running success failed).include?(build.status)
- # We need to set build trace after saving a build (id required)
- build.trace = FFaker::Lorem.paragraphs(6).join("\n\n")
+ if %w(running success failed).include?(build.status)
+ # We need to set build trace after saving a build (id required)
+ build.trace = FFaker::Lorem.paragraphs(6).join("\n\n")
+ end
end
end
diff --git a/db/schema.rb b/db/schema.rb
index 1de2cdcf026..f008a6bd7a7 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -589,12 +589,12 @@ ActiveRecord::Schema.define(version: 20160810142633) do
t.datetime "locked_at"
t.integer "updated_by_id"
t.string "merge_error"
- t.text "merge_params"
t.boolean "merge_when_build_succeeds", default: false, null: false
t.integer "merge_user_id"
t.string "merge_commit_sha"
t.datetime "deleted_at"
t.string "in_progress_merge_commit_sha"
+ t.text "merge_params"
end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
diff --git a/doc/api/services.md b/doc/api/services.md
index f821a614047..579fdc0c8c9 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -355,7 +355,7 @@ PUT /projects/:id/services/gemnasium
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
### Delete Gemnasium service
@@ -503,6 +503,7 @@ PUT /projects/:id/services/pivotaltracker
Parameters:
- `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
@@ -661,4 +662,3 @@ Get JetBrains TeamCity CI service settings for a project.
```
GET /projects/:id/services/teamcity
```
-
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
index 48a9f994759..d90d7aca4fd 100644
--- a/doc/ci/pipelines.md
+++ b/doc/ci/pipelines.md
@@ -32,6 +32,41 @@ project.
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
[jobs]: yaml/README.md#jobs
[stages]: yaml/README.md#stages
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index 6a3c416d995..c835ebc2d44 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -218,21 +218,13 @@ project's settings.
For more information read the
[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
Visit the [examples README][examples] to see a list of examples using GitLab
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
[blog-ci]: https://about.gitlab.com/2015/05/06/why-were-replacing-gitlab-ci-jobs-with-gitlab-ci-dot-yml/
[examples]: ../examples/README.md
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index 80ed4c6d64c..a7d61bc28e0 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -26,6 +26,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
end
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(find("#merge_request_target_project_id").value).to eq @project.id.to_s
expect(find("input#merge_request_source_branch").value).to eq "fix"
diff --git a/features/steps/dashboard/event_filters.rb b/features/steps/dashboard/event_filters.rb
index 726b37cfde5..ca3cd0ecc4e 100644
--- a/features/steps/dashboard/event_filters.rb
+++ b/features/steps/dashboard/event_filters.rb
@@ -1,4 +1,5 @@
class Spinach::Features::EventFilters < Spinach::FeatureSteps
+ include WaitForAjax
include SharedAuthentication
include SharedPaths
include SharedProject
@@ -72,14 +73,20 @@ class Spinach::Features::EventFilters < Spinach::FeatureSteps
end
When 'I click "push" event filter' do
- click_link("push_event_filter")
+ wait_for_ajax
+ click_link("Push events")
+ wait_for_ajax
end
When 'I click "team" event filter' do
- click_link("team_event_filter")
+ wait_for_ajax
+ click_link("Team")
+ wait_for_ajax
end
When 'I click "merge" event filter' do
- click_link("merged_event_filter")
+ wait_for_ajax
+ click_link("Merge events")
+ wait_for_ajax
end
end
diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb
index 8706f0e8e78..39c65bb6cde 100644
--- a/features/steps/dashboard/issues.rb
+++ b/features/steps/dashboard/issues.rb
@@ -43,9 +43,14 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
step 'I click "All" link' do
find(".js-author-search").click
+ expect(page).to have_selector(".dropdown-menu-author li a")
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
+ expect(page).to have_selector(".dropdown-menu-assignee li a")
find(".dropdown-menu-assignee li a", match: :first).click
+ expect(page).not_to have_selector(".dropdown-menu-assignee li a")
end
def should_see(issue)
diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb
index 06db36c7014..6777101fb15 100644
--- a/features/steps/dashboard/merge_requests.rb
+++ b/features/steps/dashboard/merge_requests.rb
@@ -47,9 +47,14 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
step 'I click "All" link' do
find(".js-author-search").click
+ expect(page).to have_selector(".dropdown-menu-author li a")
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
+ expect(page).to have_selector(".dropdown-menu-assignee li a")
find(".dropdown-menu-assignee li a", match: :first).click
+ expect(page).not_to have_selector(".dropdown-menu-assignee li a")
end
def should_see(merge_request)
diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb
index 727a6a71373..dcfa88f69fc 100644
--- a/features/steps/dashboard/new_project.rb
+++ b/features/steps/dashboard/new_project.rb
@@ -29,6 +29,7 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
end
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
end
@@ -47,6 +48,7 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
end
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
end
end
diff --git a/features/steps/project/builds/artifacts.rb b/features/steps/project/builds/artifacts.rb
index b4a32ed2e38..055fca036d3 100644
--- a/features/steps/project/builds/artifacts.rb
+++ b/features/steps/project/builds/artifacts.rb
@@ -10,6 +10,7 @@ class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps
step 'I click artifacts browse button' do
click_link 'Browse'
+ expect(page).not_to have_selector('.build-sidebar')
end
step 'I should see content of artifacts archive' do
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index 6b56a77b832..dacab6c7977 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -34,6 +34,9 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
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('.dropdown-source-project a', text: @forked_project.path_with_namespace)
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index 35f166c7c08..daee90b3767 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -354,6 +354,8 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
def filter_issue(text)
+ sleep 1
fill_in 'issue_search', with: text
+ sleep 1
end
end
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index a02a54923a5..53d1aedf27f 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -489,10 +489,12 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I fill in merge request search with "Fe"' do
+ sleep 1
fill_in 'issue_search', with: "Fe"
end
step 'I click the "Target branch" dropdown' do
+ expect(page).to have_content('Target branch')
first('.target_branch').click
end
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index 9a8896acb15..841d191d55b 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -69,6 +69,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I edit code' do
+ expect(page).to have_selector('.file-editor')
set_new_content
end
@@ -131,6 +132,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I click on "New file" link in repo' do
find('.add-to-tree').click
click_link 'New file'
+ expect(page).to have_selector('.file-editor')
end
step 'I click on "Upload file" link in repo' do
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index 732dc5d0b93..07a955b1a14 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -142,7 +142,9 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I edit the Wiki page with a path' do
+ expect(page).to have_content('three')
click_on 'three'
+ expect(find('.nav-text')).to have_content('Three')
click_on 'Edit'
end
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index b5fd24d246f..aa666a954bc 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -133,9 +133,7 @@ module SharedIssuable
end
step 'The list should be sorted by "Oldest updated"' do
- page.within('.content div.dropdown.inline.prepend-left-10') do
- expect(page.find('button.dropdown-toggle.btn')).to have_content('Oldest updated')
- end
+ expect(find('.issues-filters')).to have_content('Oldest updated')
end
step 'I click link "Next" in the sidebar' do
diff --git a/features/support/wait_for_ajax.rb b/features/support/wait_for_ajax.rb
new file mode 100644
index 00000000000..b90fc112671
--- /dev/null
+++ b/features/support/wait_for_ajax.rb
@@ -0,0 +1,11 @@
+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
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index 26c24c3baff..19df13d8aac 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -61,9 +61,9 @@ module API
#
delete ':id' do
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
# Mark all todos as done
@@ -73,9 +73,7 @@ module API
#
delete do
todos = find_todos
- todos.each(&:done)
-
- todos.length
+ TodoService.new.mark_todos_as_done(todos, current_user)
end
end
end
diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index ca80aac5a08..6e13282d5f4 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -7,7 +7,7 @@ module Banzai
UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
def whitelist
- whitelist = super
+ whitelist = super.dup
customize_whitelist(whitelist)
@@ -42,6 +42,8 @@ module Banzai
# Allow any protocol in `a` elements...
whitelist[:protocols].delete('a')
+ whitelist[:transformers] = whitelist[:transformers].dup
+
# ...but then remove links with unsafe protocols
whitelist[:transformers].push(remove_unsafe_links)
diff --git a/lib/gitlab/badge/base.rb b/lib/gitlab/badge/base.rb
new file mode 100644
index 00000000000..909fa24fa90
--- /dev/null
+++ b/lib/gitlab/badge/base.rb
@@ -0,0 +1,21 @@
+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
diff --git a/lib/gitlab/badge/build.rb b/lib/gitlab/badge/build.rb
deleted file mode 100644
index 1de721a2269..00000000000
--- a/lib/gitlab/badge/build.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-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
diff --git a/lib/gitlab/badge/build/metadata.rb b/lib/gitlab/badge/build/metadata.rb
index 553ef8d7b16..f87a7b7942e 100644
--- a/lib/gitlab/badge/build/metadata.rb
+++ b/lib/gitlab/badge/build/metadata.rb
@@ -1,25 +1,17 @@
module Gitlab
module Badge
- class Build
+ module Build
##
# Class that describes build badge metadata
#
- class Metadata
- include Gitlab::Application.routes.url_helpers
- include ActionView::Helpers::AssetTagHelper
- include ActionView::Helpers::UrlHelper
-
- def initialize(project, ref)
- @project = project
- @ref = ref
- end
-
- def to_html
- link_to(image_tag(image_url, alt: 'build status'), link_url)
+ class Metadata < Badge::Metadata
+ def initialize(badge)
+ @project = badge.project
+ @ref = badge.ref
end
- def to_markdown
- "[![build status](#{image_url})](#{link_url})"
+ def title
+ 'build status'
end
def image_url
diff --git a/lib/gitlab/badge/build/status.rb b/lib/gitlab/badge/build/status.rb
new file mode 100644
index 00000000000..50aa45e5406
--- /dev/null
+++ b/lib/gitlab/badge/build/status.rb
@@ -0,0 +1,37 @@
+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
diff --git a/lib/gitlab/badge/build/template.rb b/lib/gitlab/badge/build/template.rb
index deba3b669b3..2b95ddfcb53 100644
--- a/lib/gitlab/badge/build/template.rb
+++ b/lib/gitlab/badge/build/template.rb
@@ -1,12 +1,12 @@
module Gitlab
module Badge
- class Build
+ module Build
##
# Class that represents a build badge template.
#
# Template object will be passed to badge.svg.erb template.
#
- class Template
+ class Template < Badge::Template
STATUS_COLOR = {
success: '#4c1',
failed: '#e05d44',
@@ -17,16 +17,17 @@ module Gitlab
unknown: '#9f9f9f'
}
- def initialize(status)
- @status = status
+ def initialize(badge)
+ @entity = badge.entity
+ @status = badge.status
end
def key_text
- 'build'
+ @entity.to_s
end
def value_text
- @status
+ @status.to_s
end
def key_width
@@ -37,25 +38,8 @@ module Gitlab
54
end
- def key_color
- '#555'
- end
-
def value_color
- STATUS_COLOR[@status.to_sym] ||
- 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
+ STATUS_COLOR[@status.to_sym] || STATUS_COLOR[:unknown]
end
end
end
diff --git a/lib/gitlab/badge/coverage/metadata.rb b/lib/gitlab/badge/coverage/metadata.rb
new file mode 100644
index 00000000000..53588185622
--- /dev/null
+++ b/lib/gitlab/badge/coverage/metadata.rb
@@ -0,0 +1,30 @@
+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
diff --git a/lib/gitlab/badge/coverage/report.rb b/lib/gitlab/badge/coverage/report.rb
new file mode 100644
index 00000000000..3d56ea3e47a
--- /dev/null
+++ b/lib/gitlab/badge/coverage/report.rb
@@ -0,0 +1,56 @@
+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
diff --git a/lib/gitlab/badge/coverage/template.rb b/lib/gitlab/badge/coverage/template.rb
new file mode 100644
index 00000000000..06e0d084e9f
--- /dev/null
+++ b/lib/gitlab/badge/coverage/template.rb
@@ -0,0 +1,52 @@
+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
diff --git a/lib/gitlab/badge/metadata.rb b/lib/gitlab/badge/metadata.rb
new file mode 100644
index 00000000000..548f85b78bb
--- /dev/null
+++ b/lib/gitlab/badge/metadata.rb
@@ -0,0 +1,36 @@
+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
diff --git a/lib/gitlab/badge/template.rb b/lib/gitlab/badge/template.rb
new file mode 100644
index 00000000000..bfeb0052642
--- /dev/null
+++ b/lib/gitlab/badge/template.rb
@@ -0,0 +1,49 @@
+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
diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb
index 52f117e963b..4b32eb966aa 100644
--- a/lib/gitlab/checks/change_access.rb
+++ b/lib/gitlab/checks/change_access.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def exec
- error = protected_branch_checks || tag_checks || push_checks
+ error = push_checks || tag_checks || protected_branch_checks
if error
GitAccessStatus.new(false, error)
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index 7e71a030901..76b2178c79c 100755
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -20,10 +20,11 @@ if [ -f /.dockerenv ] || [ -f ./dockerinit ]; then
# Install phantomjs package
pushd vendor/apt
- if [ ! -e phantomjs_1.9.8-0jessie_amd64.deb ]; then
- wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb
+ PHANTOMJS_FILE="phantomjs-$PHANTOMJS_VERSION-linux-x86_64"
+ if [ ! -d "$PHANTOMJS_FILE" ]; then
+ curl -q -L "https://s3.amazonaws.com/gitlab-build-helpers/$PHANTOMJS_FILE.tar.bz2" | tar jx
fi
- dpkg -i phantomjs_1.9.8-0jessie_amd64.deb
+ cp "$PHANTOMJS_FILE/bin/phantomjs" "/usr/bin/"
popd
# Try to install packages
diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb
index 0239aea47fb..602de72d23f 100644
--- a/spec/controllers/admin/groups_controller_spec.rb
+++ b/spec/controllers/admin/groups_controller_spec.rb
@@ -7,12 +7,13 @@ describe Admin::GroupsController do
before do
sign_in(admin)
- Sidekiq::Testing.fake!
end
describe 'DELETE #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
it 'redirects to the admin group path' do
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index 4ae6364207b..a763e2c5ba8 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -89,12 +89,13 @@ describe GroupsController do
context 'as the group owner' do
before do
- Sidekiq::Testing.fake!
sign_in(user)
end
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
it 'redirects to the root path' do
diff --git a/spec/features/issuables/default_sort_order_spec.rb b/spec/features/issuables/default_sort_order_spec.rb
index 0d495cd04aa..9114f751b55 100644
--- a/spec/features/issuables/default_sort_order_spec.rb
+++ b/spec/features/issuables/default_sort_order_spec.rb
@@ -55,7 +55,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last updated"' do
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(last_merge_request).to include(first_updated_issuable.title)
end
@@ -67,7 +67,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last updated"' do
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(last_merge_request).to include(first_updated_issuable.title)
end
@@ -79,7 +79,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last created"' do
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(last_merge_request).to include(first_created_issuable.title)
end
@@ -108,7 +108,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last created"' do
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(last_issue).to include(first_created_issuable.title)
end
@@ -120,7 +120,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last created"' do
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(last_issue).to include(first_created_issuable.title)
end
@@ -132,7 +132,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last updated"' do
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(last_issue).to include(first_updated_issuable.title)
end
@@ -144,7 +144,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
it 'is "last created"' do
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(last_issue).to include(first_created_issuable.title)
end
diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb
index ea81ee54c90..e262f285868 100644
--- a/spec/features/issues/filter_issues_spec.rb
+++ b/spec/features/issues/filter_issues_spec.rb
@@ -117,7 +117,7 @@ describe 'Filter issues', feature: true do
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
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb
index e296078bad8..11c9de3c4bf 100644
--- a/spec/features/merge_requests/create_new_mr_spec.rb
+++ b/spec/features/merge_requests/create_new_mr_spec.rb
@@ -13,6 +13,8 @@ feature 'Create New Merge Request', feature: true, js: true do
it 'generates a diff for an orphaned branch' do
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('.dropdown-source-branch .dropdown-content a', text: 'orphaned-branch').click
diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb
index 787bf42d048..d14a1158b67 100644
--- a/spec/features/profiles/preferences_spec.rb
+++ b/spec/features/profiles/preferences_spec.rb
@@ -68,10 +68,14 @@ describe 'Profile > Preferences', feature: true do
allowing_for_delay do
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
end
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
end
end
diff --git a/spec/features/projects/badges/coverage_spec.rb b/spec/features/projects/badges/coverage_spec.rb
new file mode 100644
index 00000000000..af86d3c338a
--- /dev/null
+++ b/spec/features/projects/badges/coverage_spec.rb
@@ -0,0 +1,73 @@
+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
diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb
index 75166bca119..67a4a5d1ab1 100644
--- a/spec/features/projects/badges/list_spec.rb
+++ b/spec/features/projects/badges/list_spec.rb
@@ -9,25 +9,43 @@ feature 'list of badges' do
visit namespace_project_pipelines_settings_path(project.namespace, project)
end
- scenario 'user displays list of badges' do
- expect(page).to have_content 'build status'
- 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='build status']")
-
- page.within('.highlight', match: :first) do
- expect(page).to have_content 'badges/master/build.svg'
+ scenario 'user wants to see build status badge' do
+ page.within('.build-status') do
+ expect(page).to have_content 'build status'
+ 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='build status']")
+
+ page.within('.highlight', match: :first) do
+ expect(page).to have_content 'badges/master/build.svg'
+ end
end
end
- scenario 'user changes current ref on badges list page', js: true do
- first('.js-project-refs-dropdown').click
+ scenario 'user wants to see coverage report badge' do
+ 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
- click_link 'improve/awesome'
+ page.within('.highlight', match: :first) do
+ expect(page).to have_content 'badges/master/coverage.svg'
+ 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
diff --git a/spec/features/projects/files/editing_a_file_spec.rb b/spec/features/projects/files/editing_a_file_spec.rb
new file mode 100644
index 00000000000..fe047e00409
--- /dev/null
+++ b/spec/features/projects/files/editing_a_file_spec.rb
@@ -0,0 +1,34 @@
+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
diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
index e1e105e6bbe..dbd07464444 100644
--- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb
+++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
@@ -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
click_link 'Add License'
+ expect(page).to have_content('New File')
expect(current_path).to eq(
namespace_project_new_blob_path(project.namespace, project, 'master'))
expect(find('#file_name').value).to eq('LICENSE')
diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
index 67aac25e427..45bf0c0d038 100644
--- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
+++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
@@ -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)
click_link 'Create empty bare repository'
click_on 'LICENSE'
+ expect(page).to have_content('New File')
expect(current_path).to eq(
namespace_project_new_blob_path(project.namespace, project, 'master'))
diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb
index 9335f5bf120..d370f90f7d9 100644
--- a/spec/features/u2f_spec.rb
+++ b/spec/features/u2f_spec.rb
@@ -1,8 +1,16 @@
require 'spec_helper'
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) }
+ 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)
u2f_device ||= FakeU2fDevice.new(page)
u2f_device.respond_to_u2f_registration
@@ -34,7 +42,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
describe 'when 2FA via OTP is enabled' do
it 'allows registering a new device' do
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")
register_u2f_device
@@ -46,15 +54,15 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
visit profile_account_path
# First device
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
register_u2f_device
expect(page.body).to match('Your U2F device was registered')
# Second device
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
register_u2f_device
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')
end
end
@@ -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
# First user
visit profile_account_path
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
u2f_device = register_u2f_device
expect(page.body).to match('Your U2F device was registered')
logout
@@ -71,7 +79,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
user = login_as(:user)
user.update_attribute(:otp_required_for_login, true)
visit profile_account_path
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
register_u2f_device(u2f_device)
expect(page.body).to match('Your U2F device was registered')
@@ -81,7 +89,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
context "when there are form errors" do
it "doesn't register the device if there are errors" do
visit profile_account_path
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
# Have the "u2f device" respond with bad data
page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };")
@@ -96,7 +104,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
it "allows retrying registration" do
visit profile_account_path
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
# Failed registration
page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };")
@@ -122,7 +130,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
login_as(user)
user.update_attribute(:otp_required_for_login, true)
visit profile_account_path
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
@u2f_device = register_u2f_device
logout
end
@@ -161,7 +169,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
current_user = login_as(:user)
current_user.update_attribute(:otp_required_for_login, true)
visit profile_account_path
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
register_u2f_device
logout
@@ -182,7 +190,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
current_user = login_as(:user)
current_user.update_attribute(:otp_required_for_login, true)
visit profile_account_path
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
register_u2f_device(@u2f_device)
logout
@@ -248,7 +256,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
user = login_as(:user)
user.update_attribute(:otp_required_for_login, true)
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.")
register_u2f_device
end
diff --git a/spec/features/variables_spec.rb b/spec/features/variables_spec.rb
index 61f2bc61e0c..d7880d5778f 100644
--- a/spec/features/variables_spec.rb
+++ b/spec/features/variables_spec.rb
@@ -42,6 +42,7 @@ describe 'Project variables', js: true do
find('.btn-variable-edit').click
end
+ expect(page).to have_content('Update variable')
fill_in('variable_key', with: 'key')
fill_in('variable_value', with: 'key value')
click_button('Save variable')
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index 0a1cc3b3df7..7a3a74335e8 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -23,73 +23,36 @@ describe ProjectsFinder do
let(:finder) { described_class.new }
- describe 'without a group' do
- describe 'without a user' do
- subject { finder.execute }
+ describe 'without a user' do
+ subject { finder.execute }
- 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
+ it { is_expected.to eq([public_project]) }
end
- describe 'with a group' do
- describe 'without a user' do
- subject { finder.execute(nil, group: group) }
+ describe 'with a user' do
+ subject { finder.execute(user) }
- it { is_expected.to eq([public_project]) }
+ describe 'without private projects' do
+ it { is_expected.to eq([public_project, internal_project]) }
end
- describe 'with a user' do
- subject { finder.execute(user, group: group) }
-
- describe 'without shared projects' do
- it { is_expected.to eq([public_project, internal_project]) }
+ describe 'with private projects' do
+ before do
+ private_project.team.add_user(user, Gitlab::Access::MASTER)
end
- describe 'with shared projects and group membership' do
- before do
- 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
+ it do
+ is_expected.to eq([public_project, internal_project, private_project])
end
+ end
+ end
- describe 'with shared projects and project membership' do
- before do
- shared_project.team.add_user(user, Gitlab::Access::DEVELOPER)
+ describe 'with project_ids_relation' do
+ let(:project_ids_relation) { Project.where(id: internal_project.id) }
- shared_project.project_group_links.
- create(group_access: Gitlab::Access::MASTER, group: group)
- end
+ subject { finder.execute(user, project_ids_relation) }
- it do
- is_expected.to eq([shared_project, public_project, internal_project])
- end
- end
- end
+ it { is_expected.to eq([internal_project]) }
end
end
end
diff --git a/spec/lib/gitlab/badge/build/metadata_spec.rb b/spec/lib/gitlab/badge/build/metadata_spec.rb
index ad5388215c2..d678e522721 100644
--- a/spec/lib/gitlab/badge/build/metadata_spec.rb
+++ b/spec/lib/gitlab/badge/build/metadata_spec.rb
@@ -1,37 +1,27 @@
require 'spec_helper'
+require 'lib/gitlab/badge/shared/metadata'
describe Gitlab::Badge::Build::Metadata do
- let(:project) { create(:project) }
- let(:branch) { 'master' }
- let(:badge) { described_class.new(project, branch) }
+ let(:badge) { double(project: create(:project), ref: 'feature') }
+ let(:metadata) { described_class.new(badge) }
- describe '#to_html' do
- let(:html) { Nokogiri::HTML.parse(badge.to_html) }
- let(:a_href) { html.at('a') }
+ it_behaves_like 'badge metadata'
- it 'points to link' do
- expect(a_href[:href]).to eq badge.link_url
- end
-
- it 'contains clickable image' do
- expect(a_href.children.first.name).to eq 'img'
+ describe '#title' do
+ it 'returns build status title' do
+ expect(metadata.title).to eq 'build status'
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
- subject { badge.image_url }
- it { is_expected.to include "badges/#{branch}/build.svg" }
+ it 'returns valid url' do
+ expect(metadata.image_url).to include 'badges/feature/build.svg'
+ end
end
describe '#link_url' do
- subject { badge.link_url }
- it { is_expected.to include "commits/#{branch}" }
+ it 'returns valid link' do
+ expect(metadata.link_url).to include 'commits/feature'
+ end
end
end
diff --git a/spec/lib/gitlab/badge/build_spec.rb b/spec/lib/gitlab/badge/build/status_spec.rb
index bb8144d5122..38eebb2a176 100644
--- a/spec/lib/gitlab/badge/build_spec.rb
+++ b/spec/lib/gitlab/badge/build/status_spec.rb
@@ -1,11 +1,23 @@
require 'spec_helper'
-describe Gitlab::Badge::Build do
+describe Gitlab::Badge::Build::Status do
let(:project) { create(:project) }
let(:sha) { project.commit.sha }
let(:branch) { 'master' }
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
it 'returns badge metadata' do
expect(badge.metadata.image_url)
@@ -13,12 +25,6 @@ describe Gitlab::Badge::Build do
end
end
- describe '#key_text' do
- it 'always says build' do
- expect(badge.key_text).to eq 'build'
- end
- end
-
context 'build exists' do
let!(:build) { create_build(project, sha, branch) }
@@ -30,12 +36,6 @@ describe Gitlab::Badge::Build do
expect(badge.status).to eq 'success'
end
end
-
- describe '#value_text' do
- it 'returns correct value text' do
- expect(badge.value_text).to eq 'success'
- end
- end
end
context 'build failed' do
@@ -46,12 +46,6 @@ describe Gitlab::Badge::Build do
expect(badge.status).to eq 'failed'
end
end
-
- describe '#value_text' do
- it 'has correct value text' do
- expect(badge.value_text).to eq 'failed'
- end
- end
end
context 'when outdated pipeline for given ref exists' do
@@ -87,12 +81,6 @@ describe Gitlab::Badge::Build do
expect(badge.status).to eq 'unknown'
end
end
-
- describe '#value_text' do
- it 'has correct value text' do
- expect(badge.value_text).to eq 'unknown'
- end
- end
end
def create_build(project, sha, branch)
diff --git a/spec/lib/gitlab/badge/build/template_spec.rb b/spec/lib/gitlab/badge/build/template_spec.rb
index 86dead3c54e..a7e21fb8bb1 100644
--- a/spec/lib/gitlab/badge/build/template_spec.rb
+++ b/spec/lib/gitlab/badge/build/template_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
describe Gitlab::Badge::Build::Template do
- let(:status) { 'success' }
- let(:template) { described_class.new(status) }
+ let(:badge) { double(entity: 'build', status: 'success') }
+ let(:template) { described_class.new(badge) }
describe '#key_text' do
it 'is always says build' do
@@ -34,15 +34,15 @@ describe Gitlab::Badge::Build::Template do
describe '#value_color' do
context 'when status is success' do
- let(:status) { 'success' }
-
it 'has expected color' do
expect(template.value_color).to eq '#4c1'
end
end
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
expect(template.value_color).to eq '#e05d44'
@@ -50,7 +50,9 @@ describe Gitlab::Badge::Build::Template do
end
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
expect(template.value_color).to eq '#dfb317'
@@ -58,7 +60,9 @@ describe Gitlab::Badge::Build::Template do
end
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
expect(template.value_color).to eq '#9f9f9f'
@@ -66,7 +70,9 @@ describe Gitlab::Badge::Build::Template do
end
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
expect(template.value_color).to eq '#9f9f9f'
diff --git a/spec/lib/gitlab/badge/coverage/metadata_spec.rb b/spec/lib/gitlab/badge/coverage/metadata_spec.rb
new file mode 100644
index 00000000000..74eaf7eaf8b
--- /dev/null
+++ b/spec/lib/gitlab/badge/coverage/metadata_spec.rb
@@ -0,0 +1,30 @@
+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
diff --git a/spec/lib/gitlab/badge/coverage/report_spec.rb b/spec/lib/gitlab/badge/coverage/report_spec.rb
new file mode 100644
index 00000000000..1ff49602486
--- /dev/null
+++ b/spec/lib/gitlab/badge/coverage/report_spec.rb
@@ -0,0 +1,93 @@
+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
diff --git a/spec/lib/gitlab/badge/coverage/template_spec.rb b/spec/lib/gitlab/badge/coverage/template_spec.rb
new file mode 100644
index 00000000000..383bae6e087
--- /dev/null
+++ b/spec/lib/gitlab/badge/coverage/template_spec.rb
@@ -0,0 +1,130 @@
+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
diff --git a/spec/lib/gitlab/badge/shared/metadata.rb b/spec/lib/gitlab/badge/shared/metadata.rb
new file mode 100644
index 00000000000..0cf18514251
--- /dev/null
+++ b/spec/lib/gitlab/badge/shared/metadata.rb
@@ -0,0 +1,21 @@
+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
diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb
new file mode 100644
index 00000000000..39069b49978
--- /dev/null
+++ b/spec/lib/gitlab/checks/change_access_spec.rb
@@ -0,0 +1,99 @@
+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
diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb
index 1e5d6a34f83..cee20234e1f 100644
--- a/spec/models/blob_spec.rb
+++ b/spec/models/blob_spec.rb
@@ -94,4 +94,26 @@ describe Blob do
expect(blob.to_partial_path).to eq 'download'
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
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index 7df3df4bb9e..bfff639ad78 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -15,4 +15,28 @@ describe Deployment, models: true do
it { is_expected.to validate_presence_of(:ref) }
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
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 8a84ac0a7c7..c881897926e 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -30,4 +30,37 @@ describe Environment, models: true do
expect(env.external_url).to be_nil
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
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index a9d8081656d..655e0212358 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -674,6 +674,21 @@ describe MergeRequest, models: true do
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
let(:note) { create(:diff_note_on_merge_request, project: subject.project, noteable: subject) }
diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb
index b528baaf15c..ea718232255 100644
--- a/spec/models/project_services/irker_service_spec.rb
+++ b/spec/models/project_services/irker_service_spec.rb
@@ -52,19 +52,20 @@ describe IrkerService, models: true do
let(:colorize_messages) { '1' }
before do
+ @irker_server = TCPServer.new 'localhost', 0
+
allow(irker).to receive_messages(
active: true,
project: project,
project_id: project.id,
service_hook: true,
- server_host: 'localhost',
- server_port: 6659,
+ server_host: @irker_server.addr[2],
+ server_port: @irker_server.addr[1],
default_irc_uri: 'irc://chat.freenode.net/',
recipients: recipients,
colorize_messages: colorize_messages)
irker.valid?
- @irker_server = TCPServer.new 'localhost', 6659
end
after do
diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb
index f37edd4d970..d098d988521 100644
--- a/spec/models/project_services/pivotaltracker_service_spec.rb
+++ b/spec/models/project_services/pivotaltracker_service_spec.rb
@@ -39,4 +39,75 @@ describe PivotaltrackerService, models: true do
it { is_expected.not_to validate_presence_of(:token) }
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
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index 3ccd0af652f..887a2ba5b84 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -117,6 +117,12 @@ describe API::Todos, api: true do
expect(response.status).to eq(200)
expect(pending_1.reload).to be_done
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
@@ -139,6 +145,12 @@ describe API::Todos, api: true do
expect(pending_2.reload).to be_done
expect(pending_3.reload).to be_done
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
diff --git a/spec/services/files/update_service_spec.rb b/spec/services/files/update_service_spec.rb
new file mode 100644
index 00000000000..d019e50649f
--- /dev/null
+++ b/spec/services/files/update_service_spec.rb
@@ -0,0 +1,84 @@
+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
diff --git a/spec/services/merge_requests/get_urls_service_spec.rb b/spec/services/merge_requests/get_urls_service_spec.rb
index ec26770c3eb..8a4b76367e3 100644
--- a/spec/services/merge_requests/get_urls_service_spec.rb
+++ b/spec/services/merge_requests/get_urls_service_spec.rb
@@ -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(: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(:deleted_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 #{Gitlab::Git::BLANK_SHA} 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
shared_examples 'new_merge_request_link' do
@@ -32,6 +34,28 @@ describe MergeRequests::GetUrlsService do
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
let(:changes) { new_branch_changes }
it_behaves_like 'new_merge_request_link'
@@ -42,6 +66,11 @@ describe MergeRequests::GetUrlsService do
it_behaves_like 'new_merge_request_link'
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
let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch) }
let(:changes) { existing_branch_changes }
@@ -61,6 +90,11 @@ describe MergeRequests::GetUrlsService do
let(:changes) { existing_branch_changes }
# Source project is now the forked one
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'
end
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index 34d8ea9090e..6c3cbeae13c 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -472,6 +472,42 @@ describe TodoService, services: true do
expect(john_doe.todos_pending_count).to eq(1)
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 = {})
attributes.reverse_merge!(
project: project,
diff --git a/spec/views/projects/merge_requests/_heading.html.haml_spec.rb b/spec/views/projects/merge_requests/_heading.html.haml_spec.rb
new file mode 100644
index 00000000000..733b2dfa7ff
--- /dev/null
+++ b/spec/views/projects/merge_requests/_heading.html.haml_spec.rb
@@ -0,0 +1,26 @@
+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