summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop.yml5
-rw-r--r--.rubocop_todo.yml7
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock91
-rw-r--r--README.md2
-rw-r--r--app/assets/javascripts/create_label.js.es66
-rw-r--r--app/assets/javascripts/dispatcher.js.es62
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_hint.js.es629
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es627
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es66
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js.es615
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es65
-rw-r--r--app/assets/javascripts/gl_dropdown.js3
-rw-r--r--app/assets/javascripts/header.js2
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js3
-rw-r--r--app/assets/javascripts/search_autocomplete.js.es64
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss5
-rw-r--r--app/assets/stylesheets/pages/projects.scss7
-rw-r--r--app/assets/stylesheets/pages/todos.scss46
-rw-r--r--app/controllers/application_controller.rb8
-rw-r--r--app/controllers/dashboard/todos_controller.rb11
-rw-r--r--app/controllers/import/fogbugz_controller.rb2
-rw-r--r--app/controllers/import/google_code_controller.rb4
-rw-r--r--app/controllers/invites_controller.rb4
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb2
-rw-r--r--app/controllers/profiles/keys_controller.rb4
-rw-r--r--app/controllers/projects/blob_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb11
-rw-r--r--app/controllers/projects/tree_controller.rb4
-rw-r--r--app/controllers/snippets_controller.rb2
-rw-r--r--app/helpers/namespaces_helper.rb4
-rw-r--r--app/helpers/todos_helper.rb4
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/project_services/drone_ci_service.rb2
-rw-r--r--app/models/project_services/irker_service.rb2
-rw-r--r--app/services/projects/upload_service.rb2
-rw-r--r--app/services/users/destroy_service.rb4
-rw-r--r--app/views/dashboard/todos/_todo.html.haml17
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/projects/commit/_pipeline.html.haml2
-rw-r--r--app/views/projects/merge_requests/index.html.haml7
-rw-r--r--app/views/projects/new.html.haml2
-rw-r--r--app/views/projects/pipelines/_info.html.haml2
-rw-r--r--app/views/projects/pipelines_settings/_show.html.haml2
-rw-r--r--app/views/search/_results.html.haml2
-rw-r--r--app/views/search/results/_snippet_blob.html.haml55
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml8
-rw-r--r--app/views/shared/members/_member.html.haml2
-rw-r--r--app/workers/delete_user_worker.rb2
-rw-r--r--changelogs/unreleased/1363-redo-mailroom-support.yml4
-rw-r--r--changelogs/unreleased/26206-fix-download-dropdown.yml4
-rw-r--r--changelogs/unreleased/26703-todos-count.yml4
-rw-r--r--changelogs/unreleased/26881-backup-fails-if-data-changes.yml4
-rw-r--r--changelogs/unreleased/27287-label-dropdown-error-messages.yml4
-rw-r--r--changelogs/unreleased/28186-long-group-names-overflow-out-of-todos-view.yml4
-rw-r--r--changelogs/unreleased/28357-colon-search.yml4
-rw-r--r--changelogs/unreleased/28389-ux-problem-with-pipeline-coverage-placeholder.yml4
-rw-r--r--changelogs/unreleased/28462-fix-delimiter-removes-issue-in-todo-counter.yml4
-rw-r--r--changelogs/unreleased/add-filtered-search-to-mr.yml4
-rw-r--r--changelogs/unreleased/api-todos-restful.yml4
-rw-r--r--changelogs/unreleased/commit-search-ui-fix.yml4
-rw-r--r--changelogs/unreleased/group-memebrs-owner-level.yml4
-rw-r--r--changelogs/unreleased/rename-retry-failed-pipeline-to-retry.yml4
-rw-r--r--changelogs/unreleased/sh-delete-user-permission-check.yml4
-rw-r--r--changelogs/unreleased/snippets-search.yml4
-rw-r--r--config/initializers/mysql_ignore_postgresql_options.rb2
-rw-r--r--config/initializers/rack_lineprof.rb2
-rw-r--r--config/mail_room.yml5
-rw-r--r--config/routes/sidekiq.rb2
-rw-r--r--doc/api/pipelines.md2
-rw-r--r--doc/api/todos.md15
-rw-r--r--doc/api/v3_to_v4.md2
-rw-r--r--doc/ci/docker/using_docker_images.md15
-rw-r--r--doc/development/licensing.md7
-rw-r--r--doc/raketasks/backup_restore.md22
-rw-r--r--doc/workflow/todos.md3
-rw-r--r--features/project/merge_requests.feature7
-rw-r--r--features/project/merge_requests/revert.feature1
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/helpers.rb2
-rw-r--r--lib/api/pipelines.rb4
-rw-r--r--lib/api/todos.rb6
-rw-r--r--lib/api/v3/todos.rb28
-rw-r--r--lib/backup/files.rb17
-rw-r--r--lib/banzai/filter/plantuml_filter.rb2
-rw-r--r--lib/ci/ansi2html.rb2
-rw-r--r--lib/gitlab/database/migration_helpers.rb11
-rw-r--r--lib/gitlab/metrics/system.rb2
-rw-r--r--lib/tasks/gitlab/check.rake7
-rw-r--r--spec/config/mail_room_spec.rb4
-rw-r--r--spec/controllers/dashboard/todos_controller_spec.rb2
-rw-r--r--spec/features/commits_spec.rb4
-rw-r--r--spec/features/groups/members/list_spec.rb15
-rw-r--r--spec/features/issues/filtered_search/dropdown_label_spec.rb8
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb14
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb65
-rw-r--r--spec/features/merge_requests/filter_by_labels_spec.rb86
-rw-r--r--spec/features/merge_requests/filter_by_milestone_spec.rb35
-rw-r--r--spec/features/merge_requests/filter_merge_requests_spec.rb281
-rw-r--r--spec/features/merge_requests/reset_filters_spec.rb29
-rw-r--r--spec/features/projects/new_project_spec.rb45
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb12
-rw-r--r--spec/features/projects/ref_switcher_spec.rb2
-rw-r--r--spec/features/search_spec.rb4
-rw-r--r--spec/features/todos/todos_spec.rb23
-rw-r--r--spec/fixtures/config/mail_room_disabled.yml (renamed from spec/fixtures/mail_room_disabled.yml)0
-rw-r--r--spec/fixtures/config/mail_room_enabled.yml (renamed from spec/fixtures/mail_room_enabled.yml)0
-rw-r--r--spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js.es623
-rw-r--r--spec/javascripts/header_spec.js4
-rw-r--r--spec/javascripts/lib/utils/text_utility_spec.js.es611
-rw-r--r--spec/javascripts/search_autocomplete_spec.js4
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb10
-rw-r--r--spec/requests/api/todos_spec.rb27
-rw-r--r--spec/requests/api/v3/todos_spec.rb73
-rw-r--r--spec/services/users/destroy_spec.rb31
-rw-r--r--spec/support/filtered_search_helpers.rb37
-rw-r--r--spec/support/merge_request_helpers.rb9
117 files changed, 955 insertions, 580 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index b093d4d25d4..a836b469cc7 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -54,6 +54,11 @@ Style/AlignArray:
Style/AlignHash:
Enabled: true
+# Whether `and` and `or` are banned only in conditionals (conditionals)
+# or completely (always).
+Style/AndOr:
+ Enabled: true
+
# Use `Array#join` instead of `Array#*`.
Style/ArrayJoin:
Enabled: true
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 648b3fc49d2..a5b4d2f5b02 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -180,13 +180,6 @@ Security/JSONLoad:
Style/AlignParameters:
Enabled: false
-# Offense count: 27
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: always, conditionals
-Style/AndOr:
- Enabled: false
-
# Offense count: 54
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
diff --git a/Gemfile b/Gemfile
index ccbbb11c5d9..01861f1ffac 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,6 @@
source 'https://rubygems.org'
-gem 'rails', '4.2.7.1'
+gem 'rails', '4.2.8'
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with
@@ -333,7 +333,7 @@ gem 'newrelic_rpm', '~> 3.16'
gem 'octokit', '~> 4.6.2'
-gem 'mail_room', '~> 0.9.0'
+gem 'mail_room', '~> 0.9.1'
gem 'email_reply_trimmer', '~> 0.1'
gem 'html2text'
diff --git a/Gemfile.lock b/Gemfile.lock
index 4f98dc9d085..2a3be763753 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -3,40 +3,39 @@ GEM
specs:
RedCloth (4.3.2)
ace-rails-ap (4.1.0)
- actionmailer (4.2.7.1)
- actionpack (= 4.2.7.1)
- actionview (= 4.2.7.1)
- activejob (= 4.2.7.1)
+ actionmailer (4.2.8)
+ actionpack (= 4.2.8)
+ actionview (= 4.2.8)
+ activejob (= 4.2.8)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
- actionpack (4.2.7.1)
- actionview (= 4.2.7.1)
- activesupport (= 4.2.7.1)
+ actionpack (4.2.8)
+ actionview (= 4.2.8)
+ activesupport (= 4.2.8)
rack (~> 1.6)
rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (4.2.7.1)
- activesupport (= 4.2.7.1)
+ actionview (4.2.8)
+ activesupport (= 4.2.8)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
- activejob (4.2.7.1)
- activesupport (= 4.2.7.1)
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
+ activejob (4.2.8)
+ activesupport (= 4.2.8)
globalid (>= 0.3.0)
- activemodel (4.2.7.1)
- activesupport (= 4.2.7.1)
+ activemodel (4.2.8)
+ activesupport (= 4.2.8)
builder (~> 3.1)
- activerecord (4.2.7.1)
- activemodel (= 4.2.7.1)
- activesupport (= 4.2.7.1)
+ activerecord (4.2.8)
+ activemodel (= 4.2.8)
+ activesupport (= 4.2.8)
arel (~> 6.0)
activerecord_sane_schema_dumper (0.2)
rails (>= 4, < 5)
- activesupport (4.2.7.1)
+ activesupport (4.2.8)
i18n (~> 0.7)
- json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
@@ -47,7 +46,7 @@ GEM
activerecord (>= 3.0)
akismet (2.0.0)
allocations (1.0.5)
- arel (6.0.3)
+ arel (6.0.4)
asana (0.4.0)
faraday (~> 0.9)
faraday_middleware (~> 0.9)
@@ -86,7 +85,7 @@ GEM
sass (>= 3.3.4)
brakeman (3.4.1)
browser (2.2.0)
- builder (3.2.2)
+ builder (3.2.3)
bullet (5.2.0)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.10.0)
@@ -127,7 +126,7 @@ GEM
execjs
coffee-script-source (1.10.0)
colorize (0.7.7)
- concurrent-ruby (1.0.2)
+ concurrent-ruby (1.0.4)
connection_pool (2.2.1)
crack (0.4.3)
safe_yaml (~> 1.0.0)
@@ -354,7 +353,7 @@ GEM
json (~> 1.8)
multi_xml (>= 0.5.2)
httpclient (2.8.2)
- i18n (0.7.0)
+ i18n (0.8.0)
ice_nine (0.11.1)
influxdb (0.2.3)
cause
@@ -370,7 +369,7 @@ GEM
thor (>= 0.14, < 2.0)
jquery-ui-rails (5.0.5)
railties (>= 3.2.16)
- json (1.8.3)
+ json (1.8.6)
json-schema (2.6.2)
addressable (~> 2.3.8)
jwt (1.5.6)
@@ -409,7 +408,7 @@ GEM
nokogiri (>= 1.5.9)
mail (2.6.4)
mime-types (>= 1.16, < 4)
- mail_room (0.9.0)
+ mail_room (0.9.1)
memoist (0.15.0)
method_source (0.8.2)
mime-types (2.99.3)
@@ -429,9 +428,8 @@ GEM
net-ssh (3.0.1)
netrc (0.11.0)
newrelic_rpm (3.16.0.318)
- nokogiri (1.6.8)
+ nokogiri (1.6.8.1)
mini_portile2 (~> 2.1.0)
- pkg-config (~> 1.1.7)
numerizer (0.1.1)
oauth (0.5.1)
oauth2 (1.2.0)
@@ -506,7 +504,6 @@ GEM
parser (2.3.1.4)
ast (~> 2.2)
pg (0.18.4)
- pkg-config (1.1.7)
poltergeist (1.9.0)
capybara (~> 2.1)
cliver (~> 0.3.1)
@@ -548,28 +545,28 @@ GEM
rack
rack-test (0.6.3)
rack (>= 1.0)
- rails (4.2.7.1)
- actionmailer (= 4.2.7.1)
- actionpack (= 4.2.7.1)
- actionview (= 4.2.7.1)
- activejob (= 4.2.7.1)
- activemodel (= 4.2.7.1)
- activerecord (= 4.2.7.1)
- activesupport (= 4.2.7.1)
+ rails (4.2.8)
+ actionmailer (= 4.2.8)
+ actionpack (= 4.2.8)
+ actionview (= 4.2.8)
+ activejob (= 4.2.8)
+ activemodel (= 4.2.8)
+ activerecord (= 4.2.8)
+ activesupport (= 4.2.8)
bundler (>= 1.3.0, < 2.0)
- railties (= 4.2.7.1)
+ railties (= 4.2.8)
sprockets-rails
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
- rails-dom-testing (1.0.7)
+ rails-dom-testing (1.0.8)
activesupport (>= 4.2.0.beta, < 5.0)
- nokogiri (~> 1.6.0)
+ nokogiri (~> 1.6)
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
- railties (4.2.7.1)
- actionpack (= 4.2.7.1)
- activesupport (= 4.2.7.1)
+ railties (4.2.8)
+ actionpack (= 4.2.8)
+ activesupport (= 4.2.8)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.1.0)
@@ -733,10 +730,10 @@ GEM
spring (>= 0.9.1)
spring-commands-spinach (1.1.0)
spring (>= 0.9.1)
- sprockets (3.7.0)
+ sprockets (3.7.1)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
- sprockets-rails (3.1.1)
+ sprockets-rails (3.2.0)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
@@ -760,7 +757,7 @@ GEM
daemons (~> 1.0, >= 1.0.9)
eventmachine (~> 1.0, >= 1.0.4)
rack (>= 1, < 3)
- thor (0.19.1)
+ thor (0.19.4)
thread_safe (0.3.5)
tilt (2.0.5)
timecop (0.8.1)
@@ -912,7 +909,7 @@ DEPENDENCIES
license_finder (~> 2.1.0)
licensee (~> 8.0.0)
loofah (~> 2.0.3)
- mail_room (~> 0.9.0)
+ mail_room (~> 0.9.1)
method_source (~> 0.8)
minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6)
@@ -949,7 +946,7 @@ DEPENDENCIES
rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1)
rack-proxy (~> 0.6.0)
- rails (= 4.2.7.1)
+ rails (= 4.2.8)
rails-deprecated_sanitizer (~> 1.0.3)
rainbow (~> 2.1.0)
rblineprof (~> 0.3.6)
diff --git a/README.md b/README.md
index 4f85fac4a56..09e08adbb73 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,7 @@ We're hiring developers, support people, and production engineers all the time,
There are two editions of GitLab:
- GitLab Community Edition (CE) is available freely under the MIT Expat license.
-- GitLab Enterprise Edition (EE) includes [extra features](https://about.gitlab.com/features/#compare) that are more useful for organizations with more than 100 users. To use EE and get official support please [become a subscriber](https://about.gitlab.com/pricing/).
+- GitLab Enterprise Edition (EE) includes [extra features](https://about.gitlab.com/products/#compare-options) that are more useful for organizations with more than 100 users. To use EE and get official support please [become a subscriber](https://about.gitlab.com/products/).
## Website
diff --git a/app/assets/javascripts/create_label.js.es6 b/app/assets/javascripts/create_label.js.es6
index 947c129d5b5..85384d98126 100644
--- a/app/assets/javascripts/create_label.js.es6
+++ b/app/assets/javascripts/create_label.js.es6
@@ -107,9 +107,9 @@
if (typeof label.message === 'string') {
errors = label.message;
} else {
- errors = label.message.map(function (value, key) {
- return key + " " + value[0];
- }).join("<br/>");
+ errors = Object.keys(label.message).map(key =>
+ `${gl.text.humanize(key)} ${label.message[key].join(', ')}`
+ ).join("<br/>");
}
this.$newLabelError
diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6
index 45aa6050aed..f55db02f0fd 100644
--- a/app/assets/javascripts/dispatcher.js.es6
+++ b/app/assets/javascripts/dispatcher.js.es6
@@ -74,7 +74,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'projects:merge_requests:index':
case 'projects:issues:index':
if (gl.FilteredSearchManager) {
- new gl.FilteredSearchManager();
+ new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests');
}
Issuable.init();
new gl.IssuableBulkActions({
diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6
index 572c221929a..9e92d544bef 100644
--- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6
+++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6
@@ -37,23 +37,18 @@ require('./filtered_search_dropdown');
}
renderContent() {
- const dropdownData = [{
- icon: 'fa-pencil',
- hint: 'author:',
- tag: '&lt;@author&gt;',
- }, {
- icon: 'fa-user',
- hint: 'assignee:',
- tag: '&lt;@assignee&gt;',
- }, {
- icon: 'fa-clock-o',
- hint: 'milestone:',
- tag: '&lt;%milestone&gt;',
- }, {
- icon: 'fa-tag',
- hint: 'label:',
- tag: '&lt;~label&gt;',
- }];
+ const dropdownData = [];
+
+ [].forEach.call(this.input.parentElement.querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
+ const { icon, hint, tag } = dropdownMenu.dataset;
+ if (icon && hint && tag) {
+ dropdownData.push({
+ icon: `fa-${icon}`,
+ hint,
+ tag: `&lt;${tag}&gt;`,
+ });
+ }
+ });
this.droplab.changeHookList(this.hookId, this.dropdown, [droplabFilter], this.config);
this.droplab.setData(this.hookId, dropdownData);
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
index e8c2df03a46..fbc72a3001a 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
@@ -52,8 +52,9 @@
}
renderContent(forceShowList = false) {
- if (forceShowList && this.getCurrentHook().list.hidden) {
- this.getCurrentHook().list.show();
+ const currentHook = this.getCurrentHook();
+ if (forceShowList && currentHook && currentHook.list.hidden) {
+ currentHook.list.show();
}
}
@@ -92,18 +93,24 @@
}
hideDropdown() {
- this.getCurrentHook().list.hide();
+ const currentHook = this.getCurrentHook();
+ if (currentHook) {
+ currentHook.list.hide();
+ }
}
resetFilters() {
const hook = this.getCurrentHook();
- const data = hook.list.data;
- const results = data.map((o) => {
- const updated = o;
- updated.droplab_hidden = false;
- return updated;
- });
- hook.list.render(results);
+
+ if (hook) {
+ const data = hook.list.data;
+ const results = data.map((o) => {
+ const updated = o;
+ updated.droplab_hidden = false;
+ return updated;
+ });
+ hook.list.render(results);
+ }
}
}
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6
index 8ce4cf4fc36..cecd3518ce3 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6
@@ -2,10 +2,12 @@
(() => {
class FilteredSearchDropdownManager {
- constructor(baseEndpoint = '') {
+ constructor(baseEndpoint = '', page) {
this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
this.tokenizer = gl.FilteredSearchTokenizer;
+ this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
this.filteredSearchInput = document.querySelector('.filtered-search');
+ this.page = page;
this.setupMapping();
@@ -150,7 +152,7 @@
this.droplab = new DropLab();
}
- const match = gl.FilteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase());
+ const match = this.filteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase());
const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key
&& this.mapping[match.key];
const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint';
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
index ffc7d29e4c5..bbafead0305 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
@@ -1,12 +1,13 @@
(() => {
class FilteredSearchManager {
- constructor() {
+ constructor(page) {
this.filteredSearchInput = document.querySelector('.filtered-search');
this.clearSearchButton = document.querySelector('.clear-search');
+ this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
if (this.filteredSearchInput) {
this.tokenizer = gl.FilteredSearchTokenizer;
- this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '');
+ this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', page);
this.bindEvents();
this.loadSearchParamsFromURL();
@@ -117,8 +118,8 @@
const keyParam = decodeURIComponent(split[0]);
const value = split[1];
- // Check if it matches edge conditions listed in gl.FilteredSearchTokenKeys
- const condition = gl.FilteredSearchTokenKeys.searchByConditionUrl(p);
+ // Check if it matches edge conditions listed in this.filteredSearchTokenKeys
+ const condition = this.filteredSearchTokenKeys.searchByConditionUrl(p);
if (condition) {
inputValues.push(`${condition.tokenKey}:${condition.value}`);
@@ -126,7 +127,7 @@
// Sanitize value since URL converts spaces into +
// Replace before decode so that we know what was originally + versus the encoded +
const sanitizedValue = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : value;
- const match = gl.FilteredSearchTokenKeys.searchByKeyParam(keyParam);
+ const match = this.filteredSearchTokenKeys.searchByKeyParam(keyParam);
if (match) {
const indexOf = keyParam.indexOf('_');
@@ -171,9 +172,9 @@
paths.push(`state=${currentState}`);
tokens.forEach((token) => {
- const condition = gl.FilteredSearchTokenKeys
+ const condition = this.filteredSearchTokenKeys
.searchByConditionKeyValue(token.key, token.value.toLowerCase());
- const { param } = gl.FilteredSearchTokenKeys.searchByKey(token.key);
+ const { param } = this.filteredSearchTokenKeys.searchByKey(token.key) || {};
const keyParam = param ? `${token.key}_${param}` : token.key;
let tokenPath = '';
diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6
index cf53845a48b..9bf1b1ced88 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6
@@ -1,9 +1,12 @@
+require('./filtered_search_token_keys');
+
(() => {
class FilteredSearchTokenizer {
static processTokens(input) {
+ const allowedKeys = gl.FilteredSearchTokenKeys.get().map(i => i.key);
// Regex extracts `(token):(symbol)(value)`
// Values that start with a double quote must end in a double quote (same for single)
- const tokenRegex = /(\w+):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\S+))/g;
+ const tokenRegex = new RegExp(`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`, 'g');
const tokens = [];
let lastToken = null;
const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => {
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index bf3da8528f0..a01662e2f9e 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -47,9 +47,10 @@
}
// Only filter asynchronously only if option remote is set
if (this.options.remote) {
- $inputContainer.parent().addClass('is-loading');
clearTimeout(timeout);
return timeout = setTimeout(function() {
+ $inputContainer.parent().addClass('is-loading');
+
return this.options.query(this.input.val(), function(data) {
$inputContainer.parent().removeClass('is-loading');
return this.options.callback(data);
diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js
index fa85f9a6c86..a853c3aeb1f 100644
--- a/app/assets/javascripts/header.js
+++ b/app/assets/javascripts/header.js
@@ -2,7 +2,7 @@
(function() {
$(document).on('todo:toggle', function(e, count) {
var $todoPendingCount = $('.todos-pending-count');
- $todoPendingCount.text(gl.text.addDelimiter(count));
+ $todoPendingCount.text(gl.text.highCountTrim(count));
$todoPendingCount.toggleClass('hidden', count === 0);
});
})();
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index f755d212b3c..579d322e3fb 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -14,6 +14,9 @@ require('vendor/latinise');
gl.text.addDelimiter = function(text) {
return text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : text;
};
+ gl.text.highCountTrim = function(count) {
+ return count > 99 ? '99+' : count;
+ };
gl.text.randomString = function() {
return Math.random().toString(36).substring(7);
};
diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6
index 6250e75d407..6fd5345a0a6 100644
--- a/app/assets/javascripts/search_autocomplete.js.es6
+++ b/app/assets/javascripts/search_autocomplete.js.es6
@@ -169,10 +169,10 @@
url: issuesPath + "/?author_username=" + userName
}, 'separator', {
text: 'Merge requests assigned to me',
- url: mrPath + "/?assignee_id=" + userId
+ url: mrPath + "/?assignee_username=" + userName
}, {
text: "Merge requests I've created",
- url: mrPath + "/?author_id=" + userId
+ url: mrPath + "/?author_username=" + userName
}
];
if (!name) {
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 00eb5b30fd5..3fe1eef307e 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -222,6 +222,11 @@
}
}
+ .dropdown-menu {
+ max-height: 250px;
+ overflow-y: auto;
+ }
+
.dropdown-toggle,
.dropdown-menu {
color: $gl-text-color-secondary;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index adbdc5214a4..8c0de314420 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -268,6 +268,13 @@
}
}
+.project-repo-buttons {
+ .project-action-button .dropdown-menu {
+ max-height: 250px;
+ overflow-y: auto;
+ }
+}
+
.split-one {
display: inline-table;
margin-right: 12px;
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 551a66fbf3a..af9ddb9ff80 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -6,6 +6,8 @@
.navbar-nav {
li {
.badge.todos-pending-count {
+ position: inherit;
+ top: -6px;
margin-top: -5px;
font-weight: normal;
background: $todo-alert-blue;
@@ -43,6 +45,12 @@
}
}
+ .todo-avatar,
+ .todo-actions {
+ -webkit-flex: 0 0 auto;
+ flex: 0 0 auto;
+ }
+
.todo-actions {
display: -webkit-flex;
display: flex;
@@ -55,8 +63,9 @@
}
.todo-item {
- -webkit-flex: auto;
- flex: auto;
+ -webkit-flex: 0 1 100%;
+ flex: 0 1 100%;
+ min-width: 0;
}
}
@@ -74,8 +83,29 @@
.todo-item {
.todo-title {
- @include str-truncated(calc(100% - 174px));
- overflow: visible;
+ display: flex;
+
+ & > .title-item {
+ -webkit-flex: 0 0 auto;
+ flex: 0 0 auto;
+ margin: 0 2px;
+
+ &:first-child {
+ margin-left: 0;
+ }
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+
+ .todo-label {
+ -webkit-flex: 0 1 auto;
+ flex: 0 1 auto;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
}
.status-box {
@@ -154,10 +184,12 @@
.todo-item {
.todo-title {
- white-space: normal;
- overflow: visible;
- max-width: 100%;
+ flex-flow: row wrap;
margin-bottom: 10px;
+
+ .todo-label {
+ white-space: normal;
+ }
}
.todo-body {
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index bf6be3d516b..5e7af3bff0d 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -74,7 +74,7 @@ class ApplicationController < ActionController::Base
def authenticate_user!(*args)
if redirect_to_home_page_url?
- redirect_to current_application_settings.home_page_url and return
+ return redirect_to current_application_settings.home_page_url
end
super(*args)
@@ -131,7 +131,7 @@ class ApplicationController < ActionController::Base
headers['X-UA-Compatible'] = 'IE=edge'
headers['X-Content-Type-Options'] = 'nosniff'
# Enabling HSTS for non-standard ports would send clients to the wrong port
- if Gitlab.config.gitlab.https and Gitlab.config.gitlab.port == 443
+ if Gitlab.config.gitlab.https && Gitlab.config.gitlab.port == 443
headers['Strict-Transport-Security'] = 'max-age=31536000'
end
end
@@ -152,7 +152,7 @@ class ApplicationController < ActionController::Base
def check_password_expiration
if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user?
- redirect_to new_profile_password_path and return
+ return redirect_to new_profile_password_path
end
end
@@ -218,7 +218,7 @@ class ApplicationController < ActionController::Base
def require_email
if current_user && current_user.temp_oauth_email? && session[:impersonator_id].nil?
- redirect_to profile_path, notice: 'Please complete your profile with email address' and return
+ return redirect_to profile_path, notice: 'Please complete your profile with email address'
end
end
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index 4e61b0886d8..5848ca62777 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -1,4 +1,6 @@
class Dashboard::TodosController < Dashboard::ApplicationController
+ include ActionView::Helpers::NumberHelper
+
before_action :find_todos, only: [:index, :destroy_all]
def index
@@ -35,6 +37,11 @@ class Dashboard::TodosController < Dashboard::ApplicationController
render json: todos_counts
end
+ # Used in TodosHelper also
+ def self.todos_count_format(count)
+ count >= 100 ? '99+' : count
+ end
+
private
def find_todos
@@ -43,8 +50,8 @@ class Dashboard::TodosController < Dashboard::ApplicationController
def todos_counts
{
- count: current_user.todos_pending_count,
- done_count: current_user.todos_done_count
+ count: number_with_delimiter(current_user.todos_pending_count),
+ done_count: number_with_delimiter(current_user.todos_done_count)
}
end
end
diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb
index 99b10b2f9b3..5df6bd34185 100644
--- a/app/controllers/import/fogbugz_controller.rb
+++ b/app/controllers/import/fogbugz_controller.rb
@@ -29,7 +29,7 @@ class Import::FogbugzController < Import::BaseController
unless user_map.is_a?(Hash) && user_map.all? { |k, v| !v[:name].blank? }
flash.now[:alert] = 'All users must have a name.'
- render 'new_user_map' and return
+ return render 'new_user_map'
end
session[:fogbugz_user_map] = user_map
diff --git a/app/controllers/import/google_code_controller.rb b/app/controllers/import/google_code_controller.rb
index 8d0de158f98..7d7f13ce5d5 100644
--- a/app/controllers/import/google_code_controller.rb
+++ b/app/controllers/import/google_code_controller.rb
@@ -44,13 +44,13 @@ class Import::GoogleCodeController < Import::BaseController
rescue
flash.now[:alert] = "The entered user map is not a valid JSON user map."
- render "new_user_map" and return
+ return render "new_user_map"
end
unless user_map.is_a?(Hash) && user_map.all? { |k, v| k.is_a?(String) && v.is_a?(String) }
flash.now[:alert] = "The entered user map is not a valid JSON user map."
- render "new_user_map" and return
+ return render "new_user_map"
end
# This is the default, so let's not save it into the database.
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 58964a0e65d..7625187c7be 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -42,9 +42,7 @@ class InvitesController < ApplicationController
@token = params[:id]
@member = Member.find_by_invite_token(@token)
- unless @member
- render_404 and return
- end
+ return render_404 unless @member
@member
end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 3ab7e6e0658..58d50ad647b 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -122,7 +122,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
else
error_message = @user.errors.full_messages.to_sentence
- redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
+ return redirect_to omniauth_error_path(oauth['provider'], error: error_message)
end
end
diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index 830e0b9591b..c8663a3c38e 100644
--- a/app/controllers/profiles/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -45,13 +45,13 @@ class Profiles::KeysController < Profiles::ApplicationController
if user.present?
render text: user.all_ssh_keys.join("\n"), content_type: "text/plain"
else
- render_404 and return
+ return render_404
end
rescue => e
render text: e.message
end
else
- render_404 and return
+ return render_404
end
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index a1db856dcfb..39ba815cfca 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -95,7 +95,7 @@ class Projects::BlobController < Projects::ApplicationController
else
if tree = @repository.tree(@commit.id, @path)
if tree.entries.any?
- redirect_to namespace_project_tree_path(@project.namespace, @project, File.join(@ref, @path)) and return
+ return redirect_to namespace_project_tree_path(@project.namespace, @project, File.join(@ref, @path))
end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 2bf3542d089..75971faa93e 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -50,6 +50,17 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@labels = LabelsFinder.new(current_user, labels_params).execute
end
+ @users = []
+ if params[:assignee_id].present?
+ assignee = User.find_by_id(params[:assignee_id])
+ @users.push(assignee) if assignee
+ end
+
+ if params[:author_id].present?
+ author = User.find_by_id(params[:author_id])
+ @users.push(author) if author
+ end
+
respond_to do |format|
format.html
format.json do
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index cb3ed0f6f9c..4f094146348 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -15,10 +15,10 @@ class Projects::TreeController < Projects::ApplicationController
if tree.entries.empty?
if @repository.blob_at(@commit.id, @path)
- redirect_to(
+ return redirect_to(
namespace_project_blob_path(@project.namespace, @project,
File.join(@ref, @path))
- ) and return
+ )
elsif @path.present?
return render_404
end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index a632c36cfb8..2d26718873f 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -23,7 +23,7 @@ class SnippetsController < ApplicationController
if params[:username].present?
@user = User.find_by(username: params[:username])
- render_404 and return unless @user
+ return render_404 unless @user
@snippets = SnippetsFinder.new.execute(current_user, {
filter: :by_user,
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index 0676767d910..dc5ae8edbb2 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -1,4 +1,8 @@
module NamespacesHelper
+ def namespace_id_from(params)
+ params.dig(:project, :namespace_id) || params[:namespace_id]
+ end
+
def namespaces_options(selected = :current_user, display_path: false, extra_group: nil)
groups = current_user.owned_groups + current_user.masters_groups
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 845f1a0e840..c52afd6db1c 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -3,6 +3,10 @@ module TodosHelper
@todos_pending_count ||= current_user.todos_pending_count
end
+ def todos_count_format(count)
+ count > 99 ? '99+' : count
+ end
+
def todos_done_count
@todos_done_count ||= current_user.todos_done_count
end
diff --git a/app/models/project.rb b/app/models/project.rb
index fc5b1a66910..411299eef63 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -552,7 +552,7 @@ class Project < ActiveRecord::Base
end
def check_limit
- unless creator.can_create_project? or namespace.kind == 'group'
+ unless creator.can_create_project? || namespace.kind == 'group'
projects_limit = creator.projects_limit
if projects_limit == 0
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index 942ec9371e5..1ad9efac196 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -52,7 +52,7 @@ class DroneCiService < CiService
response = HTTParty.get(commit_status_path(sha, ref), verify: enable_ssl_verification)
status =
- if response.code == 200 and response['status']
+ if response.code == 200 && response['status']
case response['status']
when 'killed'
:canceled
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index 5d93064f9b3..5d6862d9faa 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -96,7 +96,7 @@ class IrkerService < Service
rescue URI::InvalidURIError
end
- unless uri.present? and default_irc_uri.nil?
+ unless uri.present? && default_irc_uri.nil?
begin
new_recipient = URI.join(default_irc_uri, '/', recipient).to_s
uri = consider_uri(URI.parse(new_recipient))
diff --git a/app/services/projects/upload_service.rb b/app/services/projects/upload_service.rb
index 012e82a7704..be34d4fa9b8 100644
--- a/app/services/projects/upload_service.rb
+++ b/app/services/projects/upload_service.rb
@@ -5,7 +5,7 @@ module Projects
end
def execute
- return nil unless @file and @file.size <= max_attachment_size
+ return nil unless @file && @file.size <= max_attachment_size
uploader = FileUploader.new(@project)
uploader.store!(@file)
diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb
index 2d11305be13..bc0653cb634 100644
--- a/app/services/users/destroy_service.rb
+++ b/app/services/users/destroy_service.rb
@@ -7,6 +7,10 @@ module Users
end
def execute(user, options = {})
+ unless current_user.admin? || current_user == user
+ raise Gitlab::Access::AccessDeniedError, "#{current_user} tried to destroy user #{user}!"
+ end
+
if !options[:delete_solo_owned_groups] && user.solo_owned_groups.present?
user.errors[:base] << 'You must transfer ownership or delete groups before you can remove user'
return user
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index dc2d924f212..a3993d5ef16 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -1,28 +1,33 @@
%li{ class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo), data: { url: todo_target_path(todo) } }
- = author_avatar(todo, size: 40)
+ .todo-avatar
+ = author_avatar(todo, size: 40)
.todo-item.todo-block
.todo-title.title
- unless todo.build_failed? || todo.unmergeable?
= todo_target_state_pill(todo)
- %span.author-name
+ .title-item.author-name
- if todo.author
= link_to_author(todo)
- else
(removed)
- %span.action-name
+ .title-item.action-name
= todo_action_name(todo)
- %span.todo-label
+ .title-item.todo-label
- if todo.target
= todo_target_link(todo)
- else
(removed)
- &middot; #{time_ago_with_tooltip(todo.created_at)}
- = todo_due_date(todo)
+ .title-item
+ &middot;
+
+ .title-item
+ #{time_ago_with_tooltip(todo.created_at)}
+ = todo_due_date(todo)
.todo-body
.todo-note
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 60b9b8bdbc4..f8986893776 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -35,7 +35,7 @@
= link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('bell fw')
%span.badge.todos-pending-count{ class: ("hidden" if todos_pending_count == 0) }
- = todos_pending_count
+ = todos_count_format(todos_pending_count)
- if Gitlab::Sherlock.enabled?
%li
= link_to sherlock_transactions_path, title: 'Sherlock Transactions',
diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml
index 6abff6aaf95..c2b32a22170 100644
--- a/app/views/projects/commit/_pipeline.html.haml
+++ b/app/views/projects/commit/_pipeline.html.haml
@@ -3,7 +3,7 @@
.pull-right
- if can?(current_user, :update_pipeline, pipeline.project)
- if pipeline.builds.latest.failed.any?(&:retryable?)
- = link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post
+ = link_to "Retry", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'js-retry-button btn btn-grouped btn-primary', method: :post
- if pipeline.builds.running_or_pending.any?
= link_to "Cancel running", cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 144b3a9c8c8..83e6c026ba7 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -5,18 +5,19 @@
= render "projects/issues/head"
= render 'projects/last_push'
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('filtered_search')
+
%div{ class: container_class }
.top-area
= render 'shared/issuable/nav', type: :merge_requests
.nav-controls
- = render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project)
-
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- if merge_project
= link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New Merge Request" do
New Merge Request
- = render 'shared/issuable/filter', type: :merge_requests
+ = render 'shared/issuable/search_bar', type: :merge_requests
.merge-requests-holder
= render 'merge_requests'
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index a07885537b9..2a98bba05ee 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -22,7 +22,7 @@
- if current_user.can_select_namespace?
.input-group-addon
= root_url
- = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user, display_path: true), {}, {class: 'select2 js-select-namespace', tabindex: 1}
+ = f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true), {}, {class: 'select2 js-select-namespace', tabindex: 1}
- else
.input-group-addon.static-namespace
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index e0c972aa2fb..0605af4fcd3 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -8,7 +8,7 @@
.header-action-buttons
- if can?(current_user, :update_pipeline, @pipeline.project)
- if @pipeline.retryable?
- = link_to "Retry failed", retry_namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'btn btn-inverted-secondary', method: :post
+ = link_to "Retry", retry_namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'js-retry-button btn btn-inverted-secondary', method: :post
- if @pipeline.cancelable?
= link_to "Cancel running", cancel_namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
diff --git a/app/views/projects/pipelines_settings/_show.html.haml b/app/views/projects/pipelines_settings/_show.html.haml
index 8024fb8979d..132f6372e40 100644
--- a/app/views/projects/pipelines_settings/_show.html.haml
+++ b/app/views/projects/pipelines_settings/_show.html.haml
@@ -60,7 +60,7 @@
= f.label :build_coverage_regex, "Test coverage parsing", class: 'label-light'
.input-group
%span.input-group-addon /
- = f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered'
+ = f.text_field :build_coverage_regex, class: 'form-control', placeholder: 'Regular expression'
%span.input-group-addon /
%p.help-block
A regular expression that will be used to find the test coverage
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index 7fe2bce3e7c..22004ecacbc 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -11,7 +11,7 @@
.results.prepend-top-10
- if @scope == 'commits'
- %ul.list-unstyled
+ %ul.content-list.commit-list.table-list.table-wide
= render partial: "search/results/commit", collection: @search_objects
- else
.search-results
diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index e977c1f1698..f84be600df8 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -12,41 +12,34 @@
%span.light= time_ago_with_tooltip(snippet.created_at)
%h4.snippet-title
- snippet_path = reliable_snippet_path(snippet)
- = link_to snippet_path do
- .file-holder
- .js-file-title.file-title
+ .file-holder
+ .js-file-title.file-title
+ = link_to snippet_path do
%i.fa.fa-file
%strong= snippet.file_name
- - if markup?(snippet.file_name)
- .file-content.wiki
+ - if markup?(snippet.file_name)
+ .file-content.wiki
+ - snippet_chunks.each do |chunk|
+ - unless chunk[:data].empty?
+ = render_markup(snippet.file_name, chunk[:data])
+ - else
+ .file-content.code
+ .nothing-here-block Empty file
+ - else
+ .file-content.code.js-syntax-highlight
+ .line-numbers
- snippet_chunks.each do |chunk|
- unless chunk[:data].empty?
- = render_markup(snippet.file_name, chunk[:data])
+ - Gitlab::Git::Util.count_lines(chunk[:data]).times do |index|
+ - offset = defined?(chunk[:start_line]) ? chunk[:start_line] : 1
+ - i = index + offset
+ = link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}", class: "diff-line-num" do
+ %i.fa.fa-link
+ = i
+ .blob-content
+ - snippet_chunks.each do |chunk|
+ - unless chunk[:data].empty?
+ = highlight(snippet.file_name, chunk[:data], repository: nil, plain: snippet.no_highlighting?)
- else
.file-content.code
.nothing-here-block Empty file
- - else
- .file-content.code.js-syntax-highlight
- .line-numbers
- - snippet_chunks.each do |chunk|
- - unless chunk[:data].empty?
- - Gitlab::Git::Util.count_lines(chunk[:data]).times do |index|
- - offset = defined?(chunk[:start_line]) ? chunk[:start_line] : 1
- - i = index + offset
- = link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}", class: "diff-line-num" do
- %i.fa.fa-link
- = i
- - unless snippet == snippet_chunks.last
- %a.diff-line-num
- = "."
- %pre.code
- %code
- - snippet_chunks.each do |chunk|
- - unless chunk[:data].empty?
- = chunk[:data]
- - unless chunk == snippet_chunks.last
- %a
- = "..."
- - else
- .file-content.code
- .nothing-here-block Empty file
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 6e417aa2251..8e04b50bb8a 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -32,7 +32,7 @@
{{hint}}
%span.js-filter-tag.dropdown-light-content
{{tag}}
- #js-dropdown-author.dropdown-menu
+ #js-dropdown-author.dropdown-menu{ data: { icon: 'pencil', hint: 'author', tag: '@author' } }
%ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true }
%li.filter-dropdown-item
%button.btn.btn-link.dropdown-user
@@ -42,7 +42,7 @@
{{name}}
%span.dropdown-light-content
@{{username}}
- #js-dropdown-assignee.dropdown-menu
+ #js-dropdown-assignee.dropdown-menu{ data: { icon: 'user', hint: 'assignee', tag: '@assignee' } }
%ul{ 'data-dropdown' => true }
%li.filter-dropdown-item{ 'data-value' => 'none' }
%button.btn.btn-link
@@ -57,7 +57,7 @@
{{name}}
%span.dropdown-light-content
@{{username}}
- #js-dropdown-milestone.dropdown-menu{ 'data-dropdown' => true }
+ #js-dropdown-milestone.dropdown-menu{ data: { icon: 'clock-o', hint: 'milestone', tag: '%milestone' } }
%ul{ 'data-dropdown' => true }
%li.filter-dropdown-item{ 'data-value' => 'none' }
%button.btn.btn-link
@@ -70,7 +70,7 @@
%li.filter-dropdown-item
%button.btn.btn-link.js-data-value
{{title}}
- #js-dropdown-label.dropdown-menu{ 'data-dropdown' => true }
+ #js-dropdown-label.dropdown-menu{ data: { icon: 'tag', hint: 'label', tag: '~label' } }
%ul{ 'data-dropdown' => true }
%li.filter-dropdown-item{ 'data-value' => 'none' }
%button.btn.btn-link
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index 239387fc9fa..8e721c9c8dd 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -61,7 +61,7 @@
= dropdown_title("Change permissions")
.dropdown-content
%ul
- - Gitlab::Access.options.each do |role, role_id|
+ - member.class.access_level_roles.each do |role, role_id|
%li
= link_to role, "javascript:void(0)",
class: ("is-active" if member.access_level == role_id),
diff --git a/app/workers/delete_user_worker.rb b/app/workers/delete_user_worker.rb
index 5483bbb210b..3340a7be4fe 100644
--- a/app/workers/delete_user_worker.rb
+++ b/app/workers/delete_user_worker.rb
@@ -7,5 +7,7 @@ class DeleteUserWorker
current_user = User.find(current_user_id)
Users::DestroyService.new(current_user).execute(delete_user, options.symbolize_keys)
+ rescue Gitlab::Access::AccessDeniedError => e
+ Rails.logger.warn("User could not be destroyed: #{e}")
end
end
diff --git a/changelogs/unreleased/1363-redo-mailroom-support.yml b/changelogs/unreleased/1363-redo-mailroom-support.yml
new file mode 100644
index 00000000000..8ed206f4fdb
--- /dev/null
+++ b/changelogs/unreleased/1363-redo-mailroom-support.yml
@@ -0,0 +1,4 @@
+---
+title: Redo internals of Incoming Mail Support
+merge_request: 9385
+author:
diff --git a/changelogs/unreleased/26206-fix-download-dropdown.yml b/changelogs/unreleased/26206-fix-download-dropdown.yml
new file mode 100644
index 00000000000..a6c101375bb
--- /dev/null
+++ b/changelogs/unreleased/26206-fix-download-dropdown.yml
@@ -0,0 +1,4 @@
+---
+title: Set dropdown height fixed to 250px and make it scrollable
+merge_request: 9063
+author:
diff --git a/changelogs/unreleased/26703-todos-count.yml b/changelogs/unreleased/26703-todos-count.yml
new file mode 100644
index 00000000000..24fd0c406e2
--- /dev/null
+++ b/changelogs/unreleased/26703-todos-count.yml
@@ -0,0 +1,4 @@
+---
+title: show 99+ for large count in todos notification bell
+merge_request: 9171
+author: mhasbini
diff --git a/changelogs/unreleased/26881-backup-fails-if-data-changes.yml b/changelogs/unreleased/26881-backup-fails-if-data-changes.yml
new file mode 100644
index 00000000000..00bf105560b
--- /dev/null
+++ b/changelogs/unreleased/26881-backup-fails-if-data-changes.yml
@@ -0,0 +1,4 @@
+---
+title: Add `copy` backup strategy to combat file changed errors
+merge_request: 8728
+author:
diff --git a/changelogs/unreleased/27287-label-dropdown-error-messages.yml b/changelogs/unreleased/27287-label-dropdown-error-messages.yml
new file mode 100644
index 00000000000..dfd4102c324
--- /dev/null
+++ b/changelogs/unreleased/27287-label-dropdown-error-messages.yml
@@ -0,0 +1,4 @@
+---
+title: Fix displaying error messages for create label dropdown
+merge_request: 9058
+author: Tom Koole
diff --git a/changelogs/unreleased/28186-long-group-names-overflow-out-of-todos-view.yml b/changelogs/unreleased/28186-long-group-names-overflow-out-of-todos-view.yml
new file mode 100644
index 00000000000..3bcf0e06d08
--- /dev/null
+++ b/changelogs/unreleased/28186-long-group-names-overflow-out-of-todos-view.yml
@@ -0,0 +1,4 @@
+---
+title: Truncate long Todo titles for non-mobile screens
+merge_request: 9311
+author:
diff --git a/changelogs/unreleased/28357-colon-search.yml b/changelogs/unreleased/28357-colon-search.yml
new file mode 100644
index 00000000000..4bbb0dc12b2
--- /dev/null
+++ b/changelogs/unreleased/28357-colon-search.yml
@@ -0,0 +1,4 @@
+---
+title: Allow searching issues for strings containing colons
+merge_request:
+author:
diff --git a/changelogs/unreleased/28389-ux-problem-with-pipeline-coverage-placeholder.yml b/changelogs/unreleased/28389-ux-problem-with-pipeline-coverage-placeholder.yml
new file mode 100644
index 00000000000..ed357d86fe3
--- /dev/null
+++ b/changelogs/unreleased/28389-ux-problem-with-pipeline-coverage-placeholder.yml
@@ -0,0 +1,4 @@
+---
+title: Changed coverage reg expression placeholder text to be more like a placeholder
+merge_request:
+author:
diff --git a/changelogs/unreleased/28462-fix-delimiter-removes-issue-in-todo-counter.yml b/changelogs/unreleased/28462-fix-delimiter-removes-issue-in-todo-counter.yml
new file mode 100644
index 00000000000..80995d75c23
--- /dev/null
+++ b/changelogs/unreleased/28462-fix-delimiter-removes-issue-in-todo-counter.yml
@@ -0,0 +1,4 @@
+---
+title: Fixes delimiter removes when todo marked as done
+merge_request: 9435
+author:
diff --git a/changelogs/unreleased/add-filtered-search-to-mr.yml b/changelogs/unreleased/add-filtered-search-to-mr.yml
new file mode 100644
index 00000000000..e3577e2aec7
--- /dev/null
+++ b/changelogs/unreleased/add-filtered-search-to-mr.yml
@@ -0,0 +1,4 @@
+---
+title: Add filtered search to MR page
+merge_request:
+author:
diff --git a/changelogs/unreleased/api-todos-restful.yml b/changelogs/unreleased/api-todos-restful.yml
new file mode 100644
index 00000000000..dba1350a495
--- /dev/null
+++ b/changelogs/unreleased/api-todos-restful.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: Use POST requests to mark todos as done'
+merge_request: 9410
+author: Robert Schilling
diff --git a/changelogs/unreleased/commit-search-ui-fix.yml b/changelogs/unreleased/commit-search-ui-fix.yml
new file mode 100644
index 00000000000..4a5c2cf6090
--- /dev/null
+++ b/changelogs/unreleased/commit-search-ui-fix.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed commit search UI
+merge_request:
+author:
diff --git a/changelogs/unreleased/group-memebrs-owner-level.yml b/changelogs/unreleased/group-memebrs-owner-level.yml
new file mode 100644
index 00000000000..ba77f38eb6d
--- /dev/null
+++ b/changelogs/unreleased/group-memebrs-owner-level.yml
@@ -0,0 +1,4 @@
+---
+title: Added option to update to owner for group members
+merge_request:
+author:
diff --git a/changelogs/unreleased/rename-retry-failed-pipeline-to-retry.yml b/changelogs/unreleased/rename-retry-failed-pipeline-to-retry.yml
new file mode 100644
index 00000000000..b813127b1e6
--- /dev/null
+++ b/changelogs/unreleased/rename-retry-failed-pipeline-to-retry.yml
@@ -0,0 +1,4 @@
+---
+title: Rename retry failed button on pipeline page to just retry
+merge_request:
+author:
diff --git a/changelogs/unreleased/sh-delete-user-permission-check.yml b/changelogs/unreleased/sh-delete-user-permission-check.yml
new file mode 100644
index 00000000000..c0e79aae2a8
--- /dev/null
+++ b/changelogs/unreleased/sh-delete-user-permission-check.yml
@@ -0,0 +1,4 @@
+---
+title: Add user deletion permission check in `Users::DestroyService`
+merge_request:
+author:
diff --git a/changelogs/unreleased/snippets-search.yml b/changelogs/unreleased/snippets-search.yml
new file mode 100644
index 00000000000..00cf34f4a48
--- /dev/null
+++ b/changelogs/unreleased/snippets-search.yml
@@ -0,0 +1,4 @@
+---
+title: Fix snippets search result spacing
+merge_request:
+author:
diff --git a/config/initializers/mysql_ignore_postgresql_options.rb b/config/initializers/mysql_ignore_postgresql_options.rb
index 835f3ec5574..9a569be7674 100644
--- a/config/initializers/mysql_ignore_postgresql_options.rb
+++ b/config/initializers/mysql_ignore_postgresql_options.rb
@@ -31,7 +31,7 @@ if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
end
def add_index_options(table_name, column_name, options = {})
- if options[:using] and options[:using] == :gin
+ if options[:using] && options[:using] == :gin
options = options.dup
options.delete(:using)
end
diff --git a/config/initializers/rack_lineprof.rb b/config/initializers/rack_lineprof.rb
index 22e77a32c61..f7172fce9bc 100644
--- a/config/initializers/rack_lineprof.rb
+++ b/config/initializers/rack_lineprof.rb
@@ -1,7 +1,7 @@
# The default colors of rack-lineprof can be very hard to look at in terminals
# with darker backgrounds. This patch tweaks the colors a bit so the output is
# actually readable.
-if Rails.env.development? and RUBY_ENGINE == 'ruby' and ENV['ENABLE_LINEPROF']
+if Rails.env.development? && RUBY_ENGINE == 'ruby' && ENV['ENABLE_LINEPROF']
Rails.application.config.middleware.use(Rack::Lineprof)
module Rack
diff --git a/config/mail_room.yml b/config/mail_room.yml
index 774c5350a45..88d93d4bc6b 100644
--- a/config/mail_room.yml
+++ b/config/mail_room.yml
@@ -1,9 +1,6 @@
-# If you change this file in a Merge Request, please also create
-# a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
-#
:mailboxes:
<%
- require_relative "lib/gitlab/mail_room" unless defined?(Gitlab::MailRoom)
+ require_relative "../lib/gitlab/mail_room" unless defined?(Gitlab::MailRoom)
config = Gitlab::MailRoom.config
if Gitlab::MailRoom.enabled?
diff --git a/config/routes/sidekiq.rb b/config/routes/sidekiq.rb
index d3e6bc4c292..0fa23f2b3d0 100644
--- a/config/routes/sidekiq.rb
+++ b/config/routes/sidekiq.rb
@@ -1,4 +1,4 @@
-constraint = lambda { |request| request.env['warden'].authenticate? and request.env['warden'].user.admin? }
+constraint = lambda { |request| request.env['warden'].authenticate? && request.env['warden'].user.admin? }
constraints constraint do
mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq
end
diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md
index 82351ae688f..f3c9827f742 100644
--- a/doc/api/pipelines.md
+++ b/doc/api/pipelines.md
@@ -163,7 +163,7 @@ Example of response
}
```
-## Retry failed builds in a pipeline
+## Retry builds in a pipeline
> [Introduced][ce-5837] in GitLab 8.11
diff --git a/doc/api/todos.md b/doc/api/todos.md
index a5e81801024..a2fbbc7e1f8 100644
--- a/doc/api/todos.md
+++ b/doc/api/todos.md
@@ -184,7 +184,7 @@ Marks a single pending todo given by its ID for the current user as done. The
todo marked as done is returned in the response.
```
-DELETE /todos/:id
+POST /todos/:id/mark_as_done
```
Parameters:
@@ -194,7 +194,7 @@ Parameters:
| `id` | integer | yes | The ID of a todo |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos/130
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos/130/mark_as_done
```
Example Response:
@@ -277,20 +277,15 @@ Example Response:
## Mark all todos as done
-Marks all pending todos for the current user as done. It returns the number of marked todos.
+Marks all pending todos for the current user as done. It returns the HTTP status code `204` with an empty response.
```
-DELETE /todos
+POST /todos/mark_as_done
```
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos/donmark_as_donee
```
-Example Response:
-
-```json
-3
-```
[ce-3188]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3188
diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md
index 3f58c098b43..1af124c56b1 100644
--- a/doc/api/v3_to_v4.md
+++ b/doc/api/v3_to_v4.md
@@ -24,9 +24,9 @@ changes are in V4:
- `/gitlab_ci_ymls/:key`
- `/dockerfiles/:key`
- Moved `/projects/fork/:id` to `/projects/:id/fork`
+- Moved `DELETE /todos` to `POST /todos/mark_as_done` and `DELETE /todos/:todo_id` to `POST /todos/:todo_id/mark_as_done`
- Endpoints `/projects/owned`, `/projects/visible`, `/projects/starred` & `/projects/all` are consolidated into `/projects` using query parameters
- Return pagination headers for all endpoints that return an array
- Removed `DELETE projects/:id/deploy_keys/:key_id/disable`. Use `DELETE projects/:id/deploy_keys/:key_id` instead
- Moved `PUT /users/:id/(block|unblock)` to `POST /users/:id/(block|unblock)`
- Labels filter on `projects/:id/issues` and `/issues` now matches only issues containing all labels (i.e.: Logical AND, not OR)
-
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index 9dee61bfa1f..00787323b6b 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -39,13 +39,15 @@ accessible during the build process.
## What is an image
-The `image` keyword is the name of the docker image that is present in the
-local Docker Engine (list all images with `docker images`) or any image that
-can be found at [Docker Hub][hub]. For more information about images and Docker
-Hub please read the [Docker Fundamentals][] documentation.
+The `image` keyword is the name of the docker image the docker executor
+will run to perform the CI tasks.
-In short, with `image` we refer to the docker image, which will be used to
-create a container on which your job will run.
+By default the executor will only pull images from [Docker Hub][hub],
+but this can be configured in the `gitlab-runner/config.toml` by setting
+the [docker pull policy][] to allow using local images.
+
+For more information about images and Docker Hub please read
+the [Docker Fundamentals][] documentation.
## What is a service
@@ -271,6 +273,7 @@ containers as well as all volumes (`-v`) that were created with the container
creation.
[Docker Fundamentals]: https://docs.docker.com/engine/understanding-docker/
+[docker pull policy]: https://docs.gitlab.com/runner/executors/docker.html#how-pull-policies-work
[hub]: https://hub.docker.com/
[linking-containers]: https://docs.docker.com/engine/userguide/networking/default_network/dockerlinks/
[tutum/wordpress]: https://hub.docker.com/r/tutum/wordpress/
diff --git a/doc/development/licensing.md b/doc/development/licensing.md
index 5d177eb26ee..1f115059fb8 100644
--- a/doc/development/licensing.md
+++ b/doc/development/licensing.md
@@ -64,6 +64,10 @@ Libraries with the following licenses are unacceptable for use:
- [GNU AGPLv3][AGPLv3]: AGPL-licensed libraries cannot be linked to from non-GPL projects.
- [Open Software License (OSL)][OSL]: is a copyleft license. In addition, the FSF [recommend against its use][OSL-GNU].
+## Requesting Approval for Licenses
+
+Libraries that are not listed in the [Acceptable Licenses][Acceptable-Licenses] or [Unacceptable Licenses][Unacceptable-Licenses] list can be submitted to the legal team for review. Please create an issue in the [Organization Repository][Org-Repo] and cc `@gl-legal`. After a decision has been made, the original requestor is responsible for updating this document.
+
## Notes
Decisions regarding the GNU GPL licenses are based on information provided by [The GNU Project][GNU-GPL-FAQ], as well as [the Open Source Initiative][OSI-GPL], which both state that linking GPL libraries makes the program itself GPL.
@@ -96,3 +100,6 @@ Gems which are included only in the "development" or "test" groups by Bundler ar
[OSI-GPL]: https://opensource.org/faq#linking-proprietary-code
[OSL]: https://opensource.org/licenses/OSL-3.0
[OSL-GNU]: https://www.gnu.org/licenses/license-list.en.html#OSL
+[Org-Repo]: https://gitlab.com/gitlab-com/organization
+[Acceptable-Licenses]: #acceptable-licenses
+[Unacceptable-Licenses]: #unacceptable-licenses
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index b4e13f5812a..a5b8cd6455c 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -84,6 +84,28 @@ Deleting tmp directories...[DONE]
Deleting old backups... [SKIPPING]
```
+## Backup Strategy Option
+
+> **Note:** Introduced as an option in 8.17
+
+The default backup strategy is to essentially stream data from the respective
+data locations to the backup using the Linux command `tar` and `gzip`. This works
+fine in most cases, but can cause problems when data is rapidly changing.
+
+When data changes while `tar` is reading it, the error `file changed as we read
+it` may occur, and will cause the backup process to fail. To combat this, 8.17
+introduces a new backup strategy called `copy`. The strategy copies data files
+to a temporary location before calling `tar` and `gzip`, avoiding the error.
+
+A side-effect is that the backup process with take up to an additional 1X disk
+space. The process does its best to clean up the temporary files at each stage
+so the problem doesn't compound, but it could be a considerable change for large
+installations. This is why the `copy` strategy is not the default in 8.17.
+
+To use the `copy` strategy instead of the default streaming strategy, specify
+`STRATEGY=copy` in the Rake task command. For example,
+`sudo gitlab-rake gitlab:backup:create STRATEGY=copy`.
+
## Exclude specific directories from the backup
You can choose what should be backed up by adding the environment variable `SKIP`.
diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md
index 6eb457fde3f..4b0fba842e9 100644
--- a/doc/workflow/todos.md
+++ b/doc/workflow/todos.md
@@ -16,7 +16,8 @@ in a simple dashboard.
You can quickly access the Todos dashboard using the bell icon next to the
search bar in the upper right corner. The number in blue is the number of Todos
-you still have open.
+you still have open if the count is < 100, else it's 99+. The exact number
+will still be shown in the body of the _To do_ tab.
![Todos icon](img/todos_icon.png)
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index 5aa592e9067..bcde497553b 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -294,13 +294,6 @@ Feature: Project Merge Requests
Then I should see the Markdown write tab
@javascript
- Scenario: I search merge request
- Given I click link "All"
- When I fill in merge request search with "Fe"
- Then I should see "Feature NS-03" in merge requests
- And I should not see "Bug NS-04" in merge requests
-
- @javascript
Scenario: I can unsubscribe from merge request
Given I visit merge request page "Bug NS-04"
Then I should see that I am subscribed
diff --git a/features/project/merge_requests/revert.feature b/features/project/merge_requests/revert.feature
index d767b088883..ec6666f227f 100644
--- a/features/project/merge_requests/revert.feature
+++ b/features/project/merge_requests/revert.feature
@@ -5,6 +5,7 @@ Feature: Revert Merge Requests
And I am signed in as a developer of the project
And I am on the Merge Request detail page
And I click on Accept Merge Request
+ And I am on the Merge Request detail page
@javascript
Scenario: I revert a merge request
diff --git a/lib/api/api.rb b/lib/api/api.rb
index dbb7271ccbd..e729c07f8c3 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -19,6 +19,7 @@ module API
mount ::API::V3::Repositories
mount ::API::V3::SystemHooks
mount ::API::V3::Tags
+ mount ::API::V3::Todos
mount ::API::V3::Templates
mount ::API::V3::Users
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 32692f19fcd..a1db2099693 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -153,7 +153,7 @@ module API
params_hash = custom_params || params
attrs = {}
keys.each do |key|
- if params_hash[key].present? or (params_hash.has_key?(key) and params_hash[key] == false)
+ if params_hash[key].present? || (params_hash.has_key?(key) && params_hash[key] == false)
attrs[key] = params_hash[key]
end
end
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index b634b1d0222..f59f7959173 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -23,7 +23,7 @@ module API
pipelines = PipelinesFinder.new(user_project).execute(scope: params[:scope])
present paginate(pipelines), with: Entities::Pipeline
end
-
+
desc 'Create a new pipeline' do
detail 'This feature was introduced in GitLab 8.14'
success Entities::Pipeline
@@ -58,7 +58,7 @@ module API
present pipeline, with: Entities::Pipeline
end
- desc 'Retry failed builds in the pipeline' do
+ desc 'Retry builds in the pipeline' do
detail 'This feature was introduced in GitLab 8.11.'
success Entities::Pipeline
end
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index 9bd077263a7..0b9650b296c 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -58,7 +58,7 @@ module API
params do
requires :id, type: Integer, desc: 'The ID of the todo being marked as done'
end
- delete ':id' do
+ post ':id/mark_as_done' do
todo = current_user.todos.find(params[:id])
TodoService.new.mark_todos_as_done([todo], current_user)
@@ -66,9 +66,11 @@ module API
end
desc 'Mark all todos as done'
- delete do
+ post '/mark_as_done' do
todos = find_todos
TodoService.new.mark_todos_as_done(todos, current_user)
+
+ no_content!
end
end
end
diff --git a/lib/api/v3/todos.rb b/lib/api/v3/todos.rb
new file mode 100644
index 00000000000..4f9b5fe72a6
--- /dev/null
+++ b/lib/api/v3/todos.rb
@@ -0,0 +1,28 @@
+module API
+ module V3
+ class Todos < Grape::API
+ before { authenticate! }
+
+ resource :todos do
+ desc 'Mark a todo as done' do
+ success ::API::Entities::Todo
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the todo being marked as done'
+ end
+ delete ':id' do
+ todo = current_user.todos.find(params[:id])
+ TodoService.new.mark_todos_as_done([todo], current_user)
+
+ present todo.reload, with: ::API::Entities::Todo, current_user: current_user
+ end
+
+ desc 'Mark all todos as done'
+ delete do
+ todos = TodosFinder.new(current_user, params).execute
+ TodoService.new.mark_todos_as_done(todos, current_user)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/backup/files.rb b/lib/backup/files.rb
index cedbb289f6a..247c32c1c0a 100644
--- a/lib/backup/files.rb
+++ b/lib/backup/files.rb
@@ -8,6 +8,7 @@ module Backup
@name = name
@app_files_dir = File.realpath(app_files_dir)
@files_parent_dir = File.realpath(File.join(@app_files_dir, '..'))
+ @backup_files_dir = File.join(Gitlab.config.backup.path, File.basename(@app_files_dir) )
@backup_tarball = File.join(Gitlab.config.backup.path, name + '.tar.gz')
end
@@ -15,7 +16,21 @@ module Backup
def dump
FileUtils.mkdir_p(Gitlab.config.backup.path)
FileUtils.rm_f(backup_tarball)
- run_pipeline!([%W(tar -C #{app_files_dir} -cf - .), %W(gzip -c -1)], out: [backup_tarball, 'w', 0600])
+
+ if ENV['STRATEGY'] == 'copy'
+ cmd = %W(cp -a #{app_files_dir} #{Gitlab.config.backup.path})
+ output, status = Gitlab::Popen.popen(cmd)
+
+ unless status.zero?
+ puts output
+ abort 'Backup failed'
+ end
+
+ run_pipeline!([%W(tar -C #{@backup_files_dir} -cf - .), %W(gzip -c -1)], out: [backup_tarball, 'w', 0600])
+ FileUtils.rm_rf(@backup_files_dir)
+ else
+ run_pipeline!([%W(tar -C #{app_files_dir} -cf - .), %W(gzip -c -1)], out: [backup_tarball, 'w', 0600])
+ end
end
def restore
diff --git a/lib/banzai/filter/plantuml_filter.rb b/lib/banzai/filter/plantuml_filter.rb
index e194cf59275..b2537117558 100644
--- a/lib/banzai/filter/plantuml_filter.rb
+++ b/lib/banzai/filter/plantuml_filter.rb
@@ -7,7 +7,7 @@ module Banzai
#
class PlantumlFilter < HTML::Pipeline::Filter
def call
- return doc unless doc.at('pre.plantuml') and settings.plantuml_enabled
+ return doc unless doc.at('pre.plantuml') && settings.plantuml_enabled
plantuml_setup
diff --git a/lib/ci/ansi2html.rb b/lib/ci/ansi2html.rb
index c10d3616f31..158a33f26fe 100644
--- a/lib/ci/ansi2html.rb
+++ b/lib/ci/ansi2html.rb
@@ -126,7 +126,7 @@ module Ci
# We are only interested in color and text style changes - triggered by
# sequences starting with '\e[' and ending with 'm'. Any other control
# sequence gets stripped (including stuff like "delete last line")
- return unless indicator == '[' and terminator == 'm'
+ return unless indicator == '[' && terminator == 'm'
close_open_tags()
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 4800a509b37..fc445ab9483 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -54,7 +54,7 @@ module Gitlab
disable_statement_timeout
- key_name = "fk_#{source}_#{target}_#{column}"
+ key_name = concurrent_foreign_key_name(source, column)
# Using NOT VALID allows us to create a key without immediately
# validating it. This means we keep the ALTER TABLE lock only for a
@@ -74,6 +74,15 @@ module Gitlab
execute("ALTER TABLE #{source} VALIDATE CONSTRAINT #{key_name};")
end
+ # Returns the name for a concurrent foreign key.
+ #
+ # PostgreSQL constraint names have a limit of 63 bytes. The logic used
+ # here is based on Rails' foreign_key_name() method, which unfortunately
+ # is private so we can't rely on it directly.
+ def concurrent_foreign_key_name(table, column)
+ "fk_#{Digest::SHA256.hexdigest("#{table}_#{column}_fk").first(10)}"
+ end
+
# Long-running migrations may take more than the timeout allowed by
# the database. Disable the session's statement timeout to ensure
# migrations don't get killed prematurely. (PostgreSQL only)
diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb
index 287b7a83547..3aaebb3e9c3 100644
--- a/lib/gitlab/metrics/system.rb
+++ b/lib/gitlab/metrics/system.rb
@@ -11,7 +11,7 @@ module Gitlab
mem = 0
match = File.read('/proc/self/status').match(/VmRSS:\s+(\d+)/)
- if match and match[1]
+ if match && match[1]
mem = match[1].to_f * 1024
end
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 35c4194e87c..6102517e730 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -724,8 +724,11 @@ namespace :gitlab do
def check_imap_authentication
print "IMAP server credentials are correct? ... "
- config_path = Rails.root.join('config', 'mail_room.yml')
- config_file = YAML.load(ERB.new(File.read(config_path)).result)
+ config_path = Rails.root.join('config', 'mail_room.yml').to_s
+ erb = ERB.new(File.read(config_path))
+ erb.filename = config_path
+ config_file = YAML.load(erb.result)
+
config = config_file[:mailboxes].first
if config
diff --git a/spec/config/mail_room_spec.rb b/spec/config/mail_room_spec.rb
index 294fae95752..0b8ff006d22 100644
--- a/spec/config/mail_room_spec.rb
+++ b/spec/config/mail_room_spec.rb
@@ -8,7 +8,7 @@ describe 'mail_room.yml' do
context 'when incoming email is disabled' do
before do
- ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = Rails.root.join('spec/fixtures/mail_room_disabled.yml').to_s
+ ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = Rails.root.join('spec/fixtures/config/mail_room_disabled.yml').to_s
Gitlab::MailRoom.reset_config!
end
@@ -26,7 +26,7 @@ describe 'mail_room.yml' do
let(:gitlab_redis) { Gitlab::Redis.new(Rails.env) }
before do
- ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = Rails.root.join('spec/fixtures/mail_room_enabled.yml').to_s
+ ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = Rails.root.join('spec/fixtures/config/mail_room_enabled.yml').to_s
Gitlab::MailRoom.reset_config!
end
diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb
index 0a3ac9f9512..7072bd5e87c 100644
--- a/spec/controllers/dashboard/todos_controller_spec.rb
+++ b/spec/controllers/dashboard/todos_controller_spec.rb
@@ -46,7 +46,7 @@ describe Dashboard::TodosController do
expect(todo.reload).to be_pending
expect(response).to have_http_status(200)
- expect(json_response).to eq({ "count" => 1, "done_count" => 0 })
+ expect(json_response).to eq({ "count" => "1", "done_count" => "0" })
end
end
end
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 8f561c8f90b..324ede798fe 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -153,7 +153,7 @@ describe 'Commits' do
expect(page).to have_content pipeline.git_author_name
expect(page).to have_link('Download artifacts')
expect(page).not_to have_link('Cancel running')
- expect(page).not_to have_link('Retry failed')
+ expect(page).not_to have_link('Retry')
end
end
@@ -172,7 +172,7 @@ describe 'Commits' do
expect(page).to have_content pipeline.git_author_name
expect(page).not_to have_link('Download artifacts')
expect(page).not_to have_link('Cancel running')
- expect(page).not_to have_link('Retry failed')
+ expect(page).not_to have_link('Retry')
end
end
end
diff --git a/spec/features/groups/members/list_spec.rb b/spec/features/groups/members/list_spec.rb
index 109de39b2dd..14c193f7450 100644
--- a/spec/features/groups/members/list_spec.rb
+++ b/spec/features/groups/members/list_spec.rb
@@ -30,6 +30,21 @@ feature 'Groups members list', feature: true do
expect(second_row).to be_blank
end
+ it 'updates user to owner level', :js do
+ group.add_owner(user1)
+ group.add_developer(user2)
+
+ visit group_group_members_path(group)
+
+ page.within(second_row) do
+ click_button('Developer')
+
+ click_link('Owner')
+
+ expect(page).to have_button('Owner')
+ end
+ end
+
def first_row
page.all('ul.content-list > li')[0]
end
diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb
index c6a88e1b7b0..ab3b868fd3a 100644
--- a/spec/features/issues/filtered_search/dropdown_label_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe 'Dropdown label', js: true, feature: true do
+ include FilteredSearchHelpers
+
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:filtered_search) { find('.filtered-search') }
@@ -17,12 +19,6 @@ describe 'Dropdown label', js: true, feature: true do
let!(:long_label) { create(:label, project: project, title: 'this is a very long title this is a very long title this is a very long title this is a very long title this is a very long title') }
end
- def init_label_search
- filtered_search.set('label:')
- # This ensures the dropdown is shown
- expect(find(js_dropdown_label)).not_to have_css('.filter-dropdown-loading')
- end
-
def search_for_label(label)
init_label_search
filtered_search.send_keys(label)
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 64f448a83b7..0420e64d42c 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -1,6 +1,7 @@
require 'rails_helper'
describe 'Filter issues', js: true, feature: true do
+ include FilteredSearchHelpers
include WaitForAjax
let!(:group) { create(:group) }
@@ -17,19 +18,6 @@ describe 'Filter issues', js: true, feature: true do
let!(:multiple_words_label) { create(:label, project: project, title: "Two words") }
let!(:closed_issue) { create(:issue, title: 'bug that is closed', project: project, state: :closed) }
- let(:filtered_search) { find('.filtered-search') }
-
- def input_filtered_search(search_term, submit: true)
- filtered_search.set(search_term)
-
- if submit
- filtered_search.send_keys(:enter)
- end
- end
-
- def expect_filtered_search_input(input)
- expect(find('.filtered-search').value).to eq(input)
- end
def expect_no_issues_list
page.within '.issues-list' do
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 1eb981942ea..7b9d4534ada 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -7,9 +7,9 @@ feature 'Issue Sidebar', feature: true do
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) }
let!(:user) { create(:user)}
+ let!(:label) { create(:label, project: project, title: 'bug') }
before do
- create(:label, project: project, title: 'bug')
login_as(user)
end
@@ -50,16 +50,6 @@ feature 'Issue Sidebar', feature: true do
visit_issue(project, issue)
end
- describe 'when clicking on edit labels', js: true do
- it 'shows dropdown option to create a new label' do
- find('.block.labels .edit-link').click
-
- page.within('.block.labels') do
- expect(page).to have_content 'Create new'
- end
- end
- end
-
context 'sidebar', js: true do
it 'changes size when the screen size is smaller' do
sidebar_selector = 'aside.right-sidebar.right-sidebar-collapsed'
@@ -77,36 +67,53 @@ feature 'Issue Sidebar', feature: true do
end
end
- context 'creating a new label', js: true do
- it 'shows option to crate a new label is present' do
+ context 'editing issue labels', js: true do
+ before do
page.within('.block.labels') do
find('.edit-link').click
+ end
+ end
+ it 'shows option to create a new label' do
+ page.within('.block.labels') do
expect(page).to have_content 'Create new'
end
end
- it 'shows dropdown switches to "create label" section' do
- page.within('.block.labels') do
- find('.edit-link').click
- click_link 'Create new'
+ context 'creating a new label', js: true do
+ before do
+ page.within('.block.labels') do
+ click_link 'Create new'
+ end
+ end
- expect(page).to have_content 'Create new label'
+ it 'shows dropdown switches to "create label" section' do
+ page.within('.block.labels') do
+ expect(page).to have_content 'Create new label'
+ end
end
- end
- it 'adds new label' do
- page.within('.block.labels') do
- find('.edit-link').click
- sleep 1
- click_link 'Create new'
+ it 'adds new label' do
+ page.within('.block.labels') do
+ fill_in 'new_label_name', with: 'wontfix'
+ page.find(".suggest-colors a", match: :first).click
+ click_button 'Create'
+
+ page.within('.dropdown-page-one') do
+ expect(page).to have_content 'wontfix'
+ end
+ end
+ end
- fill_in 'new_label_name', with: 'wontfix'
- page.find(".suggest-colors a", match: :first).click
- click_button 'Create'
+ it 'shows error message if label title is taken' do
+ page.within('.block.labels') do
+ fill_in 'new_label_name', with: label.title
+ page.find('.suggest-colors a', match: :first).click
+ click_button 'Create'
- page.within('.dropdown-page-one') do
- expect(page).to have_content 'wontfix'
+ page.within('.dropdown-page-two') do
+ expect(page).to have_content 'Title has already been taken'
+ end
end
end
end
diff --git a/spec/features/merge_requests/filter_by_labels_spec.rb b/spec/features/merge_requests/filter_by_labels_spec.rb
index 4c60329865c..55f3c1863ff 100644
--- a/spec/features/merge_requests/filter_by_labels_spec.rb
+++ b/spec/features/merge_requests/filter_by_labels_spec.rb
@@ -1,6 +1,8 @@
require 'rails_helper'
feature 'Issue filtering by Labels', feature: true, js: true do
+ include FilteredSearchHelpers
+ include MergeRequestHelpers
include WaitForAjax
let(:project) { create(:project, :public) }
@@ -32,123 +34,77 @@ feature 'Issue filtering by Labels', feature: true, js: true do
context 'filter by label bug' do
before do
- select_labels('bug')
+ input_filtered_search('label:~bug')
end
it 'apply the filter' do
expect(page).to have_content "Bugfix1"
expect(page).to have_content "Bugfix2"
expect(page).not_to have_content "Feature1"
- expect(find('.filtered-labels')).to have_content "bug"
- expect(find('.filtered-labels')).not_to have_content "feature"
- expect(find('.filtered-labels')).not_to have_content "enhancement"
-
- find('.js-label-filter-remove').click
- wait_for_ajax
- expect(find('.filtered-labels', visible: false)).to have_no_content "bug"
end
end
context 'filter by label feature' do
before do
- select_labels('feature')
+ input_filtered_search('label:~feature')
end
it 'applies the filter' do
expect(page).to have_content "Feature1"
expect(page).not_to have_content "Bugfix2"
expect(page).not_to have_content "Bugfix1"
- expect(find('.filtered-labels')).to have_content "feature"
- expect(find('.filtered-labels')).not_to have_content "bug"
- expect(find('.filtered-labels')).not_to have_content "enhancement"
end
end
context 'filter by label enhancement' do
before do
- select_labels('enhancement')
+ input_filtered_search('label:~enhancement')
end
it 'applies the filter' do
expect(page).to have_content "Bugfix2"
expect(page).not_to have_content "Feature1"
expect(page).not_to have_content "Bugfix1"
- expect(find('.filtered-labels')).to have_content "enhancement"
- expect(find('.filtered-labels')).not_to have_content "bug"
- expect(find('.filtered-labels')).not_to have_content "feature"
end
end
context 'filter by label enhancement and bug in issues list' do
before do
- select_labels('bug', 'enhancement')
+ input_filtered_search('label:~bug label:~enhancement')
end
it 'applies the filters' do
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_content "Bugfix2"
expect(page).not_to have_content "Feature1"
- expect(find('.filtered-labels')).to have_content "bug"
- expect(find('.filtered-labels')).to have_content "enhancement"
- expect(find('.filtered-labels')).not_to have_content "feature"
-
- find('.js-label-filter-remove', match: :first).click
- wait_for_ajax
-
- expect(page).to have_content "Bugfix2"
- expect(page).not_to have_content "Feature1"
- expect(page).not_to have_content "Bugfix1"
- expect(find('.filtered-labels')).not_to have_content "bug"
- expect(find('.filtered-labels')).to have_content "enhancement"
- expect(find('.filtered-labels')).not_to have_content "feature"
end
end
- context 'remove filtered labels' do
+ context 'clear button' do
before do
- page.within '.labels-filter' do
- click_button 'Label'
- wait_for_ajax
- click_link 'bug'
- find('.dropdown-menu-close').click
- end
-
- page.within '.filtered-labels' do
- expect(page).to have_content 'bug'
- end
+ input_filtered_search('label:~bug')
end
it 'allows user to remove filtered labels' do
- first('.js-label-filter-remove').click
- wait_for_ajax
+ first('.clear-search').click
+ filtered_search.send_keys(:enter)
- expect(find('.filtered-labels', visible: false)).not_to have_content 'bug'
- expect(find('.labels-filter')).not_to have_content 'bug'
+ expect(page).to have_issuable_counts(open: 3, closed: 0, all: 3)
+ expect(page).to have_content "Bugfix2"
+ expect(page).to have_content "Feature1"
+ expect(page).to have_content "Bugfix1"
end
end
- context 'dropdown filtering' do
+ context 'filter dropdown' do
it 'filters by label name' do
- page.within '.labels-filter' do
- click_button 'Label'
- wait_for_ajax
- find('.dropdown-input input').set 'bug'
-
- page.within '.dropdown-content' do
- expect(page).not_to have_content 'enhancement'
- expect(page).to have_content 'bug'
- end
- end
- end
- end
+ init_label_search
+ filtered_search.send_keys('~bug')
- def select_labels(*labels)
- page.find('.js-label-select').click
- wait_for_ajax
- labels.each do |label|
- execute_script("$('.dropdown-menu-labels li:contains(\"#{label}\") a').click()")
+ page.within '.filter-dropdown' do
+ expect(page).not_to have_content 'enhancement'
+ expect(page).to have_content 'bug'
+ end
end
- page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
- wait_for_ajax
end
end
diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb
index f6e9230c8da..5608cda28f8 100644
--- a/spec/features/merge_requests/filter_by_milestone_spec.rb
+++ b/spec/features/merge_requests/filter_by_milestone_spec.rb
@@ -1,10 +1,18 @@
require 'rails_helper'
feature 'Merge Request filtering by Milestone', feature: true do
+ include FilteredSearchHelpers
+ include MergeRequestHelpers
+
let(:project) { create(:project, :public) }
let!(:user) { create(:user)}
let(:milestone) { create(:milestone, project: project) }
+ def filter_by_milestone(title)
+ find(".js-milestone-select").click
+ find(".milestone-filter a", text: title).click
+ end
+
before do
project.team << [user, :master]
login_as(user)
@@ -15,42 +23,42 @@ feature 'Merge Request filtering by Milestone', feature: true do
create(:merge_request, :simple, source_project: project, milestone: milestone)
visit_merge_requests(project)
- filter_by_milestone(Milestone::None.title)
+ input_filtered_search('milestone:none')
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_css('.merge-request', count: 1)
end
context 'filters by upcoming milestone', js: true do
- it 'does not show issues with no expiry' do
+ it 'does not show merge requests with no expiry' do
create(:merge_request, :with_diffs, source_project: project)
create(:merge_request, :simple, source_project: project, milestone: milestone)
visit_merge_requests(project)
- filter_by_milestone(Milestone::Upcoming.title)
+ input_filtered_search('milestone:upcoming')
expect(page).to have_css('.merge-request', count: 0)
end
- it 'shows issues in future' do
+ it 'shows merge requests in future' do
milestone = create(:milestone, project: project, due_date: Date.tomorrow)
create(:merge_request, :with_diffs, source_project: project)
create(:merge_request, :simple, source_project: project, milestone: milestone)
visit_merge_requests(project)
- filter_by_milestone(Milestone::Upcoming.title)
+ input_filtered_search('milestone:upcoming')
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_css('.merge-request', count: 1)
end
- it 'does not show issues in past' do
+ it 'does not show merge requests in past' do
milestone = create(:milestone, project: project, due_date: Date.yesterday)
create(:merge_request, :with_diffs, source_project: project)
create(:merge_request, :simple, source_project: project, milestone: milestone)
visit_merge_requests(project)
- filter_by_milestone(Milestone::Upcoming.title)
+ input_filtered_search('milestone:upcoming')
expect(page).to have_css('.merge-request', count: 0)
end
@@ -61,7 +69,7 @@ feature 'Merge Request filtering by Milestone', feature: true do
create(:merge_request, :simple, source_project: project)
visit_merge_requests(project)
- filter_by_milestone(milestone.title)
+ input_filtered_search("milestone:%'#{milestone.title}'")
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_css('.merge-request', count: 1)
@@ -77,19 +85,10 @@ feature 'Merge Request filtering by Milestone', feature: true do
create(:merge_request, :simple, source_project: project)
visit_merge_requests(project)
- filter_by_milestone(milestone.title)
+ input_filtered_search("milestone:%\"#{milestone.title}\"")
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_css('.merge-request', count: 1)
end
end
-
- def visit_merge_requests(project)
- visit namespace_project_merge_requests_path(project.namespace, project)
- end
-
- def filter_by_milestone(title)
- find(".js-milestone-select").click
- find(".milestone-filter a", text: title).click
- end
end
diff --git a/spec/features/merge_requests/filter_merge_requests_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb
index 4642b5a530d..6579a88d4ab 100644
--- a/spec/features/merge_requests/filter_merge_requests_spec.rb
+++ b/spec/features/merge_requests/filter_merge_requests_spec.rb
@@ -1,11 +1,13 @@
require 'rails_helper'
describe 'Filter merge requests', feature: true do
+ include FilteredSearchHelpers
+ include MergeRequestHelpers
include WaitForAjax
let!(:project) { create(:project) }
let!(:group) { create(:group) }
- let!(:user) { create(:user)}
+ let!(:user) { create(:user) }
let!(:milestone) { create(:milestone, project: project) }
let!(:label) { create(:label, project: project) }
let!(:wontfix) { create(:label, project: project, title: "Won't fix") }
@@ -15,183 +17,134 @@ describe 'Filter merge requests', feature: true do
group.add_developer(user)
login_as(user)
create(:merge_request, source_project: project, target_project: project)
+
+ visit namespace_project_merge_requests_path(project.namespace, project)
end
describe 'for assignee from mr#index' do
- before do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ let(:search_query) { "assignee:@#{user.username}" }
- find('.js-assignee-search').click
-
- find('.dropdown-menu-user-link', text: user.username).click
+ before do
+ input_filtered_search(search_query)
- wait_for_ajax
+ expect_mr_list_count(0)
end
context 'assignee', js: true do
it 'updates to current user' do
- expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
+ expect_filtered_search_input(search_query)
end
it 'does not change when closed link is clicked' do
find('.issues-state-filters a', text: "Closed").click
- expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
+ expect_filtered_search_input(search_query)
end
it 'does not change when all link is clicked' do
find('.issues-state-filters a', text: "All").click
- expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
+ expect_filtered_search_input(search_query)
end
end
end
describe 'for milestone from mr#index' do
- before do
- visit namespace_project_merge_requests_path(project.namespace, project)
-
- find('.js-milestone-select').click
+ let(:search_query) { "milestone:%#{milestone.title}" }
- find('.milestone-filter .dropdown-content a', text: milestone.title).click
+ before do
+ input_filtered_search(search_query)
- wait_for_ajax
+ expect_mr_list_count(0)
end
context 'milestone', js: true do
it 'updates to current milestone' do
- expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title)
+ expect_filtered_search_input(search_query)
end
it 'does not change when closed link is clicked' do
find('.issues-state-filters a', text: "Closed").click
- expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title)
+ expect_filtered_search_input(search_query)
end
it 'does not change when all link is clicked' do
find('.issues-state-filters a', text: "All").click
- expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title)
+ expect_filtered_search_input(search_query)
end
end
end
describe 'for label from mr#index', js: true do
- before do
- visit namespace_project_merge_requests_path(project.namespace, project)
- find('.js-label-select').click
- wait_for_ajax
- end
-
- it 'filters by any label' do
- find('.dropdown-menu-labels a', text: 'Any Label').click
- page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
- wait_for_ajax
-
- expect(find('.labels-filter')).to have_content 'Label'
- end
-
it 'filters by no label' do
- find('.dropdown-menu-labels a', text: 'No Label').click
- page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
- wait_for_ajax
+ input_filtered_search('label:none')
- page.within '.labels-filter' do
- expect(page).to have_content 'Labels'
- end
- expect(find('.js-label-select .dropdown-toggle-text')).to have_content('Labels')
+ expect_mr_list_count(1)
+ expect_filtered_search_input('label:none')
end
it 'filters by a label' do
- find('.dropdown-menu-labels a', text: label.title).click
- page.within '.labels-filter' do
- expect(page).to have_content label.title
- end
- expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title)
+ input_filtered_search("label:~#{label.title}")
+
+ expect_mr_list_count(0)
+ expect_filtered_search_input("label:~#{label.title}")
end
it "filters by `won't fix` and another label" do
- page.within '.labels-filter' do
- click_link wontfix.title
- expect(page).to have_content wontfix.title
- click_link label.title
- end
+ input_filtered_search("label:~\"#{wontfix.title}\" label:~#{label.title}")
- expect(find('.js-label-select .dropdown-toggle-text')).to have_content("#{wontfix.title} +1 more")
+ expect_mr_list_count(0)
+ expect_filtered_search_input("label:~\"#{wontfix.title}\" label:~#{label.title}")
end
it "filters by `won't fix` label followed by another label after page load" do
- page.within '.labels-filter' do
- click_link wontfix.title
- expect(page).to have_content wontfix.title
- end
-
- find('body').click
-
- expect(find('.filtered-labels')).to have_content(wontfix.title)
-
- find('.js-label-select').click
- wait_for_ajax
- find('.dropdown-menu-labels a', text: label.title).click
-
- find('body').click
+ input_filtered_search("label:~\"#{wontfix.title}\"")
- expect(find('.filtered-labels')).to have_content(wontfix.title)
- expect(find('.filtered-labels')).to have_content(label.title)
+ expect_mr_list_count(0)
+ expect_filtered_search_input("label:~\"#{wontfix.title}\"")
- find('.js-label-select').click
- wait_for_ajax
+ input_filtered_search_keys(" label:~#{label.title}")
- expect(find('.dropdown-menu-labels li', text: wontfix.title)).to have_css('.is-active')
- expect(find('.dropdown-menu-labels li', text: label.title)).to have_css('.is-active')
- end
+ expect_filtered_search_input("label:~\"#{wontfix.title}\" label:~#{label.title}")
- it "selects and unselects `won't fix`" do
- find('.dropdown-menu-labels a', text: wontfix.title).click
- find('.dropdown-menu-labels a', text: wontfix.title).click
- # Close label dropdown to load
- find('body').click
- expect(page).not_to have_css('.filtered-labels')
+ expect_mr_list_count(0)
+ expect_filtered_search_input("label:~\"#{wontfix.title}\" label:~#{label.title}")
end
end
describe 'for assignee and label from issues#index' do
- before do
- visit namespace_project_merge_requests_path(project.namespace, project)
-
- find('.js-assignee-search').click
+ let(:search_query) { "assignee:@#{user.username} label:~#{label.title}" }
- find('.dropdown-menu-user-link', text: user.username).click
+ before do
+ input_filtered_search("assignee:@#{user.username}")
- expect(page).not_to have_selector('.mr-list .merge-request')
+ expect_mr_list_count(1)
+ expect_filtered_search_input("assignee:@#{user.username}")
- find('.js-label-select').click
+ input_filtered_search_keys(" label:~#{label.title}")
- find('.dropdown-menu-labels .dropdown-content a', text: label.title).click
- page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
+ expect_mr_list_count(1)
- wait_for_ajax
+ find("#state-opened[href=\"#{URI.parse(current_url).path}?assignee_username=#{user.username}&label_name%5B%5D=#{label.title}&scope=all&state=opened\"]")
end
context 'assignee and label', js: true do
it 'updates to current assignee and label' do
- expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
- expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title)
+ expect_filtered_search_input(search_query)
end
it 'does not change when closed link is clicked' do
find('.issues-state-filters a', text: "Closed").click
- expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
- expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title)
+ expect_filtered_search_input(search_query)
end
it 'does not change when all link is clicked' do
find('.issues-state-filters a', text: "All").click
- expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
- expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title)
+ expect_filtered_search_input(search_query)
end
end
end
@@ -203,11 +156,11 @@ describe 'Filter merge requests', feature: true do
bug_label = create(:label, project: project, title: 'bug')
milestone = create(:milestone, title: "8", project: project)
- mr = create(:merge_request,
- title: "Bug 2",
- source_project: project,
- target_project: project,
- source_branch: "bug2",
+ mr = create(:merge_request,
+ title: "Bug 2",
+ source_project: project,
+ target_project: project,
+ source_branch: "bug2",
milestone: milestone,
author: user,
assignee: user)
@@ -218,15 +171,13 @@ describe 'Filter merge requests', feature: true do
context 'only text', js: true do
it 'filters merge requests by searched text' do
- fill_in 'issuable_search', with: 'Bug'
+ input_filtered_search('bug')
- page.within '.mr-list' do
- expect(page).to have_selector('.merge-request', count: 2)
- end
+ expect_mr_list_count(2)
end
it 'does not show any merge requests' do
- fill_in 'issuable_search', with: 'testing'
+ input_filtered_search('testing')
page.within '.mr-list' do
expect(page).not_to have_selector('.merge-request')
@@ -234,82 +185,49 @@ describe 'Filter merge requests', feature: true do
end
end
- context 'text and dropdown options', js: true do
+ context 'filters and searches', js: true do
it 'filters by text and label' do
- fill_in 'issuable_search', with: 'Bug'
+ input_filtered_search('Bug')
- expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2)
- page.within '.mr-list' do
- expect(page).to have_selector('.merge-request', count: 2)
- end
+ expect_mr_list_count(2)
+ expect_filtered_search_input('Bug')
- click_button 'Label'
- page.within '.labels-filter' do
- click_link 'bug'
- end
- find('.dropdown-menu-close-icon').click
+ input_filtered_search_keys(' label:~bug')
- expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- page.within '.mr-list' do
- expect(page).to have_selector('.merge-request', count: 1)
- end
+ expect_mr_list_count(1)
end
it 'filters by text and milestone' do
- fill_in 'issuable_search', with: 'Bug'
+ input_filtered_search('Bug')
- expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2)
- page.within '.mr-list' do
- expect(page).to have_selector('.merge-request', count: 2)
- end
+ expect_mr_list_count(2)
+ expect_filtered_search_input('Bug')
- click_button 'Milestone'
- page.within '.milestone-filter' do
- click_link '8'
- end
+ input_filtered_search_keys(' milestone:%8')
- expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- page.within '.mr-list' do
- expect(page).to have_selector('.merge-request', count: 1)
- end
+ expect_mr_list_count(1)
end
it 'filters by text and assignee' do
- fill_in 'issuable_search', with: 'Bug'
+ input_filtered_search('Bug')
- expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2)
- page.within '.mr-list' do
- expect(page).to have_selector('.merge-request', count: 2)
- end
+ expect_mr_list_count(2)
+ expect_filtered_search_input('Bug')
- click_button 'Assignee'
- page.within '.dropdown-menu-assignee' do
- click_link user.name
- end
+ input_filtered_search_keys(" assignee:@#{user.username}")
- expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- page.within '.mr-list' do
- expect(page).to have_selector('.merge-request', count: 1)
- end
+ expect_mr_list_count(1)
end
it 'filters by text and author' do
- fill_in 'issuable_search', with: 'Bug'
+ input_filtered_search('Bug')
- expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2)
- page.within '.mr-list' do
- expect(page).to have_selector('.merge-request', count: 2)
- end
+ expect_mr_list_count(2)
+ expect_filtered_search_input('Bug')
- click_button 'Author'
- page.within '.dropdown-menu-author' do
- click_link user.name
- end
+ input_filtered_search_keys(" author:@#{user.username}")
- expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- page.within '.mr-list' do
- expect(page).to have_selector('.merge-request', count: 1)
- end
+ expect_mr_list_count(1)
end
end
end
@@ -328,18 +246,9 @@ describe 'Filter merge requests', feature: true do
end
it 'is able to filter and sort merge requests' do
- click_button 'Label'
- wait_for_ajax
- page.within '.labels-filter' do
- click_link 'bug'
- end
- find('.dropdown-menu-close-icon').click
- wait_for_ajax
+ input_filtered_search('label:~bug')
- expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2)
- page.within '.mr-list' do
- expect(page).to have_selector('.merge-request', count: 2)
- end
+ expect_mr_list_count(2)
click_button 'Last created'
page.within '.dropdown-menu-sort' do
@@ -352,4 +261,38 @@ describe 'Filter merge requests', feature: true do
end
end
end
+
+ describe 'filter by assignee id', js: true do
+ it 'filter by current user' do
+ visit namespace_project_merge_requests_path(project.namespace, project, assignee_id: user.id)
+
+ expect_filtered_search_input("assignee:@#{user.username}")
+ end
+
+ it 'filter by new user' do
+ new_user = create(:user)
+ project.add_developer(new_user)
+
+ visit namespace_project_merge_requests_path(project.namespace, project, assignee_id: new_user.id)
+
+ expect_filtered_search_input("assignee:@#{new_user.username}")
+ end
+ end
+
+ describe 'filter by author id', js: true do
+ it 'filter by current user' do
+ visit namespace_project_merge_requests_path(project.namespace, project, author_id: user.id)
+
+ expect_filtered_search_input("author:@#{user.username}")
+ end
+
+ it 'filter by new user' do
+ new_user = create(:user)
+ project.add_developer(new_user)
+
+ visit namespace_project_merge_requests_path(project.namespace, project, author_id: new_user.id)
+
+ expect_filtered_search_input("author:@#{new_user.username}")
+ end
+ end
end
diff --git a/spec/features/merge_requests/reset_filters_spec.rb b/spec/features/merge_requests/reset_filters_spec.rb
index 3a7ece7e1d6..58f11499e3f 100644
--- a/spec/features/merge_requests/reset_filters_spec.rb
+++ b/spec/features/merge_requests/reset_filters_spec.rb
@@ -1,17 +1,20 @@
require 'rails_helper'
feature 'Issues filter reset button', feature: true, js: true do
+ include FilteredSearchHelpers
+ include MergeRequestHelpers
include WaitForAjax
include IssueHelpers
- let!(:project) { create(:project, :public) }
- let!(:user) { create(:user)}
- let!(:milestone) { create(:milestone, project: project) }
- let!(:bug) { create(:label, project: project, name: 'bug')}
+ let!(:project) { create(:project, :public) }
+ let!(:user) { create(:user) }
+ let!(:milestone) { create(:milestone, project: project) }
+ let!(:bug) { create(:label, project: project, name: 'bug')}
let!(:mr1) { create(:merge_request, title: "Feature", source_project: project, target_project: project, source_branch: "Feature", milestone: milestone, author: user, assignee: user) }
let!(:mr2) { create(:merge_request, title: "Bugfix1", source_project: project, target_project: project, source_branch: "Bugfix1") }
- let(:merge_request_css) { '.merge-request' }
+ let(:merge_request_css) { '.merge-request' }
+ let(:clear_search_css) { '.filtered-search-input-container .clear-search' }
before do
mr2.labels << bug
@@ -50,7 +53,7 @@ feature 'Issues filter reset button', feature: true, js: true do
context 'when author filter has been applied' do
it 'resets the author filter' do
- visit_merge_requests(project, author_id: user.id)
+ visit_merge_requests(project, author_username: user.username)
expect(page).to have_css(merge_request_css, count: 1)
reset_filters
@@ -60,7 +63,7 @@ feature 'Issues filter reset button', feature: true, js: true do
context 'when assignee filter has been applied' do
it 'resets the assignee filter' do
- visit_merge_requests(project, assignee_id: user.id)
+ visit_merge_requests(project, assignee_username: user.username)
expect(page).to have_css(merge_request_css, count: 1)
reset_filters
@@ -70,7 +73,7 @@ feature 'Issues filter reset button', feature: true, js: true do
context 'when all filters have been applied' do
it 'resets all filters' do
- visit_merge_requests(project, assignee_id: user.id, author_id: user.id, milestone_title: milestone.title, label_name: bug.name, search: 'Bug')
+ visit_merge_requests(project, assignee_username: user.username, author_username: user.username, milestone_title: milestone.title, label_name: bug.name, search: 'Bug')
expect(page).to have_css(merge_request_css, count: 0)
reset_filters
@@ -82,15 +85,7 @@ feature 'Issues filter reset button', feature: true, js: true do
it 'the reset link should not be visible' do
visit_merge_requests(project)
expect(page).to have_css(merge_request_css, count: 2)
- expect(page).not_to have_css '.reset_filters'
+ expect(page).not_to have_css(clear_search_css)
end
end
-
- def visit_merge_requests(project, opts = {})
- visit namespace_project_merge_requests_path project.namespace, project, opts
- end
-
- def reset_filters
- find('.reset-filters').click
- end
end
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index b56e562b2b6..45185f2dd1f 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -19,6 +19,51 @@ feature "New project", feature: true do
end
end
+ context "Namespace selector" do
+ context "with user namespace" do
+ before do
+ visit new_project_path
+ end
+
+ it "selects the user namespace" do
+ namespace = find("#project_namespace_id")
+
+ expect(namespace.text).to eq user.username
+ end
+ end
+
+ context "with group namespace" do
+ let(:group) { create(:group, :private, owner: user) }
+
+ before do
+ group.add_owner(user)
+ visit new_project_path(namespace_id: group.id)
+ end
+
+ it "selects the group namespace" do
+ namespace = find("#project_namespace_id option[selected]")
+
+ expect(namespace.text).to eq group.name
+ end
+
+ context "on validation error" do
+ before do
+ fill_in('project_path', with: 'private-group-project')
+ choose('Internal')
+ click_button('Create project')
+
+ expect(page).to have_css '.project-edit-errors .alert.alert-danger'
+ end
+
+ it "selects the group namespace" do
+ namespace = find("#project_namespace_id option[selected]")
+
+ expect(namespace.text).to eq group.name
+ end
+ end
+ end
+ end
+
context 'Import project options' do
before do
visit new_project_path
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 0b5ccc8c515..9f06e52ab55 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -54,7 +54,7 @@ describe 'Pipeline', :feature, :js do
expect(page).to have_content('Build')
expect(page).to have_content('Test')
expect(page).to have_content('Deploy')
- expect(page).to have_content('Retry failed')
+ expect(page).to have_content('Retry')
expect(page).to have_content('Cancel running')
end
@@ -164,9 +164,9 @@ describe 'Pipeline', :feature, :js do
it { expect(page).not_to have_content('retried') }
context 'when retrying' do
- before { click_on 'Retry failed' }
+ before { find('.js-retry-button').trigger('click') }
- it { expect(page).not_to have_content('Retry failed') }
+ it { expect(page).not_to have_content('Retry') }
end
end
@@ -198,7 +198,7 @@ describe 'Pipeline', :feature, :js do
expect(page).to have_content(build_failed.id)
expect(page).to have_content(build_running.id)
expect(page).to have_content(build_external.id)
- expect(page).to have_content('Retry failed')
+ expect(page).to have_content('Retry')
expect(page).to have_content('Cancel running')
expect(page).to have_link('Play')
end
@@ -226,9 +226,9 @@ describe 'Pipeline', :feature, :js do
it { expect(page).not_to have_content('retried') }
context 'when retrying' do
- before { click_on 'Retry failed' }
+ before { find('.js-retry-button').trigger('click') }
- it { expect(page).not_to have_content('Retry failed') }
+ it { expect(page).not_to have_content('Retry') }
it { expect(page).to have_selector('.retried') }
end
end
diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb
index 4eafac1acd8..3b8f0b2d3f8 100644
--- a/spec/features/projects/ref_switcher_spec.rb
+++ b/spec/features/projects/ref_switcher_spec.rb
@@ -20,6 +20,8 @@ feature 'Ref switcher', feature: true, js: true do
input.set 'binary'
wait_for_ajax
+ expect(find('.dropdown-content ul')).to have_selector('li', count: 6)
+
page.within '.dropdown-content ul' do
input.native.send_keys :enter
end
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index 0fe5a897565..7da05defa81 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -186,7 +186,7 @@ describe "Search", feature: true do
sleep 2
expect(page).to have_selector('.merge-requests-holder')
- expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
+ expect(find('.filtered-search').value).to eq("assignee:@#{user.username}")
end
it 'takes user to her MR page when MR authored is clicked' do
@@ -194,7 +194,7 @@ describe "Search", feature: true do
sleep 2
expect(page).to have_selector('.merge-requests-holder')
- expect(find('.js-author-search .dropdown-toggle-text')).to have_content(user.name)
+ expect(find('.filtered-search').value).to eq("author:@#{user.username}")
end
end
diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb
index fb19dac1d6a..3495091a0d5 100644
--- a/spec/features/todos/todos_spec.rb
+++ b/spec/features/todos/todos_spec.rb
@@ -171,6 +171,29 @@ describe 'Dashboard Todos', feature: true do
end
end
+ context 'User have large number of todos' do
+ before do
+ create_list(:todo, 101, :mentioned, user: user, project: project, target: issue, author: author)
+
+ login_as(user)
+ visit dashboard_todos_path
+ end
+
+ it 'shows 99+ for count >= 100 in notification' do
+ expect(page).to have_selector('.todos-pending-count', text: '99+')
+ end
+
+ it 'shows exact number in To do tab' do
+ expect(page).to have_selector('.todos-pending .badge', text: '101')
+ end
+
+ it 'shows exact number for count < 100' do
+ 3.times { first('.js-done-todo').click }
+
+ expect(page).to have_selector('.todos-pending-count', text: '98')
+ end
+ end
+
context 'User has a Build Failed todo' do
let!(:todo) { create(:todo, :build_failed, user: user, project: project, author: author) }
diff --git a/spec/fixtures/mail_room_disabled.yml b/spec/fixtures/config/mail_room_disabled.yml
index 97f8cff051f..97f8cff051f 100644
--- a/spec/fixtures/mail_room_disabled.yml
+++ b/spec/fixtures/config/mail_room_disabled.yml
diff --git a/spec/fixtures/mail_room_enabled.yml b/spec/fixtures/config/mail_room_enabled.yml
index 9c94649244d..9c94649244d 100644
--- a/spec/fixtures/mail_room_enabled.yml
+++ b/spec/fixtures/config/mail_room_enabled.yml
diff --git a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js.es6
index 84c0e9cbfe2..a91801cfc89 100644
--- a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js.es6
+++ b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js.es6
@@ -99,6 +99,29 @@ require('~/filtered_search/filtered_search_tokenizer');
expect(results.tokens[2].value).toBe('Doing');
expect(results.tokens[2].symbol).toBe('~');
});
+
+ it('returns search value for invalid tokens', () => {
+ const results = gl.FilteredSearchTokenizer.processTokens('fake:token');
+ expect(results.lastToken).toBe('fake:token');
+ expect(results.searchToken).toBe('fake:token');
+ expect(results.tokens.length).toEqual(0);
+ });
+
+ it('returns search value and token for mix of valid and invalid tokens', () => {
+ const results = gl.FilteredSearchTokenizer.processTokens('label:real fake:token');
+ expect(results.tokens.length).toEqual(1);
+ expect(results.tokens[0].key).toBe('label');
+ expect(results.tokens[0].value).toBe('real');
+ expect(results.tokens[0].symbol).toBe('');
+ expect(results.lastToken).toBe('fake:token');
+ expect(results.searchToken).toBe('fake:token');
+ });
+
+ it('returns search value for invalid symbols', () => {
+ const results = gl.FilteredSearchTokenizer.processTokens('std::includes');
+ expect(results.lastToken).toBe('std::includes');
+ expect(results.searchToken).toBe('std::includes');
+ });
});
});
})();
diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js
index 576b6cfefdc..46a27b8c98f 100644
--- a/spec/javascripts/header_spec.js
+++ b/spec/javascripts/header_spec.js
@@ -45,8 +45,8 @@ require('~/lib/utils/text_utility');
expect(isTodosCountHidden()).toEqual(false);
});
- it('should add delimiter to todos-pending-count', function() {
- expect($(todosPendingCount).text()).toEqual('1,000');
+ it('should show 99+ for todos-pending-count', function() {
+ expect($(todosPendingCount).text()).toEqual('99+');
});
});
});
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js.es6 b/spec/javascripts/lib/utils/text_utility_spec.js.es6
index 86ade66ec29..06b69b8ac17 100644
--- a/spec/javascripts/lib/utils/text_utility_spec.js.es6
+++ b/spec/javascripts/lib/utils/text_utility_spec.js.es6
@@ -35,5 +35,16 @@ require('~/lib/utils/text_utility');
expect(gl.text.pluralize('test', 1)).toBe('test');
});
});
+
+ describe('gl.text.highCountTrim', () => {
+ it('returns 99+ for count >= 100', () => {
+ expect(gl.text.highCountTrim(105)).toBe('99+');
+ expect(gl.text.highCountTrim(100)).toBe('99+');
+ });
+
+ it('returns exact number for count < 100', () => {
+ expect(gl.text.highCountTrim(45)).toBe(45);
+ });
+ });
});
})();
diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js
index cb8754581c0..aaf058bd755 100644
--- a/spec/javascripts/search_autocomplete_spec.js
+++ b/spec/javascripts/search_autocomplete_spec.js
@@ -89,8 +89,8 @@ require('vendor/fuzzaldrin-plus');
var a1, a2, a3, a4, issuesAssignedToMeLink, issuesIHaveCreatedLink, mrsAssignedToMeLink, mrsIHaveCreatedLink;
issuesAssignedToMeLink = issuesPath + "/?assignee_username=" + userName;
issuesIHaveCreatedLink = issuesPath + "/?author_username=" + userName;
- mrsAssignedToMeLink = mrsPath + "/?assignee_id=" + userId;
- mrsIHaveCreatedLink = mrsPath + "/?author_id=" + userId;
+ mrsAssignedToMeLink = mrsPath + "/?assignee_username=" + userName;
+ mrsIHaveCreatedLink = mrsPath + "/?author_username=" + userName;
a1 = "a[href='" + issuesAssignedToMeLink + "']";
a2 = "a[href='" + issuesIHaveCreatedLink + "']";
a3 = "a[href='" + mrsAssignedToMeLink + "']";
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index e94ca4fcfd2..e007044868c 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -101,6 +101,16 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
end
end
+ describe '#concurrent_foreign_key_name' do
+ it 'returns the name for a foreign key' do
+ name = model.concurrent_foreign_key_name(:this_is_a_very_long_table_name,
+ :with_a_very_long_column_name)
+
+ expect(name).to be_an_instance_of(String)
+ expect(name.length).to eq(13)
+ end
+ end
+
describe '#disable_statement_timeout' do
context 'using PostgreSQL' do
it 'disables statement timeouts' do
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index 2069d2a7c75..f35e963a14b 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -107,46 +107,47 @@ describe API::Todos, api: true do
end
end
- describe 'DELETE /todos/:id' do
+ describe 'POST /todos/:id/mark_as_done' do
context 'when unauthenticated' do
it 'returns authentication error' do
- delete api("/todos/#{pending_1.id}")
+ post api("/todos/#{pending_1.id}/mark_as_done")
- expect(response.status).to eq(401)
+ expect(response).to have_http_status(401)
end
end
context 'when authenticated' do
it 'marks a todo as done' do
- delete api("/todos/#{pending_1.id}", john_doe)
+ post api("/todos/#{pending_1.id}/mark_as_done", john_doe)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(201)
+ expect(json_response['id']).to eq(pending_1.id)
+ expect(json_response['state']).to eq('done')
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)
+ post api("/todos/#{pending_1.id}/mark_as_done", john_doe)
end
end
end
- describe 'DELETE /todos' do
+ describe 'POST /mark_as_done' do
context 'when unauthenticated' do
it 'returns authentication error' do
- delete api('/todos')
+ post api('/todos/mark_as_done')
- expect(response.status).to eq(401)
+ expect(response).to have_http_status(401)
end
end
context 'when authenticated' do
it 'marks all todos as done' do
- delete api('/todos', john_doe)
+ post api('/todos/mark_as_done', john_doe)
- expect(response.status).to eq(200)
- expect(response.body).to eq('3')
+ expect(response).to have_http_status(204)
expect(pending_1.reload).to be_done
expect(pending_2.reload).to be_done
expect(pending_3.reload).to be_done
@@ -155,7 +156,7 @@ describe API::Todos, api: true do
it 'updates todos cache' do
expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original
- delete api("/todos", john_doe)
+ post api("/todos/mark_as_done", john_doe)
end
end
end
diff --git a/spec/requests/api/v3/todos_spec.rb b/spec/requests/api/v3/todos_spec.rb
new file mode 100644
index 00000000000..80fa697e949
--- /dev/null
+++ b/spec/requests/api/v3/todos_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+describe API::V3::Todos, api: true do
+ include ApiHelpers
+
+ let(:project_1) { create(:empty_project) }
+ let(:project_2) { create(:empty_project) }
+ let(:author_1) { create(:user) }
+ let(:author_2) { create(:user) }
+ let(:john_doe) { create(:user, username: 'john_doe') }
+ let!(:pending_1) { create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe) }
+ let!(:pending_2) { create(:todo, project: project_2, author: author_2, user: john_doe) }
+ let!(:pending_3) { create(:todo, project: project_1, author: author_2, user: john_doe) }
+ let!(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe) }
+
+ before do
+ project_1.team << [john_doe, :developer]
+ project_2.team << [john_doe, :developer]
+ end
+
+ describe 'DELETE /todos/:id' do
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ delete v3_api("/todos/#{pending_1.id}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'marks a todo as done' do
+ delete v3_api("/todos/#{pending_1.id}", john_doe)
+
+ 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 v3_api("/todos/#{pending_1.id}", john_doe)
+ end
+ end
+ end
+
+ describe 'DELETE /todos' do
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ delete v3_api('/todos')
+
+ expect(response.status).to eq(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'marks all todos as done' do
+ delete v3_api('/todos', john_doe)
+
+ expect(response.status).to eq(200)
+ expect(response.body).to eq('3')
+ expect(pending_1.reload).to be_done
+ 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 v3_api("/todos", john_doe)
+ end
+ end
+ end
+end
diff --git a/spec/services/users/destroy_spec.rb b/spec/services/users/destroy_spec.rb
index 46e58393218..c0bf27c698c 100644
--- a/spec/services/users/destroy_spec.rb
+++ b/spec/services/users/destroy_spec.rb
@@ -2,11 +2,11 @@ require 'spec_helper'
describe Users::DestroyService, services: true do
describe "Deletes a user and all their personal projects" do
- let!(:user) { create(:user) }
- let!(:current_user) { create(:user) }
- let!(:namespace) { create(:namespace, owner: user) }
- let!(:project) { create(:project, namespace: namespace) }
- let(:service) { described_class.new(current_user) }
+ let!(:user) { create(:user) }
+ let!(:admin) { create(:admin) }
+ let!(:namespace) { create(:namespace, owner: user) }
+ let!(:project) { create(:project, namespace: namespace) }
+ let(:service) { described_class.new(admin) }
context 'no options are given' do
it 'deletes the user' do
@@ -57,5 +57,26 @@ describe Users::DestroyService, services: true do
expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
end
end
+
+ context "deletion permission checks" do
+ it 'does not delete the user when user is not an admin' do
+ other_user = create(:user)
+
+ expect { described_class.new(other_user).execute(user) }.to raise_error(Gitlab::Access::AccessDeniedError)
+ expect(User.exists?(user.id)).to be(true)
+ end
+
+ it 'allows admins to delete anyone' do
+ described_class.new(admin).execute(user)
+
+ expect(User.exists?(user.id)).to be(false)
+ end
+
+ it 'allows users to delete their own account' do
+ described_class.new(user).execute(user)
+
+ expect(User.exists?(user.id)).to be(false)
+ end
+ end
end
end
diff --git a/spec/support/filtered_search_helpers.rb b/spec/support/filtered_search_helpers.rb
new file mode 100644
index 00000000000..58f6636e680
--- /dev/null
+++ b/spec/support/filtered_search_helpers.rb
@@ -0,0 +1,37 @@
+module FilteredSearchHelpers
+ def filtered_search
+ page.find('.filtered-search')
+ end
+
+ def input_filtered_search(search_term, submit: true)
+ filtered_search.set(search_term)
+
+ if submit
+ filtered_search.send_keys(:enter)
+ end
+ end
+
+ def input_filtered_search_keys(search_term)
+ filtered_search.send_keys(search_term)
+ filtered_search.send_keys(:enter)
+ end
+
+ def expect_filtered_search_input(input)
+ expect(find('.filtered-search').value).to eq(input)
+ end
+
+ def clear_search_field
+ find('.filtered-search-input-container .clear-search').click
+ end
+
+ def reset_filters
+ clear_search_field
+ filtered_search.send_keys(:enter)
+ end
+
+ def init_label_search
+ filtered_search.set('label:')
+ # This ensures the dropdown is shown
+ expect(find('#js-dropdown-label')).not_to have_css('.filter-dropdown-loading')
+ end
+end
diff --git a/spec/support/merge_request_helpers.rb b/spec/support/merge_request_helpers.rb
index d5801c8272f..326b85eabd0 100644
--- a/spec/support/merge_request_helpers.rb
+++ b/spec/support/merge_request_helpers.rb
@@ -10,4 +10,13 @@ module MergeRequestHelpers
def last_merge_request
page.all('ul.mr-list > li').last.text
end
+
+ def expect_mr_list_count(open_count, closed_count = 0)
+ all_count = open_count + closed_count
+
+ expect(page).to have_issuable_counts(open: open_count, closed: closed_count, all: all_count)
+ page.within '.mr-list' do
+ expect(page).to have_selector('.merge-request', count: open_count)
+ end
+ end
end