Merge remote-tracking branch 'origin/master' into rename-ci-commit
This commit is contained in:
commit
0a51c95464
40 changed files with 270 additions and 204 deletions
0
.vagrant_enabled
Normal file
0
.vagrant_enabled
Normal file
30
CHANGELOG
30
CHANGELOG
|
@ -7,6 +7,7 @@ v 8.9.0 (unreleased)
|
|||
- Fix issue todo not remove when leave project !4150 (Long Nguyen)
|
||||
- Allow forking projects with restricted visibility level
|
||||
- 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
|
||||
- Fix 404 page when viewing TODOs that contain milestones or labels in different projects
|
||||
- Redesign navigation for project pages
|
||||
|
@ -27,15 +28,30 @@ v 8.9.0 (unreleased)
|
|||
- Measure queue duration between gitlab-workhorse and Rails
|
||||
- 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)
|
||||
- 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
|
||||
- Fix incorrect links on pipeline page when merge request created from fork
|
||||
- Fix gitlab importer failing to import new projects due to missing credentials
|
||||
- Fix serious performance bug with rendering Markdown with InlineDiffFilter
|
||||
- Fix import URL migration not rescuing with the correct Error
|
||||
- In search results, only show notes on confidential issues that the user has access to
|
||||
- Fix health check access token changing due to old application settings being used
|
||||
- Fix wiki project clone address error (chujinjin)
|
||||
- Fix 404 page when viewing TODOs that contain milestones or labels in different projects. !4312
|
||||
- Fixed JS error when trying to remove discussion form. !4303
|
||||
- Fixed issue with button color when no CI enabled. !4287
|
||||
- Fixed potential issue with 2 CI status polling events happening. !3869
|
||||
- Improve design of Pipeline view. !4230
|
||||
- Fix gitlab importer failing to import new projects due to missing credentials. !4301
|
||||
- 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
|
||||
- Added remove due date button. !4209
|
||||
|
|
8
Gemfile
8
Gemfile
|
@ -83,8 +83,14 @@ gem "carrierwave", '~> 0.10.0'
|
|||
# Drag and Drop UI
|
||||
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
|
||||
gem "fog", "~> 1.36.0"
|
||||
gem "unf", '~> 0.1.4'
|
||||
|
||||
# Authorization
|
||||
|
|
114
Gemfile.lock
114
Gemfile.lock
|
@ -1,7 +1,6 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (2.3.2)
|
||||
RedCloth (4.2.9)
|
||||
ace-rails-ap (4.0.2)
|
||||
actionmailer (4.2.6)
|
||||
|
@ -183,7 +182,7 @@ GEM
|
|||
erubis (2.7.0)
|
||||
escape_utils (1.1.1)
|
||||
eventmachine (1.0.8)
|
||||
excon (0.45.4)
|
||||
excon (0.49.0)
|
||||
execjs (2.6.0)
|
||||
expression_parser (0.9.0)
|
||||
factory_girl (4.5.0)
|
||||
|
@ -200,8 +199,6 @@ GEM
|
|||
multi_json
|
||||
ffaker (2.0.0)
|
||||
ffi (1.9.10)
|
||||
fission (0.5.0)
|
||||
CFPropertyList (~> 2.2)
|
||||
flay (2.6.1)
|
||||
ruby_parser (~> 3.0)
|
||||
sexp_processor (~> 4.0)
|
||||
|
@ -211,109 +208,28 @@ GEM
|
|||
flowdock (0.7.1)
|
||||
httparty (~> 0.7)
|
||||
multi_json
|
||||
fog (1.36.0)
|
||||
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-aws (0.9.2)
|
||||
fog-core (~> 1.27)
|
||||
fog-json (~> 1.0)
|
||||
fog-xml (~> 0.1)
|
||||
ipaddress (~> 0.8)
|
||||
fog-brightbox (0.10.1)
|
||||
fog-core (~> 1.22)
|
||||
fog-json
|
||||
inflecto (~> 0.0.2)
|
||||
fog-core (1.35.0)
|
||||
fog-core (1.40.0)
|
||||
builder
|
||||
excon (~> 0.45)
|
||||
excon (~> 0.49)
|
||||
formatador (~> 0.2)
|
||||
fog-dynect (0.0.2)
|
||||
fog-core
|
||||
fog-json
|
||||
fog-xml
|
||||
fog-ecloud (0.3.0)
|
||||
fog-core
|
||||
fog-xml
|
||||
fog-google (0.1.0)
|
||||
fog-google (0.3.2)
|
||||
fog-core
|
||||
fog-json
|
||||
fog-xml
|
||||
fog-json (1.0.2)
|
||||
fog-core (~> 1.0)
|
||||
multi_json (~> 1.10)
|
||||
fog-local (0.2.1)
|
||||
fog-local (0.3.0)
|
||||
fog-core (~> 1.27)
|
||||
fog-powerdns (0.1.1)
|
||||
fog-core (~> 1.27)
|
||||
fog-json (~> 1.0)
|
||||
fog-xml (~> 0.1)
|
||||
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-openstack (0.1.6)
|
||||
fog-core (>= 1.39)
|
||||
fog-json (>= 1.0)
|
||||
ipaddress (>= 0.8)
|
||||
fog-xml (0.1.2)
|
||||
fog-core
|
||||
nokogiri (~> 1.5, >= 1.5.11)
|
||||
|
@ -422,11 +338,10 @@ GEM
|
|||
httpclient (2.7.0.1)
|
||||
i18n (0.7.0)
|
||||
ice_nine (0.11.1)
|
||||
inflecto (0.0.2)
|
||||
influxdb (0.2.3)
|
||||
cause
|
||||
json
|
||||
ipaddress (0.8.2)
|
||||
ipaddress (0.8.3)
|
||||
jquery-atwho-rails (1.3.2)
|
||||
jquery-rails (4.1.1)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
|
@ -873,7 +788,6 @@ GEM
|
|||
builder
|
||||
expression_parser
|
||||
rinku
|
||||
xml-simple (1.1.5)
|
||||
xpath (2.0.0)
|
||||
nokogiri (~> 1.3)
|
||||
|
||||
|
@ -927,7 +841,11 @@ DEPENDENCIES
|
|||
ffaker (~> 2.0.0)
|
||||
flay
|
||||
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)
|
||||
foreman
|
||||
fuubar (~> 2.0.0)
|
||||
|
|
|
@ -20,8 +20,7 @@ class @SearchAutocomplete
|
|||
@dropdown = @wrap.find('.dropdown')
|
||||
@dropdownContent = @dropdown.find('.dropdown-content')
|
||||
|
||||
@locationBadgeEl = @getElement('.search-location-badge')
|
||||
@locationText = @getElement('.location-text')
|
||||
@locationBadgeEl = @getElement('.location-badge')
|
||||
@scopeInputEl = @getElement('#scope')
|
||||
@searchInput = @getElement('.search-input')
|
||||
@projectInputEl = @getElement('#search_project_id')
|
||||
|
@ -133,7 +132,7 @@ class @SearchAutocomplete
|
|||
scope: @scopeInputEl.val()
|
||||
|
||||
# Location badge
|
||||
_location: @locationText.text()
|
||||
_location: @locationBadgeEl.text()
|
||||
}
|
||||
|
||||
bindEvents: ->
|
||||
|
@ -143,12 +142,14 @@ class @SearchAutocomplete
|
|||
@searchInput.on 'click', @onSearchInputClick
|
||||
@searchInput.on 'focus', @onSearchInputFocus
|
||||
@clearInput.on 'click', @onClearInputClick
|
||||
@locationBadgeEl.on 'click', =>
|
||||
@searchInput.focus()
|
||||
|
||||
onDocumentClick: (e) =>
|
||||
# If clicking outside the search box
|
||||
# And search input is not focused
|
||||
# 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()
|
||||
|
||||
enableAutocomplete: ->
|
||||
|
@ -221,10 +222,8 @@ class @SearchAutocomplete
|
|||
category = if item.category? then "#{item.category}: " else ''
|
||||
value = if item.value? then item.value else ''
|
||||
|
||||
html = "<span class='location-badge'>
|
||||
<i class='location-text'>#{category}#{value}</i>
|
||||
</span>"
|
||||
@locationBadgeEl.html(html)
|
||||
badgeText = "#{category}#{value}"
|
||||
@locationBadgeEl.text(badgeText).show()
|
||||
@wrap.addClass('has-location-badge')
|
||||
|
||||
restoreOriginalState: ->
|
||||
|
@ -233,9 +232,8 @@ class @SearchAutocomplete
|
|||
for input in inputs
|
||||
@getElement("##{input}").val(@originalState[input])
|
||||
|
||||
|
||||
if @originalState._location is ''
|
||||
@locationBadgeEl.empty()
|
||||
@locationBadgeEl.hide()
|
||||
else
|
||||
@addLocationBadge(
|
||||
value: @originalState._location
|
||||
|
@ -244,7 +242,7 @@ class @SearchAutocomplete
|
|||
@dropdown.removeClass 'open'
|
||||
|
||||
badgePresent: ->
|
||||
@locationBadgeEl.children().length
|
||||
@locationBadgeEl.length
|
||||
|
||||
resetSearchState: ->
|
||||
inputs = Object.keys @originalState
|
||||
|
@ -257,7 +255,7 @@ class @SearchAutocomplete
|
|||
@getElement("##{input}").val('')
|
||||
|
||||
removeLocationBadge: ->
|
||||
@locationBadgeEl.empty()
|
||||
@locationBadgeEl.hide()
|
||||
|
||||
# Reset state
|
||||
@resetSearchState()
|
||||
|
|
|
@ -29,8 +29,6 @@
|
|||
margin-top: 6px;
|
||||
|
||||
p {
|
||||
overflow-x: auto;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
}
|
||||
|
||||
.search-input {
|
||||
padding-right: 20px;
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
|
@ -47,6 +48,7 @@
|
|||
display: inline-block;
|
||||
background-color: $location-badge-bg;
|
||||
vertical-align: top;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.search-input-container {
|
||||
|
@ -55,7 +57,7 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.search-location-badge, .search-input-wrap {
|
||||
.search-input-wrap {
|
||||
// Fallback if flexbox is not supported
|
||||
display: inline-block;
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ class Projects::BranchesController < Projects::ApplicationController
|
|||
redirect_to namespace_project_branches_path(@project.namespace,
|
||||
@project), status: 303
|
||||
end
|
||||
format.js { render status: status[:return_code] }
|
||||
format.js { render nothing: true, status: status[:return_code] }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -17,7 +17,9 @@ module TodosHelper
|
|||
|
||||
def todo_target_link(todo)
|
||||
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
|
||||
|
||||
def todo_target_path(todo)
|
||||
|
|
|
@ -313,6 +313,7 @@ module Ci
|
|||
build_data = Gitlab::BuildDataBuilder.build(self)
|
||||
project.execute_hooks(build_data.dup, :build_hooks)
|
||||
project.execute_services(build_data.dup, :build_hooks)
|
||||
project.running_or_pending_build_count(force: true)
|
||||
end
|
||||
|
||||
def artifacts?
|
||||
|
|
|
@ -68,6 +68,14 @@ module Issuable
|
|||
strip_attributes :title
|
||||
|
||||
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
|
||||
|
||||
module ClassMethods
|
||||
|
@ -205,6 +213,10 @@ module Issuable
|
|||
hook_data
|
||||
end
|
||||
|
||||
def labels_array
|
||||
labels.to_a
|
||||
end
|
||||
|
||||
def label_names
|
||||
labels.order('title ASC').pluck(:title)
|
||||
end
|
||||
|
|
|
@ -1011,4 +1011,22 @@ class Project < ActiveRecord::Base
|
|||
|
||||
update_attribute(:pending_delete, true)
|
||||
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
|
||||
|
|
|
@ -776,6 +776,23 @@ class User < ActiveRecord::Base
|
|||
notification_settings.find_or_initialize_by(source: source)
|
||||
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
|
||||
|
||||
def projects_union
|
||||
|
|
|
@ -56,14 +56,14 @@ module Projects
|
|||
|
||||
after_create_actions if @project.persisted?
|
||||
|
||||
@project.add_import_job if @project.import?
|
||||
|
||||
if @project.errors.empty?
|
||||
@project.add_import_job if @project.import?
|
||||
else
|
||||
fail(error: @project.errors.full_messages.join(', '))
|
||||
end
|
||||
@project
|
||||
rescue => e
|
||||
message = "Unable to save project: #{e.message}"
|
||||
Rails.logger.error(message)
|
||||
@project.errors.add(:base, message) if @project
|
||||
@project
|
||||
fail(error: e.message)
|
||||
end
|
||||
|
||||
protected
|
||||
|
@ -103,5 +103,19 @@ module Projects
|
|||
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
|
||||
|
|
|
@ -39,7 +39,7 @@ module Projects
|
|||
begin
|
||||
gitlab_shell.import_repository(project.path_with_namespace, project.import_url)
|
||||
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
|
||||
|
||||
|
|
|
@ -6,11 +6,8 @@
|
|||
.search.search-form{class: "#{'has-location-badge' if label.present?}"}
|
||||
= form_tag search_path, method: :get, class: 'navbar-form' do |f|
|
||||
.search-input-container
|
||||
.search-location-badge
|
||||
- if label.present?
|
||||
%span.location-badge
|
||||
%i.location-text
|
||||
= label
|
||||
- if label.present?
|
||||
.location-badge= label
|
||||
.search-input-wrap
|
||||
.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' }
|
||||
|
|
|
@ -30,13 +30,13 @@
|
|||
= icon('exclamation-circle fw')
|
||||
%span
|
||||
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
|
||||
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
|
||||
= icon('tasks fw')
|
||||
%span
|
||||
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
|
||||
= link_to dashboard_snippets_path, title: 'Snippets' do
|
||||
= icon('clipboard fw')
|
||||
|
|
|
@ -33,18 +33,11 @@
|
|||
%span
|
||||
Activity
|
||||
- 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
|
||||
= icon('files-o fw')
|
||||
= icon('code fw')
|
||||
%span
|
||||
Files
|
||||
|
||||
- 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
|
||||
Code
|
||||
|
||||
- if project_nav_tab? :pipelines
|
||||
= nav_link(controller: :pipelines) do
|
||||
|
@ -129,4 +122,10 @@
|
|||
= link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
|
||||
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
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
$('.js-totalbranch-count').html("#{@repository.branch_count}")
|
|
@ -1,9 +1,11 @@
|
|||
%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
|
||||
= link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
|
||||
Commits
|
||||
%span.badge
|
||||
= number_with_delimiter(@repository.commit_count)
|
||||
|
||||
= nav_link(controller: %w(network)) do
|
||||
= link_to namespace_project_network_path(@project.namespace, @project, current_ref) do
|
||||
|
@ -16,9 +18,7 @@
|
|||
= nav_link(html_options: {class: branches_tab_class}) do
|
||||
= link_to namespace_project_branches_path(@project.namespace, @project) do
|
||||
Branches
|
||||
%span.badge.js-totalbranch-count= @repository.branch_count
|
||||
|
||||
= nav_link(controller: [:tags, :releases]) do
|
||||
= link_to namespace_project_tags_path(@project.namespace, @project) do
|
||||
Tags
|
||||
%span.badge.js-totaltags-count= @repository.tag_count
|
||||
|
|
|
@ -11,4 +11,4 @@
|
|||
= link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
|
||||
%span
|
||||
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)
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
$('.js-totaltags-count').html("#{@repository.tags.size}");
|
||||
- if @repository.tags.empty?
|
||||
$('.tags').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
- 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")
|
||||
= render 'projects/last_push'
|
||||
= render "projects/commits/head"
|
||||
|
||||
.tree-controls
|
||||
= render 'projects/find_file_link'
|
||||
|
|
|
@ -114,20 +114,20 @@
|
|||
.sidebar-collapsed-icon
|
||||
= icon('tags')
|
||||
%span
|
||||
= issuable.labels.count
|
||||
= issuable.labels_array.size
|
||||
.title.hide-collapsed
|
||||
Labels
|
||||
= icon('spinner spin', class: 'block-loading')
|
||||
- if can_edit_issuable
|
||||
= link_to 'Edit', '#', class: 'edit-link pull-right'
|
||||
.value.bold.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels.any?) }
|
||||
- if issuable.labels.any?
|
||||
- issuable.labels.each do |label|
|
||||
.value.bold.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels_array.any?) }
|
||||
- if issuable.labels_array.any?
|
||||
- issuable.labels_array.each do |label|
|
||||
= link_to_label(label, type: issuable.to_ability_name)
|
||||
- else
|
||||
.light None
|
||||
.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
|
||||
.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)}}
|
||||
|
|
|
@ -15,8 +15,7 @@ class RepositoryForkWorker
|
|||
result = gitlab_shell.fork_repository(source_path, target_path)
|
||||
unless result
|
||||
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.import_fail
|
||||
project.mark_import_as_failed('The project could not be forked.')
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -24,8 +23,7 @@ class RepositoryForkWorker
|
|||
|
||||
unless project.valid_repo?
|
||||
logger.error("Project #{project_id} had an invalid repository after fork")
|
||||
project.update(import_error: "The forked repository is invalid.")
|
||||
project.import_fail
|
||||
project.mark_import_as_failed('The forked repository is invalid.')
|
||||
return
|
||||
end
|
||||
|
||||
|
|
|
@ -13,8 +13,7 @@ class RepositoryImportWorker
|
|||
result = Projects::ImportService.new(project, current_user).execute
|
||||
|
||||
if result[:status] == :error
|
||||
project.update(import_error: Gitlab::UrlSanitizer.sanitize(result[:message]))
|
||||
project.import_fail
|
||||
project.mark_import_as_failed(result[:message])
|
||||
return
|
||||
end
|
||||
|
||||
|
|
|
@ -33,4 +33,24 @@ be under 'Wiki' tab and so on and so forth.
|
|||
We want GitLab to work well on small mobile screens as well. Size limitations make it is impossible to fit everything on a mobile screen. In this case it is OK to hide
|
||||
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
|
||||
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
|
|||
Then the active main tab should be Home
|
||||
And no other main tabs should be active
|
||||
|
||||
Scenario: On Project Files
|
||||
Scenario: On Project Code
|
||||
Given I visit my project's files page
|
||||
Then the active main tab should be Files
|
||||
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
|
||||
Then the active main tab should be Code
|
||||
And no other main tabs should be active
|
||||
|
||||
Scenario: On Project Issues
|
||||
|
@ -64,40 +59,46 @@ Feature: Project Active Tab
|
|||
And no other sub navs should be active
|
||||
And the active main tab should be Settings
|
||||
|
||||
# Sub Tabs: Commits
|
||||
# Sub Tabs: Code
|
||||
|
||||
Scenario: On Project Commits/Commits
|
||||
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 Code/Commits
|
||||
Given I visit my project's commits page
|
||||
Then the active sub tab should be Commits
|
||||
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
|
||||
Then the active sub tab should be Network
|
||||
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
|
||||
And I click the "Compare" tab
|
||||
Then the active sub tab should be Compare
|
||||
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
|
||||
And I click the "Branches" tab
|
||||
Then the active sub tab should be Branches
|
||||
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
|
||||
And I click the "Tags" tab
|
||||
Then the active sub tab should be Tags
|
||||
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
|
||||
Given I visit my project's issues page
|
||||
|
|
|
@ -24,3 +24,4 @@ Feature: Project Builds Summary
|
|||
Then recent build has been erased
|
||||
And recent build summary does not have artifacts widget
|
||||
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
|
|||
@javascript
|
||||
Scenario: Navigate to files tab
|
||||
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
|
||||
Scenario: Navigate to commits tab
|
||||
Given I visit my project's files page
|
||||
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
|
||||
Scenario: Navigate to network tab
|
||||
Given I press "g" and "n"
|
||||
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
|
||||
Scenario: Navigate to graphs tab
|
||||
|
|
|
@ -63,10 +63,6 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
|
|||
click_link('Tags')
|
||||
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
|
||||
ensure_active_sub_tab('Compare')
|
||||
end
|
||||
|
|
|
@ -36,4 +36,8 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps
|
|||
expect(page).to have_content 'Build has been erased'
|
||||
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
|
||||
|
|
|
@ -13,12 +13,12 @@ class Spinach::Features::ProjectFindFile < Spinach::FeatureSteps
|
|||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
|
|
|
@ -8,12 +8,8 @@ module SharedProjectTab
|
|||
ensure_active_main_tab('Project')
|
||||
end
|
||||
|
||||
step 'the active main tab should be Files' do
|
||||
ensure_active_main_tab('Files')
|
||||
end
|
||||
|
||||
step 'the active main tab should be Commits' do
|
||||
ensure_active_main_tab('Commits')
|
||||
step 'the active main tab should be Code' do
|
||||
ensure_active_main_tab('Code')
|
||||
end
|
||||
|
||||
step 'the active main tab should be Graphs' do
|
||||
|
@ -51,4 +47,12 @@ module SharedProjectTab
|
|||
step 'the active sub tab should be Network' do
|
||||
ensure_active_sub_tab('Network')
|
||||
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
|
||||
|
|
|
@ -122,27 +122,23 @@ describe Projects::BranchesController do
|
|||
let(:branch) { "feature" }
|
||||
|
||||
it { expect(response.status).to eq(200) }
|
||||
it { expect(subject).to render_template('destroy') }
|
||||
end
|
||||
|
||||
context "valid branch name with unencoded slashes" do
|
||||
let(:branch) { "improve/awesome" }
|
||||
|
||||
it { expect(response.status).to eq(200) }
|
||||
it { expect(subject).to render_template('destroy') }
|
||||
end
|
||||
|
||||
context "valid branch name with encoded slashes" do
|
||||
let(:branch) { "improve%2Fawesome" }
|
||||
|
||||
it { expect(response.status).to eq(200) }
|
||||
it { expect(subject).to render_template('destroy') }
|
||||
end
|
||||
context "invalid branch name, valid ref" do
|
||||
let(:branch) { "no-branch" }
|
||||
|
||||
it { expect(response.status).to eq(404) }
|
||||
it { expect(subject).to render_template('destroy') }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -227,6 +227,20 @@ describe Issue, "Issuable" do
|
|||
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
|
||||
let(:project) { issue.project }
|
||||
|
||||
|
|
|
@ -269,4 +269,21 @@ describe Issue, models: true do
|
|||
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
|
||||
|
|
|
@ -438,4 +438,21 @@ describe MergeRequest, models: true do
|
|||
expect(mr.participants).to include(note1.author, note2.author)
|
||||
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
|
||||
|
|
|
@ -49,7 +49,7 @@ describe Projects::ImportService, services: true do
|
|||
result = subject.execute
|
||||
|
||||
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
|
||||
|
||||
|
|
Loading…
Reference in a new issue