Commit 0a51c954 authored by Kamil Trzcinski's avatar Kamil Trzcinski

Merge remote-tracking branch 'origin/master' into rename-ci-commit

parents 3577b57f 3f4ac2ff
...@@ -7,6 +7,7 @@ v 8.9.0 (unreleased) ...@@ -7,6 +7,7 @@ v 8.9.0 (unreleased)
- Fix issue todo not remove when leave project !4150 (Long Nguyen) - Fix issue todo not remove when leave project !4150 (Long Nguyen)
- Allow forking projects with restricted visibility level - Allow forking projects with restricted visibility level
- Improve note validation to prevent errors when creating invalid note via API - Improve note validation to prevent errors when creating invalid note via API
- Reduce number of fog gem dependencies
- Remove project notification settings associated with deleted projects - Remove project notification settings associated with deleted projects
- Fix 404 page when viewing TODOs that contain milestones or labels in different projects - Fix 404 page when viewing TODOs that contain milestones or labels in different projects
- Redesign navigation for project pages - Redesign navigation for project pages
...@@ -27,15 +28,30 @@ v 8.9.0 (unreleased) ...@@ -27,15 +28,30 @@ v 8.9.0 (unreleased)
- Measure queue duration between gitlab-workhorse and Rails - Measure queue duration between gitlab-workhorse and Rails
- Make authentication service for Container Registry to be compatible with < Docker 1.11 - Make authentication service for Container Registry to be compatible with < Docker 1.11
- Add Application Setting to configure Container Registry token expire delay (default 5min) - Add Application Setting to configure Container Registry token expire delay (default 5min)
- Cache assigned issue and merge request counts in sidebar nav
- Cache project build count in sidebar nav
- Reduce number of queries needed to render issue labels in the sidebar
- Improve error handling importing projects
- Put project Files and Commits tabs under Code tab
v 8.8.3 v 8.8.3
- Fix incorrect links on pipeline page when merge request created from fork - Fix 404 page when viewing TODOs that contain milestones or labels in different projects. !4312
- Fix gitlab importer failing to import new projects due to missing credentials - Fixed JS error when trying to remove discussion form. !4303
- Fix serious performance bug with rendering Markdown with InlineDiffFilter - Fixed issue with button color when no CI enabled. !4287
- Fix import URL migration not rescuing with the correct Error - Fixed potential issue with 2 CI status polling events happening. !3869
- In search results, only show notes on confidential issues that the user has access to - Improve design of Pipeline view. !4230
- Fix health check access token changing due to old application settings being used - Fix gitlab importer failing to import new projects due to missing credentials. !4301
- Fix wiki project clone address error (chujinjin) - Fix import URL migration not rescuing with the correct Error. !4321
- Fix health check access token changing due to old application settings being used. !4332
- Make authentication service for Container Registry to be compatible with Docker versions before 1.11. !4363
- Add Application Setting to configure Container Registry token expire delay (default 5 min). !4364
- Pass the "Remember me" value to the 2FA token form. !4369
- Fix incorrect links on pipeline page when merge request created from fork. !4376
- Use downcased path to container repository as this is expected path by Docker. !4420
- Fix wiki project clone address error (chujinjin). !4429
- Fix serious performance bug with rendering Markdown with InlineDiffFilter. !4392
- Fix missing number on generated ordered list element. !4437
- Prevent disclosure of notes on confidential issues in search results.
v 8.8.2 v 8.8.2
- Added remove due date button. !4209 - Added remove due date button. !4209
......
...@@ -83,8 +83,14 @@ gem "carrierwave", '~> 0.10.0' ...@@ -83,8 +83,14 @@ gem "carrierwave", '~> 0.10.0'
# Drag and Drop UI # Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1' gem 'dropzonejs-rails', '~> 0.7.1'
# for backups
gem 'fog-aws', '~> 0.9'
gem 'fog-core', '~> 1.40'
gem 'fog-local', '~> 0.3'
gem 'fog-google', '~> 0.3'
gem 'fog-openstack', '~> 0.1'
# for aws storage # for aws storage
gem "fog", "~> 1.36.0"
gem "unf", '~> 0.1.4' gem "unf", '~> 0.1.4'
# Authorization # Authorization
......
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
CFPropertyList (2.3.2)
RedCloth (4.2.9) RedCloth (4.2.9)
ace-rails-ap (4.0.2) ace-rails-ap (4.0.2)
actionmailer (4.2.6) actionmailer (4.2.6)
...@@ -183,7 +182,7 @@ GEM ...@@ -183,7 +182,7 @@ GEM
erubis (2.7.0) erubis (2.7.0)
escape_utils (1.1.1) escape_utils (1.1.1)
eventmachine (1.0.8) eventmachine (1.0.8)
excon (0.45.4) excon (0.49.0)
execjs (2.6.0) execjs (2.6.0)
expression_parser (0.9.0) expression_parser (0.9.0)
factory_girl (4.5.0) factory_girl (4.5.0)
...@@ -200,8 +199,6 @@ GEM ...@@ -200,8 +199,6 @@ GEM
multi_json multi_json
ffaker (2.0.0) ffaker (2.0.0)
ffi (1.9.10) ffi (1.9.10)
fission (0.5.0)
CFPropertyList (~> 2.2)
flay (2.6.1) flay (2.6.1)
ruby_parser (~> 3.0) ruby_parser (~> 3.0)
sexp_processor (~> 4.0) sexp_processor (~> 4.0)
...@@ -211,109 +208,28 @@ GEM ...@@ -211,109 +208,28 @@ GEM
flowdock (0.7.1) flowdock (0.7.1)
httparty (~> 0.7) httparty (~> 0.7)
multi_json multi_json
fog (1.36.0) fog-aws (0.9.2)
fog-aliyun (>= 0.1.0)
fog-atmos
fog-aws (>= 0.6.0)
fog-brightbox (~> 0.4)
fog-core (~> 1.32)
fog-dynect (~> 0.0.2)
fog-ecloud (~> 0.1)
fog-google (<= 0.1.0)
fog-json
fog-local
fog-powerdns (>= 0.1.1)
fog-profitbricks
fog-radosgw (>= 0.0.2)
fog-riakcs
fog-sakuracloud (>= 0.0.4)
fog-serverlove
fog-softlayer
fog-storm_on_demand
fog-terremark
fog-vmfusion
fog-voxel
fog-xenserver
fog-xml (~> 0.1.1)
ipaddress (~> 0.5)
nokogiri (~> 1.5, >= 1.5.11)
fog-aliyun (0.1.0)
fog-core (~> 1.27)
fog-json (~> 1.0)
ipaddress (~> 0.8)
xml-simple (~> 1.1)
fog-atmos (0.1.0)
fog-core
fog-xml
fog-aws (0.8.1)
fog-core (~> 1.27) fog-core (~> 1.27)
fog-json (~> 1.0) fog-json (~> 1.0)
fog-xml (~> 0.1) fog-xml (~> 0.1)
ipaddress (~> 0.8) ipaddress (~> 0.8)
fog-brightbox (0.10.1) fog-core (1.40.0)
fog-core (~> 1.22)
fog-json
inflecto (~> 0.0.2)
fog-core (1.35.0)
builder builder
excon (~> 0.45) excon (~> 0.49)
formatador (~> 0.2) formatador (~> 0.2)
fog-dynect (0.0.2) fog-google (0.3.2)
fog-core
fog-json
fog-xml
fog-ecloud (0.3.0)
fog-core
fog-xml
fog-google (0.1.0)
fog-core fog-core
fog-json fog-json
fog-xml fog-xml
fog-json (1.0.2) fog-json (1.0.2)
fog-core (~> 1.0) fog-core (~> 1.0)
multi_json (~> 1.10) multi_json (~> 1.10)
fog-local (0.2.1) fog-local (0.3.0)
fog-core (~> 1.27) fog-core (~> 1.27)
fog-powerdns (0.1.1) fog-openstack (0.1.6)
fog-core (~> 1.27) fog-core (>= 1.39)
fog-json (~> 1.0) fog-json (>= 1.0)
fog-xml (~> 0.1) ipaddress (>= 0.8)
fog-profitbricks (0.0.5)
fog-core
fog-xml
nokogiri
fog-radosgw (0.0.5)
fog-core (>= 1.21.0)
fog-json
fog-xml (>= 0.0.1)
fog-riakcs (0.1.0)
fog-core
fog-json
fog-xml
fog-sakuracloud (1.7.5)
fog-core
fog-json
fog-serverlove (0.1.2)
fog-core
fog-json
fog-softlayer (1.0.3)
fog-core
fog-json
fog-storm_on_demand (0.1.1)
fog-core
fog-json
fog-terremark (0.1.0)
fog-core
fog-xml
fog-vmfusion (0.1.0)
fission
fog-core
fog-voxel (0.1.0)
fog-core
fog-xml
fog-xenserver (0.2.2)
fog-core
fog-xml
fog-xml (0.1.2) fog-xml (0.1.2)
fog-core fog-core
nokogiri (~> 1.5, >= 1.5.11) nokogiri (~> 1.5, >= 1.5.11)
...@@ -422,11 +338,10 @@ GEM ...@@ -422,11 +338,10 @@ GEM
httpclient (2.7.0.1) httpclient (2.7.0.1)
i18n (0.7.0) i18n (0.7.0)
ice_nine (0.11.1) ice_nine (0.11.1)
inflecto (0.0.2)
influxdb (0.2.3) influxdb (0.2.3)
cause cause
json json
ipaddress (0.8.2) ipaddress (0.8.3)
jquery-atwho-rails (1.3.2) jquery-atwho-rails (1.3.2)
jquery-rails (4.1.1) jquery-rails (4.1.1)
rails-dom-testing (>= 1, < 3) rails-dom-testing (>= 1, < 3)
...@@ -873,7 +788,6 @@ GEM ...@@ -873,7 +788,6 @@ GEM
builder builder
expression_parser expression_parser
rinku rinku
xml-simple (1.1.5)
xpath (2.0.0) xpath (2.0.0)
nokogiri (~> 1.3) nokogiri (~> 1.3)
...@@ -927,7 +841,11 @@ DEPENDENCIES ...@@ -927,7 +841,11 @@ DEPENDENCIES
ffaker (~> 2.0.0) ffaker (~> 2.0.0)
flay flay
flog flog
fog (~> 1.36.0) fog-aws (~> 0.9)
fog-core (~> 1.40)
fog-google (~> 0.3)
fog-local (~> 0.3)
fog-openstack (~> 0.1)
font-awesome-rails (~> 4.2) font-awesome-rails (~> 4.2)
foreman foreman
fuubar (~> 2.0.0) fuubar (~> 2.0.0)
......
...@@ -20,8 +20,7 @@ class @SearchAutocomplete ...@@ -20,8 +20,7 @@ class @SearchAutocomplete
@dropdown = @wrap.find('.dropdown') @dropdown = @wrap.find('.dropdown')
@dropdownContent = @dropdown.find('.dropdown-content') @dropdownContent = @dropdown.find('.dropdown-content')
@locationBadgeEl = @getElement('.search-location-badge') @locationBadgeEl = @getElement('.location-badge')
@locationText = @getElement('.location-text')
@scopeInputEl = @getElement('#scope') @scopeInputEl = @getElement('#scope')
@searchInput = @getElement('.search-input') @searchInput = @getElement('.search-input')
@projectInputEl = @getElement('#search_project_id') @projectInputEl = @getElement('#search_project_id')
...@@ -133,7 +132,7 @@ class @SearchAutocomplete ...@@ -133,7 +132,7 @@ class @SearchAutocomplete
scope: @scopeInputEl.val() scope: @scopeInputEl.val()
# Location badge # Location badge
_location: @locationText.text() _location: @locationBadgeEl.text()
} }
bindEvents: -> bindEvents: ->
...@@ -143,12 +142,14 @@ class @SearchAutocomplete ...@@ -143,12 +142,14 @@ class @SearchAutocomplete
@searchInput.on 'click', @onSearchInputClick @searchInput.on 'click', @onSearchInputClick
@searchInput.on 'focus', @onSearchInputFocus @searchInput.on 'focus', @onSearchInputFocus
@clearInput.on 'click', @onClearInputClick @clearInput.on 'click', @onClearInputClick
@locationBadgeEl.on 'click', =>
@searchInput.focus()
onDocumentClick: (e) => onDocumentClick: (e) =>
# If clicking outside the search box # If clicking outside the search box
# And search input is not focused # And search input is not focused
# And we are not clicking inside a suggestion # And we are not clicking inside a suggestion
if not $.contains(@dropdown[0], e.target) and @isFocused and not $(e.target).parents('ul').length if not $.contains(@dropdown[0], e.target) and @isFocused and not $(e.target).closest('.search-form').length
@onSearchInputBlur() @onSearchInputBlur()
enableAutocomplete: -> enableAutocomplete: ->
...@@ -221,10 +222,8 @@ class @SearchAutocomplete ...@@ -221,10 +222,8 @@ class @SearchAutocomplete
category = if item.category? then "#{item.category}: " else '' category = if item.category? then "#{item.category}: " else ''
value = if item.value? then item.value else '' value = if item.value? then item.value else ''
html = "<span class='location-badge'> badgeText = "#{category}#{value}"
<i class='location-text'>#{category}#{value}</i> @locationBadgeEl.text(badgeText).show()
</span>"
@locationBadgeEl.html(html)
@wrap.addClass('has-location-badge') @wrap.addClass('has-location-badge')
restoreOriginalState: -> restoreOriginalState: ->
...@@ -233,9 +232,8 @@ class @SearchAutocomplete ...@@ -233,9 +232,8 @@ class @SearchAutocomplete
for input in inputs for input in inputs
@getElement("##{input}").val(@originalState[input]) @getElement("##{input}").val(@originalState[input])
if @originalState._location is '' if @originalState._location is ''
@locationBadgeEl.empty() @locationBadgeEl.hide()
else else
@addLocationBadge( @addLocationBadge(
value: @originalState._location value: @originalState._location
...@@ -244,7 +242,7 @@ class @SearchAutocomplete ...@@ -244,7 +242,7 @@ class @SearchAutocomplete
@dropdown.removeClass 'open' @dropdown.removeClass 'open'
badgePresent: -> badgePresent: ->
@locationBadgeEl.children().length @locationBadgeEl.length
resetSearchState: -> resetSearchState: ->
inputs = Object.keys @originalState inputs = Object.keys @originalState
...@@ -257,7 +255,7 @@ class @SearchAutocomplete ...@@ -257,7 +255,7 @@ class @SearchAutocomplete
@getElement("##{input}").val('') @getElement("##{input}").val('')
removeLocationBadge: -> removeLocationBadge: ->
@locationBadgeEl.empty() @locationBadgeEl.hide()
# Reset state # Reset state
@resetSearchState() @resetSearchState()
......
...@@ -29,8 +29,6 @@ ...@@ -29,8 +29,6 @@
margin-top: 6px; margin-top: 6px;
p { p {
overflow-x: auto;
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
} }
.search-input { .search-input {
padding-right: 20px;
border: none; border: none;
font-size: 14px; font-size: 14px;
outline: none; outline: none;
...@@ -47,6 +48,7 @@ ...@@ -47,6 +48,7 @@
display: inline-block; display: inline-block;
background-color: $location-badge-bg; background-color: $location-badge-bg;
vertical-align: top; vertical-align: top;
cursor: default;
} }
.search-input-container { .search-input-container {
...@@ -55,7 +57,7 @@ ...@@ -55,7 +57,7 @@
position: relative; position: relative;
} }
.search-location-badge, .search-input-wrap { .search-input-wrap {
// Fallback if flexbox is not supported // Fallback if flexbox is not supported
display: inline-block; display: inline-block;
} }
......
...@@ -50,7 +50,7 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -50,7 +50,7 @@ class Projects::BranchesController < Projects::ApplicationController
redirect_to namespace_project_branches_path(@project.namespace, redirect_to namespace_project_branches_path(@project.namespace,
@project), status: 303 @project), status: 303
end end
format.js { render status: status[:return_code] } format.js { render nothing: true, status: status[:return_code] }
end end
end end
......
...@@ -17,7 +17,9 @@ module TodosHelper ...@@ -17,7 +17,9 @@ module TodosHelper
def todo_target_link(todo) def todo_target_link(todo)
target = todo.target_type.titleize.downcase target = todo.target_type.titleize.downcase
link_to "#{target} #{todo.target_reference}", todo_target_path(todo), { title: todo.target.title } link_to "#{target} #{todo.target_reference}", todo_target_path(todo),
class: 'has-tooltip',
title: todo.target.title
end end
def todo_target_path(todo) def todo_target_path(todo)
......
...@@ -313,6 +313,7 @@ module Ci ...@@ -313,6 +313,7 @@ module Ci
build_data = Gitlab::BuildDataBuilder.build(self) build_data = Gitlab::BuildDataBuilder.build(self)
project.execute_hooks(build_data.dup, :build_hooks) project.execute_hooks(build_data.dup, :build_hooks)
project.execute_services(build_data.dup, :build_hooks) project.execute_services(build_data.dup, :build_hooks)
project.running_or_pending_build_count(force: true)
end end
def artifacts? def artifacts?
......
...@@ -68,6 +68,14 @@ module Issuable ...@@ -68,6 +68,14 @@ module Issuable
strip_attributes :title strip_attributes :title
acts_as_paranoid acts_as_paranoid
after_save :update_assignee_cache_counts, if: :assignee_id_changed?
def update_assignee_cache_counts
# make sure we flush the cache for both the old *and* new assignee
User.find(assignee_id_was).update_cache_counts if assignee_id_was
assignee.update_cache_counts if assignee
end
end end
module ClassMethods module ClassMethods
...@@ -205,6 +213,10 @@ module Issuable ...@@ -205,6 +213,10 @@ module Issuable
hook_data hook_data
end end
def labels_array
labels.to_a
end
def label_names def label_names
labels.order('title ASC').pluck(:title) labels.order('title ASC').pluck(:title)
end end
......
...@@ -1011,4 +1011,22 @@ class Project < ActiveRecord::Base ...@@ -1011,4 +1011,22 @@ class Project < ActiveRecord::Base
update_attribute(:pending_delete, true) update_attribute(:pending_delete, true)
end end
def running_or_pending_build_count(force: false)
Rails.cache.fetch(['projects', id, 'running_or_pending_build_count'], force: force) do
builds.running_or_pending.count(:all)
end
end
def mark_import_as_failed(error_message)
original_errors = errors.dup
sanitized_message = Gitlab::UrlSanitizer.sanitize(error_message)
import_fail
update_column(:import_error, sanitized_message)
rescue ActiveRecord::ActiveRecordError => e
Rails.logger.error("Error setting import status to failed: #{e.message}. Original error: #{sanitized_message}")
ensure
@errors = original_errors
end
end end
...@@ -776,6 +776,23 @@ class User < ActiveRecord::Base ...@@ -776,6 +776,23 @@ class User < ActiveRecord::Base
notification_settings.find_or_initialize_by(source: source) notification_settings.find_or_initialize_by(source: source)
end end
def assigned_open_merge_request_count(force: false)
Rails.cache.fetch(['users', id, 'assigned_open_merge_request_count'], force: force) do
assigned_merge_requests.opened.count
end
end
def assigned_open_issues_count(force: false)
Rails.cache.fetch(['users', id, 'assigned_open_issues_count'], force: force) do
assigned_issues.opened.count
end
end
def update_cache_counts
assigned_open_merge_request_count(force: true)
assigned_open_issues_count(force: true)
end
private private
def projects_union def projects_union
......
...@@ -56,14 +56,14 @@ module Projects ...@@ -56,14 +56,14 @@ module Projects
after_create_actions if @project.persisted? after_create_actions if @project.persisted?
if @project.errors.empty?
@project.add_import_job if @project.import? @project.add_import_job if @project.import?
else
fail(error: @project.errors.full_messages.join(', '))
end
@project @project
rescue => e rescue => e
message = "Unable to save project: #{e.message}" fail(error: e.message)
Rails.logger.error(message)
@project.errors.add(:base, message) if @project
@project
end end
protected protected
...@@ -103,5 +103,19 @@ module Projects ...@@ -103,5 +103,19 @@ module Projects
end end
end end
end end
def fail(error:)
message = "Unable to save project. Error: #{error}"
message << "Project ID: #{@project.id}" if @project && @project.id
Rails.logger.error(message)
if @project && @project.import?
@project.errors.add(:base, message)
@project.mark_import_as_failed(message)
end
@project
end
end end
end end
...@@ -39,7 +39,7 @@ module Projects ...@@ -39,7 +39,7 @@ module Projects
begin begin
gitlab_shell.import_repository(project.path_with_namespace, project.import_url) gitlab_shell.import_repository(project.path_with_namespace, project.import_url)
rescue Gitlab::Shell::Error => e rescue Gitlab::Shell::Error => e
raise Error, e.message raise Error, "Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}"
end end
end end
......
...@@ -6,11 +6,8 @@ ...@@ -6,11 +6,8 @@
.search.search-form{class: "#{'has-location-badge' if label.present?}"} .search.search-form{class: "#{'has-location-badge' if label.present?}"}
= form_tag search_path, method: :get, class: 'navbar-form' do |f| = form_tag search_path, method: :get, class: 'navbar-form' do |f|
.search-input-container .search-input-container
.search-location-badge
- if label.present? - if label.present?
%span.location-badge .location-badge= label
%i.location-text
= label
.search-input-wrap .search-input-wrap
.dropdown{ data: {url: search_autocomplete_path } } .dropdown{ data: {url: search_autocomplete_path } }
= search_field_tag "search", nil, placeholder: 'Search', class: "search-input dropdown-menu-toggle", spellcheck: false, tabindex: "1", autocomplete: 'off', data: { toggle: 'dropdown' } = search_field_tag "search", nil, placeholder: 'Search', class: "search-input dropdown-menu-toggle", spellcheck: false, tabindex: "1", autocomplete: 'off', data: { toggle: 'dropdown' }
......
...@@ -30,13 +30,13 @@ ...@@ -30,13 +30,13 @@
= icon('exclamation-circle fw') = icon('exclamation-circle fw')
%span %span
Issues Issues
%span.count= number_with_delimiter(current_user.assigned_issues.opened.count) %span.count= number_with_delimiter(current_user.assigned_open_issues_count)
= nav_link(path: 'dashboard#merge_requests') do = nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
= icon('tasks fw') = icon('tasks fw')
%span %span
Merge Requests Merge Requests
%span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count) %span.count= number_with_delimiter(current_user.assigned_open_merge_request_count)
= nav_link(controller: :snippets) do = nav_link(controller: :snippets) do
= link_to dashboard_snippets_path, title: 'Snippets' do = link_to dashboard_snippets_path, title: 'Snippets' do
= icon('clipboard fw') = icon('clipboard fw')
......
...@@ -33,18 +33,11 @@ ...@@ -33,18 +33,11 @@
%span %span
Activity Activity
- if project_nav_tab? :files - if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases network)) do
= link_to project_files_path(@project), title: 'Files', class: 'shortcuts-tree' do = link_to project_files_path(@project), title: 'Files', class: 'shortcuts-tree' do
= icon('files-o fw') = icon('code fw')
%span %span
Files Code
- if project_nav_tab? :commits
= nav_link(controller: %w(commit commits compare repositories tags branches releases network)) do
= link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
= icon('history fw')
%span
Commits
- if project_nav_tab? :pipelines - if project_nav_tab? :pipelines
= nav_link(controller: :pipelines) do = nav_link(controller: :pipelines) do
...@@ -129,4 +122,10 @@ ...@@ -129,4 +122,10 @@
= link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
Builds Builds
-# Shortcut to commits page
- if project_nav_tab? :commits
%li.hidden
= link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
Commits
.fade-right .fade-right
$('.js-totalbranch-count').html("#{@repository.branch_count}")
%ul.nav-links %ul.nav-links
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
= link_to project_files_path(@project) do
Files
= nav_link(controller: [:commit, :commits]) do = nav_link(controller: [:commit, :commits]) do
= link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
Commits Commits
%span.badge
= number_with_delimiter(@repository.commit_count)
= nav_link(controller: %w(network)) do = nav_link(controller: %w(network)) do
= link_to namespace_project_network_path(@project.namespace, @project, current_ref) do = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do
...@@ -16,9 +18,7 @@ ...@@ -16,9 +18,7 @@
= nav_link(html_options: {class: branches_tab_class}) do = nav_link(html_options: {class: branches_tab_class}) do
= link_to namespace_project_branches_path(@project.namespace, @project) do = link_to namespace_project_branches_path(@project.namespace, @project) do
Branches Branches
%span.badge.js-totalbranch-count= @repository.branch_count
= nav_link(controller: [:tags, :releases]) do = nav_link(controller: [:tags, :releases]) do
= link_to namespace_project_tags_path(@project.namespace, @project) do = link_to namespace_project_tags_path(@project.namespace, @project) do
Tags Tags
%span.badge.js-totaltags-count= @repository.tag_count
...@@ -11,4 +11,4 @@ ...@@ -11,4 +11,4 @@
= link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
%span %span
Builds Builds
%span.badge.count.builds_counter= number_with_delimiter(@project.builds.running_or_pending.count(:all)) %span.badge.count.builds_counter= number_with_delimiter(@project.running_or_pending_build_count)
$('.js-totaltags-count').html("#{@repository.tags.size}");
- if @repository.tags.empty? - if @repository.tags.empty?
$('.tags').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000) $('.tags').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000)
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
- if current_user - if current_user
= auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits") = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits")
= render 'projects/last_push' = render 'projects/last_push'
= render "projects/commits/head"
.tree-controls .tree-controls
= render 'projects/find_file_link' = render 'projects/find_file_link'
......
...@@ -114,20 +114,20 @@ ...@@ -114,20 +114,20 @@
.sidebar-collapsed-icon .sidebar-collapsed-icon
= icon('tags') = icon('tags')
%span %span
= issuable.labels.count = issuable.labels_array.size
.title.hide-collapsed .title.hide-collapsed
Labels Labels
= icon('spinner spin', class: 'block-loading') = icon('spinner spin', class: 'block-loading')
- if can_edit_issuable - if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right' = link_to 'Edit', '#', class: 'edit-link pull-right'
.value.bold.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels.any?) } .value.bold.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels_array.any?) }
- if issuable.labels.any? - if issuable.labels_array.any?
- issuable.labels.each do |label| - issuable.labels_array.each do |label|
= link_to_label(label, type: issuable.to_ability_name) = link_to_label(label, type: issuable.to_ability_name)
- else - else
.light None .light None
.selectbox.hide-collapsed .selectbox.hide-collapsed
- issuable.labels.each do |label| - issuable.labels_array.each do |label|
= hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil
.dropdown .dropdown
%button.dropdown-menu-toggle.js-label-select.js-multiselect{type: "button", data: {toggle: "dropdown", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} %button.dropdown-menu-toggle.js-label-select.js-multiselect{type: "button", data: {toggle: "dropdown", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}}
......
...@@ -15,8 +15,7 @@ class RepositoryForkWorker ...@@ -15,8 +15,7 @@ class RepositoryForkWorker
result = gitlab_shell.fork_repository(source_path, target_path) result = gitlab_shell.fork_repository(source_path, target_path)
unless result unless result
logger.error("Unable to fork project #{project_id} for repository #{source_path} -> #{target_path}") logger.error("Unable to fork project #{project_id} for repository #{source_path} -> #{target_path}")
project.update(import_error: "The project could not be forked.") project.mark_import_as_failed('The project could not be forked.')
project.import_fail
return return
end end
...@@ -24,8 +23,7 @@ class RepositoryForkWorker ...@@ -24,8 +23,7 @@ class RepositoryForkWorker
unless project.valid_repo? unless project.valid_repo?
logger.error("Project #{project_id} had an invalid repository after fork") logger.error("Project #{project_id} had an invalid repository after fork")
project.update(import_error: "The forked repository is invalid.") project.mark_import_as_failed('The forked repository is invalid.')
project.import_fail
return return
end end
......
...@@ -13,8 +13,7 @@ class RepositoryImportWorker ...@@ -13,8 +13,7 @@ class RepositoryImportWorker
result = Projects::ImportService.new(project, current_user).execute result = Projects::ImportService.new(project, current_user).execute
if result[:status] == :error if result[:status] == :error
project.update(import_error: Gitlab::UrlSanitizer.sanitize(result[:message])) project.mark_import_as_failed(result[:message])
project.import_fail
return return
end end
......
...@@ -34,3 +34,23 @@ We want GitLab to work well on small mobile screens as well. Size limitations ma ...@@ -34,3 +34,23 @@ We want GitLab to work well on small mobile screens as well. Size limitations ma
part of the UI for smaller resolutions in favor of a better user experience. part of the UI for smaller resolutions in favor of a better user experience.
However core functionality like browsing files, creating issues, writing comments, should However core functionality like browsing files, creating issues, writing comments, should
be available on all resolutions. be available on all resolutions.
## Icons
* `trash` icon for button or link that does destructive action like removing
information from database or file system
* `x` icon for closing/hiding UI element. For example close modal window
* `pencil` icon for edit button or link
* `eye` icon for subscribe action
* `rss` for rss/atom feed
* `plus` for link or dropdown that lead to page where you create new object (For example new issue page)
## Buttons
* Button should contain icon or text. Exceptions should be approved by UX designer.
* Use gray button on white background or white button on gray background.
* Use red button for destructive actions (not revertable). For example removing issue.
* Use green or blue button for primary action. Primary button should be only one.
Do not use both green and blue button in one form.
...@@ -10,14 +10,9 @@ Feature: Project Active Tab ...@@ -10,14 +10,9 @@ Feature: Project Active Tab
Then the active main tab should be Home Then the active main tab should be Home
And no other main tabs should be active And no other main tabs should be active
Scenario: On Project Files Scenario: On Project Code
Given I visit my project's files page Given I visit my project's files page
Then the active main tab should be Files Then the active main tab should be Code
And no other main tabs should be active
Scenario: On Project Commits
Given I visit my project's commits page
Then the active main tab should be Commits
And no other main tabs should be active And no other main tabs should be active
Scenario: On Project Issues Scenario: On Project Issues
...@@ -64,40 +59,46 @@ Feature: Project Active Tab ...@@ -64,40 +59,46 @@ Feature: Project Active Tab
And no other sub navs should be active And no other sub navs should be active
And the active main tab should be Settings And the active main tab should be Settings
# Sub Tabs: Commits # Sub Tabs: Code
Scenario: On Project Code/Files
Given I visit my project's files page
Then the active sub tab should be Files
And no other sub tabs should be active
And the active main tab should be Code
Scenario: On Project Commits/Commits Scenario: On Project Code/Commits
Given I visit my project's commits page Given I visit my project's commits page
Then the active sub tab should be Commits Then the active sub tab should be Commits
And no other sub tabs should be active And no other sub tabs should be active
And the active main tab should be Commits And the active main tab should be Code
Scenario: On Project Commits/Network Scenario: On Project Code/Network
Given I visit my project's network page Given I visit my project's network page
Then the active sub tab should be Network Then the active sub tab should be Network
And no other sub tabs should be active And no other sub tabs should be active
And the active main tab should be Commits And the active main tab should be Code
Scenario: On Project Commits/Compare Scenario: On Project Code/Compare
Given I visit my project's commits page Given I visit my project's commits page
And I click the "Compare" tab And I click the "Compare" tab
Then the active sub tab should be Compare Then the active sub tab should be Compare
And no other sub tabs should be active And no other sub tabs should be active
And the active main tab should be Commits And the active main tab should be Code
Scenario: On Project Commits/Branches Scenario: On Project Code/Branches
Given I visit my project's commits page Given I visit my project's commits page
And I click the "Branches" tab And I click the "Branches" tab
Then the active sub tab should be Branches Then the active sub tab should be Branches
And no other sub tabs should be active And no other sub tabs should be active
And the active main tab should be Commits And the active main tab should be Code
Scenario: On Project Commits/Tags Scenario: On Project Code/Tags
Given I visit my project's commits page Given I visit my project's commits page
And I click the "Tags" tab And I click the "Tags" tab
Then the active sub tab should be Tags Then the active sub tab should be Tags
And no other sub tabs should be active And no other sub tabs should be active
And the active main tab should be Commits And the active main tab should be Code
Scenario: On Project Issues/Browse Scenario: On Project Issues/Browse
Given I visit my project's issues page Given I visit my project's issues page
......
...@@ -24,3 +24,4 @@ Feature: Project Builds Summary ...@@ -24,3 +24,4 @@ Feature: Project Builds Summary
Then recent build has been erased Then recent build has been erased
And recent build summary does not have artifacts widget And recent build summary does not have artifacts widget
And recent build summary contains information saying that build has been erased And recent build summary contains information saying that build has been erased
And the build count cache is updated
...@@ -8,19 +8,21 @@ Feature: Project Shortcuts ...@@ -8,19 +8,21 @@ Feature: Project Shortcuts
@javascript @javascript
Scenario: Navigate to files tab Scenario: Navigate to files tab
Given I press "g" and "f" Given I press "g" and "f"
Then the active main tab should be Files Then the active main tab should be Code
Then the active sub tab should be Files
@javascript @javascript
Scenario: Navigate to commits tab Scenario: Navigate to commits tab
Given I visit my project's files page Given I visit my project's files page
Given I press "g" and "c" Given I press "g" and "c"
Then the active main tab should be Commits Then the active main tab should be Code
Then the active sub tab should be Commits
@javascript @javascript
Scenario: Navigate to network tab Scenario: Navigate to network tab
Given I press "g" and "n" Given I press "g" and "n"
Then the active sub tab should be Network Then the active sub tab should be Network
And the active main tab should be Commits And the active main tab should be Code
@javascript @javascript
Scenario: Navigate to graphs tab Scenario: Navigate to graphs tab
......
...@@ -63,10 +63,6 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps ...@@ -63,10 +63,6 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
click_link('Tags') click_link('Tags')
end end
step 'the active sub tab should be Commits' do
ensure_active_sub_tab('Commits')
end
step 'the active sub tab should be Compare' do step 'the active sub tab should be Compare' do
ensure_active_sub_tab('Compare') ensure_active_sub_tab('Compare')
end end
......
...@@ -36,4 +36,8 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps ...@@ -36,4 +36,8 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps
expect(page).to have_content 'Build has been erased' expect(page).to have_content 'Build has been erased'
end end
end end
step 'the build count cache is updated' do
expect(@build.project.running_or_pending_build_count).to eq @build.project.builds.running_or_pending.count(:all)
end
end end
...@@ -13,12 +13,12 @@ class Spinach::Features::ProjectFindFile < Spinach::FeatureSteps ...@@ -13,12 +13,12 @@ class Spinach::Features::ProjectFindFile < Spinach::FeatureSteps
end end
step 'I should see "find file" page' do step 'I should see "find file" page' do
ensure_active_main_tab('Files') ensure_active_main_tab('Code')
expect(page).to have_selector('.file-finder-holder', count: 1) expect(page).to have_selector('.file-finder-holder', count: 1)
end end
step 'I fill in Find by path with "git"' do step 'I fill in Find by path with "git"' do
ensure_active_main_tab('Files') ensure_active_main_tab('Code')
expect(page).to have_selector('.file-finder-holder', count: 1) expect(page).to have_selector('.file-finder-holder', count: 1)
end end
......
...@@ -8,12 +8,8 @@ module SharedProjectTab ...@@ -8,12 +8,8 @@ module SharedProjectTab
ensure_active_main_tab('Project') ensure_active_main_tab('Project')
end end
step 'the active main tab should be Files' do step 'the active main tab should be Code' do
ensure_active_main_tab('Files') ensure_active_main_tab('Code')
end
step 'the active main tab should be Commits' do
ensure_active_main_tab('Commits')
end end
step 'the active main tab should be Graphs' do step 'the active main tab should be Graphs' do
...@@ -51,4 +47,12 @@ module SharedProjectTab ...@@ -51,4 +47,12 @@ module SharedProjectTab
step 'the active sub tab should be Network' do step 'the active sub tab should be Network' do
ensure_active_sub_tab('Network') ensure_active_sub_tab('Network')
end end
step 'the active sub tab should be Files' do
ensure_active_sub_tab('Files')
end
step 'the active sub tab should be Commits' do
ensure_active_sub_tab('Commits')
end
end end
...@@ -122,27 +122,23 @@ describe Projects::BranchesController do ...@@ -122,27 +122,23 @@ describe Projects::BranchesController do
let(:branch) { "feature" } let(:branch) { "feature" }
it { expect(response.status).to eq(200) } it { expect(response.status).to eq(200) }
it { expect(subject).to render_template('destroy') }
end end
context "valid branch name with unencoded slashes" do context "valid branch name with unencoded slashes" do
let(:branch) { "improve/awesome" } let(:branch) { "improve/awesome" }
it { expect(response.status).to eq(200) } it { expect(response.status).to eq(200) }
it { expect(subject).to render_template('destroy') }
end end
context "valid branch name with encoded slashes" do context "valid branch name with encoded slashes" do
let(:branch) { "improve%2Fawesome" } let(:branch) { "improve%2Fawesome" }
it { expect(response.status).to eq(200) } it { expect(response.status).to eq(200) }
it { expect(subject).to render_template('destroy') }
end end
context "invalid branch name, valid ref" do context "invalid branch name, valid ref" do
let(:branch) { "no-branch" } let(:branch) { "no-branch" }
it { expect(response.status).to eq(404) } it { expect(response.status).to eq(404) }
it { expect(subject).to render_template('destroy') }
end end
end end
end end
...@@ -227,6 +227,20 @@ describe Issue, "Issuable" do ...@@ -227,6 +227,20 @@ describe Issue, "Issuable" do
end end
end end
describe '#labels_array' do
let(:project) { create(:project) }
let(:bug) { create(:label, project: project, title: 'bug') }
let(:issue) { create(:issue, project: project) }
before(:each) do
issue.labels << bug
end
it 'loads the association and returns it as an array' do
expect(issue.reload.labels_array).to eq([bug])
end
end
describe "votes" do describe "votes" do
let(:project) { issue.project } let(:project) { issue.project }
......
...@@ -269,4 +269,21 @@ describe Issue, models: true do ...@@ -269,4 +269,21 @@ describe Issue, models: true do
end end
end end
end end
describe 'cached counts' do
it 'updates when assignees change' do
user1 = create(:user)
user2 = create(:user)
issue = create(:issue, assignee: user1)
expect(user1.assigned_open_issues_count).to eq(1)
expect(user2.assigned_open_issues_count).to eq(0)
issue.assignee = user2
issue.save
expect(user1.assigned_open_issues_count).to eq(0)
expect(user2.assigned_open_issues_count).to eq(1)
end
end
end end
...@@ -438,4 +438,21 @@ describe MergeRequest, models: true do ...@@ -438,4 +438,21 @@ describe MergeRequest, models: true do
expect(mr.participants).to include(note1.author, note2.author) expect(mr.participants).to include(note1.author, note2.author)
end end
end end
describe 'cached counts' do
it 'updates when assignees change' do
user1 = create(:user)
user2 = create(:user)
mr = create(:merge_request, assignee: user1)
expect(user1.assigned_open_merge_request_count).to eq(1)
expect(user2.assigned_open_merge_request_count).to eq(0)
mr.assignee = user2
mr.save
expect(user1.assigned_open_merge_request_count).to eq(0)
expect(user2.assigned_open_merge_request_count).to eq(1)
end
end
end end
...@@ -49,7 +49,7 @@ describe Projects::ImportService, services: true do ...@@ -49,7 +49,7 @@ describe Projects::ImportService, services: true do
result = subject.execute result = subject.execute
expect(result[:status]).to eq :error expect(result[:status]).to eq :error
expect(result[:message]).to eq 'Failed to import the repository' expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.path_with_namespace} - Failed to import the repository"
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment