summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml1
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock3
-rw-r--r--app/assets/javascripts/activities.js3
-rw-r--r--app/assets/javascripts/awards_handler.js2
-rw-r--r--app/assets/javascripts/boards/components/board_blank_state.js4
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js3
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js3
-rw-r--r--app/assets/javascripts/diff_notes/components/diff_note_avatars.js4
-rw-r--r--app/assets/javascripts/dispatcher.js2
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js2
-rw-r--r--app/assets/javascripts/issuable_context.js3
-rw-r--r--app/assets/javascripts/main.js2
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflict_store.js2
-rw-r--r--app/assets/javascripts/merge_request_tabs.js4
-rw-r--r--app/assets/javascripts/notes.js4
-rw-r--r--app/assets/javascripts/project.js3
-rw-r--r--app/assets/javascripts/right_sidebar.js3
-rw-r--r--app/assets/javascripts/user.js3
-rw-r--r--app/assets/javascripts/user_callout.js6
-rw-r--r--app/controllers/application_controller.rb7
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb4
-rw-r--r--app/models/project_services/chat_notification_service.rb27
-rw-r--r--app/models/project_services/mattermost_service.rb16
-rw-r--r--app/models/project_services/slack_service.rb16
-rw-r--r--app/models/user.rb2
-rw-r--r--app/services/boards/issues/list_service.rb2
-rw-r--r--app/views/dashboard/todos/index.html.haml13
-rw-r--r--changelogs/unreleased/24215-closed-issues-board.yml4
-rw-r--r--changelogs/unreleased/29555-align-all-todo.yml4
-rw-r--r--changelogs/unreleased/filter-bar-fix-ie.yml4
-rw-r--r--changelogs/unreleased/zj-chat-notification-default-branch.yml4
-rw-r--r--config/environments/test.rb3
-rw-r--r--doc/administration/high_availability/nfs.md2
-rw-r--r--lib/gitlab/etag_caching/store.rb2
-rw-r--r--lib/gitlab/testing/request_blocker_middleware.rb61
-rw-r--r--spec/features/admin/admin_settings_spec.rb1
-rw-r--r--spec/features/participants_autocomplete_spec.rb95
-rw-r--r--spec/features/projects/deploy_keys_spec.rb26
-rw-r--r--spec/javascripts/boards/boards_store_spec.js2
-rw-r--r--spec/javascripts/filtered_search/filtered_search_manager_spec.js2
-rw-r--r--spec/javascripts/test_bundle.js1
-rw-r--r--spec/javascripts/user_callout_spec.js18
-rw-r--r--spec/mailers/emails/profile_spec.rb10
-rw-r--r--spec/mailers/notify_spec.rb380
-rw-r--r--spec/services/boards/issues/list_service_spec.rb3
-rw-r--r--spec/services/issuable/bulk_update_service_spec.rb2
-rw-r--r--spec/spec_helper.rb3
-rw-r--r--spec/support/notify_shared_examples.rb140
-rw-r--r--spec/support/slack_mattermost_notifications_shared_examples.rb19
-rw-r--r--spec/support/wait_for_requests.rb32
-rw-r--r--vendor/assets/javascripts/js.cookie.js156
52 files changed, 510 insertions, 609 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f506b1b375e..24627b5faca 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -333,6 +333,7 @@ migration paths:
- bundle install --without postgres production --jobs $(nproc) $FLAGS --retry=3
- bundle exec rake db:drop db:create db:schema:load db:seed_fu
- git checkout $CI_COMMIT_SHA
+ - bundle install --without postgres production --jobs $(nproc) $FLAGS --retry=3
- source scripts/prepare_build.sh
- bundle exec rake db:migrate
diff --git a/Gemfile b/Gemfile
index 6af27ce0f3e..38158387642 100644
--- a/Gemfile
+++ b/Gemfile
@@ -327,6 +327,7 @@ group :test do
gem 'test_after_commit', '~> 1.1'
gem 'sham_rack', '~> 1.3.6'
gem 'timecop', '~> 0.8.0'
+ gem 'concurrent-ruby', '~> 1.0.5'
end
gem 'octokit', '~> 4.6.2'
diff --git a/Gemfile.lock b/Gemfile.lock
index 043ca4f8800..07be5d7aded 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -128,7 +128,7 @@ GEM
execjs
coffee-script-source (1.10.0)
colorize (0.7.7)
- concurrent-ruby (1.0.4)
+ concurrent-ruby (1.0.5)
connection_pool (2.2.1)
crack (0.4.3)
safe_yaml (~> 1.0.0)
@@ -868,6 +868,7 @@ DEPENDENCIES
chronic (~> 0.10.2)
chronic_duration (~> 0.10.6)
coffee-rails (~> 4.1.0)
+ concurrent-ruby (~> 1.0.5)
connection_pool (~> 2.0)
creole (~> 0.5.0)
d3_rails (~> 3.5.0)
diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js
index aebda7780e1..d816df831eb 100644
--- a/app/assets/javascripts/activities.js
+++ b/app/assets/javascripts/activities.js
@@ -1,6 +1,7 @@
/* eslint-disable no-param-reassign, class-methods-use-this */
/* global Pager */
-/* global Cookies */
+
+import Cookies from 'js-cookie';
class Activities {
constructor() {
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 9349918f7a0..c743dd551d7 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -1,4 +1,4 @@
-/* global Cookies */
+import Cookies from 'js-cookie';
import emojiMap from 'emojis/digests.json';
import emojiAliases from 'emojis/aliases.json';
diff --git a/app/assets/javascripts/boards/components/board_blank_state.js b/app/assets/javascripts/boards/components/board_blank_state.js
index 52893d4642b..3fc68457961 100644
--- a/app/assets/javascripts/boards/components/board_blank_state.js
+++ b/app/assets/javascripts/boards/components/board_blank_state.js
@@ -1,5 +1,7 @@
/* global ListLabel */
-/* global Cookies */
+
+import Cookies from 'js-cookie';
+
const Store = gl.issueBoards.BoardsStore;
export default {
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index 28ecb322df7..8912f234aa6 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -1,7 +1,8 @@
/* eslint-disable comma-dangle, space-before-function-paren, one-var, no-shadow, dot-notation, max-len */
-/* global Cookies */
/* global List */
+import Cookies from 'js-cookie';
+
(() => {
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
index 18b682fd5cb..ae17d05e679 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
@@ -1,9 +1,8 @@
-/* global Cookies */
/* global Flash */
import Vue from 'vue';
+import Cookies from 'js-cookie';
-window.Cookies = require('js-cookie');
require('./components/stage_code_component');
require('./components/stage_issue_component');
require('./components/stage_plan_component');
diff --git a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
index dd7081aefb7..0297add94d5 100644
--- a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
+++ b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
@@ -1,4 +1,6 @@
-/* global CommentsStore Cookies notes */
+/* global CommentsStore */
+/* global notes */
+
import Vue from 'vue';
import collapseIcon from '../icons/collapse_icon.svg';
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 3557f6f617e..d1a662459e1 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -41,9 +41,9 @@ import GroupsList from './groups_list';
import ProjectsList from './projects_list';
import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
+import UserCallout from './user_callout';
const ShortcutsBlob = require('./shortcuts_blob');
-const UserCallout = require('./user_callout');
(function() {
var Dispatcher;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index c6bb7fda8f2..22352950452 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -384,7 +384,7 @@ import FilteredSearchContainer from './container';
paths.push(`search=${sanitized}`);
}
- const parameterizedUrl = `?scope=all&utf8=✓&${paths.join('&')}`;
+ const parameterizedUrl = `?scope=all&utf8=%E2%9C%93&${paths.join('&')}`;
if (this.updateObject) {
this.updateObject(parameterizedUrl);
diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js
index 115312d4b83..834b98e8601 100644
--- a/app/assets/javascripts/issuable_context.js
+++ b/app/assets/javascripts/issuable_context.js
@@ -1,8 +1,9 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, comma-dangle, quotes, prefer-arrow-callback, consistent-return, one-var, no-var, one-var-declaration-per-line, no-underscore-dangle, max-len */
/* global UsersSelect */
-/* global Cookies */
/* global bp */
+import Cookies from 'js-cookie';
+
(function() {
this.IssuableContext = (function() {
function IssuableContext(currentUser) {
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 81d5748191d..b1ca0dc091d 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -1,6 +1,5 @@
/* eslint-disable func-names, space-before-function-paren, no-var, quotes, consistent-return, prefer-arrow-callback, comma-dangle, object-shorthand, no-new, max-len, no-multi-spaces, import/newline-after-import, import/first */
/* global bp */
-/* global Cookies */
/* global Flash */
/* global ConfirmDangerModal */
/* global Aside */
@@ -24,7 +23,6 @@ import './extensions/array';
window.jQuery = jQuery;
window.$ = jQuery;
window._ = _;
-window.Cookies = Cookies;
window.Pikaday = Pikaday;
window.Dropzone = Dropzone;
window.Sortable = Sortable;
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
index 07632d6225c..c4e379a4a0b 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
@@ -1,7 +1,7 @@
/* eslint-disable comma-dangle, object-shorthand, no-param-reassign, camelcase, no-nested-ternary, no-continue, max-len */
-/* global Cookies */
import Vue from 'vue';
+import Cookies from 'js-cookie';
((global) => {
global.mergeConflicts = global.mergeConflicts || {};
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 190336dbd20..d9692269c38 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -1,10 +1,10 @@
/* eslint-disable no-new, class-methods-use-this */
/* global Breakpoints */
-/* global Cookies */
/* global Flash */
+import Cookies from 'js-cookie';
+
require('./breakpoints');
-window.Cookies = require('js-cookie');
require('./flash');
/* eslint-disable max-len */
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 47cc34e7a20..1d563c63f39 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -1,14 +1,14 @@
/* eslint-disable no-restricted-properties, func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape */
/* global Flash */
/* global Autosave */
-/* global Cookies */
/* global ResolveService */
/* global mrRefreshWidgetUrl */
+import Cookies from 'js-cookie';
+
require('./autosave');
window.autosize = require('vendor/autosize');
window.Dropzone = require('dropzone');
-window.Cookies = require('js-cookie');
require('./dropzone_input');
require('./gfm_auto_complete');
require('vendor/jquery.caret'); // required by jquery.atwho
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
index db7ceaa2421..f944fcc5a58 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/project.js
@@ -1,7 +1,8 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, comma-dangle, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */
-/* global Cookies */
/* global ProjectSelect */
+import Cookies from 'js-cookie';
+
(function() {
this.Project = (function() {
function Project() {
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index 903862cac6b..7298a7d5347 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -1,5 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, object-shorthand, comma-dangle, no-else-return, no-param-reassign, max-len */
-/* global Cookies */
+
+import Cookies from 'js-cookie';
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
diff --git a/app/assets/javascripts/user.js b/app/assets/javascripts/user.js
index 059e6c628b3..19c9efe7fbd 100644
--- a/app/assets/javascripts/user.js
+++ b/app/assets/javascripts/user.js
@@ -1,5 +1,6 @@
/* eslint-disable class-methods-use-this, comma-dangle, arrow-parens, no-param-reassign */
-/* global Cookies */
+
+import Cookies from 'js-cookie';
((global) => {
global.User = class {
diff --git a/app/assets/javascripts/user_callout.js b/app/assets/javascripts/user_callout.js
index 99419e85b20..b27d252a3ef 100644
--- a/app/assets/javascripts/user_callout.js
+++ b/app/assets/javascripts/user_callout.js
@@ -1,4 +1,4 @@
-/* global Cookies */
+import Cookies from 'js-cookie';
const userCalloutElementName = '.user-callout';
const closeButton = '.close-user-callout';
@@ -27,7 +27,7 @@ const USER_CALLOUT_TEMPLATE = `
</div>
</div>`;
-class UserCallout {
+export default class UserCallout {
constructor() {
this.isCalloutDismissed = Cookies.get(USER_CALLOUT_COOKIE);
this.userCalloutBody = $(userCalloutElementName);
@@ -56,5 +56,3 @@ class UserCallout {
}
}
}
-
-module.exports = UserCallout;
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index b7ce081a5cd..6a6e335d314 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -64,8 +64,11 @@ class ApplicationController < ActionController::Base
# This filter handles both private tokens and personal access tokens
def authenticate_user_from_private_token!
- token_string = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence
- user = User.find_by_authentication_token(token_string) || User.find_by_personal_access_token(token_string)
+ token = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence
+
+ return unless token.present?
+
+ user = User.find_by_authentication_token(token) || User.find_by_personal_access_token(token)
if user && can?(user, :log_in)
# Notice we are passing store false, so the user is not
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index 1502b734f37..d0c44e297e3 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -31,8 +31,10 @@ class Projects::DeployKeysController < Projects::ApplicationController
end
def disable
- @project.deploy_keys_projects.find_by(deploy_key_id: params[:id]).destroy
+ deploy_key_project = @project.deploy_keys_projects.find_by(deploy_key_id: params[:id])
+ return render_404 unless deploy_key_project
+ deploy_key_project.destroy!
redirect_to_repository_settings(@project)
end
diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb
index 200be99f36b..75834103db5 100644
--- a/app/models/project_services/chat_notification_service.rb
+++ b/app/models/project_services/chat_notification_service.rb
@@ -6,7 +6,7 @@ class ChatNotificationService < Service
default_value_for :category, 'chat'
prop_accessor :webhook, :username, :channel
- boolean_accessor :notify_only_broken_pipelines
+ boolean_accessor :notify_only_broken_pipelines, :notify_only_default_branch
validates :webhook, presence: true, url: true, if: :activated?
@@ -17,6 +17,7 @@ class ChatNotificationService < Service
if properties.nil?
self.properties = {}
self.notify_only_broken_pipelines = true
+ self.notify_only_default_branch = true
end
end
@@ -29,6 +30,19 @@ class ChatNotificationService < Service
pipeline wiki_page]
end
+ def fields
+ default_fields + build_event_channels
+ end
+
+ def default_fields
+ [
+ { type: 'text', name: 'webhook', placeholder: "e.g. #{webhook_placeholder}" },
+ { type: 'text', name: 'username', placeholder: 'e.g. GitLab' },
+ { type: 'checkbox', name: 'notify_only_broken_pipelines' },
+ { type: 'checkbox', name: 'notify_only_default_branch' },
+ ]
+ end
+
def execute(data)
return unless supported_events.include?(data[:object_kind])
return unless webhook.present?
@@ -123,6 +137,17 @@ class ChatNotificationService < Service
end
def should_pipeline_be_notified?(data)
+ notify_for_ref?(data) && notify_for_pipeline?(data)
+ end
+
+ def notify_for_ref?(data)
+ return true if data[:object_attributes][:tag]
+ return true unless notify_only_default_branch
+
+ data[:object_attributes][:ref] == project.default_branch
+ end
+
+ def notify_for_pipeline?(data)
case data[:object_attributes][:status]
when 'success'
!notify_only_broken_pipelines?
diff --git a/app/models/project_services/mattermost_service.rb b/app/models/project_services/mattermost_service.rb
index 1156d050622..0362ed172c7 100644
--- a/app/models/project_services/mattermost_service.rb
+++ b/app/models/project_services/mattermost_service.rb
@@ -22,19 +22,11 @@ class MattermostService < ChatNotificationService
</ol>'
end
- def fields
- default_fields + build_event_channels
- end
-
- def default_fields
- [
- { type: 'text', name: 'webhook', placeholder: 'e.g. http://mattermost_host/hooks/…' },
- { type: 'text', name: 'username', placeholder: 'e.g. GitLab' },
- { type: 'checkbox', name: 'notify_only_broken_pipelines' },
- ]
- end
-
def default_channel_placeholder
"Channel handle (e.g. town-square)"
end
+
+ def webhook_placeholder
+ 'http://mattermost.example.com/hooks/…'
+ end
end
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index b657db6f9ee..71da0af75f6 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -21,19 +21,11 @@ class SlackService < ChatNotificationService
</ol>'
end
- def fields
- default_fields + build_event_channels
- end
-
- def default_fields
- [
- { type: 'text', name: 'webhook', placeholder: 'e.g. https://hooks.slack.com/services/…' },
- { type: 'text', name: 'username', placeholder: 'e.g. GitLab' },
- { type: 'checkbox', name: 'notify_only_broken_pipelines' },
- ]
- end
-
def default_channel_placeholder
"Channel name (e.g. general)"
end
+
+ def webhook_placeholder
+ 'https://hooks.slack.com/services/…'
+ end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 8c7ad5d5174..5d19d873f43 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -324,6 +324,8 @@ class User < ActiveRecord::Base
end
def find_by_personal_access_token(token_string)
+ return unless token_string
+
PersonalAccessTokensFinder.new(state: 'active').find_by(token: token_string)&.user
end
diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb
index 83f51947bd4..cb6d30396ec 100644
--- a/app/services/boards/issues/list_service.rb
+++ b/app/services/boards/issues/list_service.rb
@@ -3,7 +3,7 @@ module Boards
class ListService < BaseService
def execute
issues = IssuesFinder.new(current_user, filter_params).execute
- issues = without_board_labels(issues) unless movable_list?
+ issues = without_board_labels(issues) unless list
issues = with_list_label(issues) if movable_list?
issues.order_by_position_and_priority
end
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index d31ced004a0..e31fa5fbe95 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -19,12 +19,13 @@
.nav-controls
- if @todos.any?(&:pending?)
- = link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn btn-loading js-todos-mark-all', method: :delete, data: { href: destroy_all_dashboard_todos_path(todos_filter_params) } do
- Mark all as done
- = icon('spinner spin')
- = link_to bulk_restore_dashboard_todos_path, class: 'btn btn-loading js-todos-undo-all hidden', method: :patch , data: { href: bulk_restore_dashboard_todos_path(todos_filter_params) } do
- Undo mark all as done
- = icon('spinner spin')
+ .append-right-default
+ = link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn btn-loading js-todos-mark-all', method: :delete, data: { href: destroy_all_dashboard_todos_path(todos_filter_params) } do
+ Mark all as done
+ = icon('spinner spin')
+ = link_to bulk_restore_dashboard_todos_path, class: 'btn btn-loading js-todos-undo-all hidden', method: :patch , data: { href: bulk_restore_dashboard_todos_path(todos_filter_params) } do
+ Undo mark all as done
+ = icon('spinner spin')
.todos-filters
.row-content-block.second-block
diff --git a/changelogs/unreleased/24215-closed-issues-board.yml b/changelogs/unreleased/24215-closed-issues-board.yml
new file mode 100644
index 00000000000..678ec34b274
--- /dev/null
+++ b/changelogs/unreleased/24215-closed-issues-board.yml
@@ -0,0 +1,4 @@
+---
+title: Display all closed issues in “done” board list
+merge_request:
+author:
diff --git a/changelogs/unreleased/29555-align-all-todo.yml b/changelogs/unreleased/29555-align-all-todo.yml
new file mode 100644
index 00000000000..c1555a96a92
--- /dev/null
+++ b/changelogs/unreleased/29555-align-all-todo.yml
@@ -0,0 +1,4 @@
+---
+title: align Mark all as done with other Done buttons on Todos page
+merge_request:
+author:
diff --git a/changelogs/unreleased/filter-bar-fix-ie.yml b/changelogs/unreleased/filter-bar-fix-ie.yml
new file mode 100644
index 00000000000..f1fa7d9b177
--- /dev/null
+++ b/changelogs/unreleased/filter-bar-fix-ie.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed filtered search not working in IE
+merge_request:
+author:
diff --git a/changelogs/unreleased/zj-chat-notification-default-branch.yml b/changelogs/unreleased/zj-chat-notification-default-branch.yml
new file mode 100644
index 00000000000..fa0052d5034
--- /dev/null
+++ b/changelogs/unreleased/zj-chat-notification-default-branch.yml
@@ -0,0 +1,4 @@
+---
+title: Only send chat notifications for the default branch
+merge_request:
+author:
diff --git a/config/environments/test.rb b/config/environments/test.rb
index fb25d3a8b14..a25c5016a3b 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -1,4 +1,7 @@
Rails.application.configure do
+ # Make sure the middleware is inserted first in middleware chain
+ config.middleware.insert_before('ActionDispatch::Static', 'Gitlab::Testing::RequestBlockerMiddleware')
+
# Settings specified here will take precedence over those in config/application.rb
# The test environment is used exclusively to run your application's
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index 3893d837006..bf1aa6b9ac5 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -26,7 +26,7 @@ options:
circumstances it could lead to data loss if a failure occurs before data has
synced.
-## Client mount options
+## NFS Client mount options
Below is an example of an NFS mount point we use on GitLab.com:
diff --git a/lib/gitlab/etag_caching/store.rb b/lib/gitlab/etag_caching/store.rb
index 9532e432f78..0039fc01c8f 100644
--- a/lib/gitlab/etag_caching/store.rb
+++ b/lib/gitlab/etag_caching/store.rb
@@ -1,7 +1,7 @@
module Gitlab
module EtagCaching
class Store
- EXPIRY_TIME = 10.minutes
+ EXPIRY_TIME = 20.minutes
REDIS_NAMESPACE = 'etag:'.freeze
def get(key)
diff --git a/lib/gitlab/testing/request_blocker_middleware.rb b/lib/gitlab/testing/request_blocker_middleware.rb
new file mode 100644
index 00000000000..aa67fa08577
--- /dev/null
+++ b/lib/gitlab/testing/request_blocker_middleware.rb
@@ -0,0 +1,61 @@
+# rubocop:disable Style/ClassVars
+
+# This is inspired by http://www.salsify.com/blog/engineering/tearing-capybara-ajax-tests
+# Rack middleware that keeps track of the number of active requests and can block new requests.
+module Gitlab
+ module Testing
+ class RequestBlockerMiddleware
+ @@num_active_requests = Concurrent::AtomicFixnum.new(0)
+ @@block_requests = Concurrent::AtomicBoolean.new(false)
+
+ # Returns the number of requests the server is currently processing.
+ def self.num_active_requests
+ @@num_active_requests.value
+ end
+
+ # Prevents the server from accepting new requests. Any new requests will return an HTTP
+ # 503 status.
+ def self.block_requests!
+ @@block_requests.value = true
+ end
+
+ # Allows the server to accept requests again.
+ def self.allow_requests!
+ @@block_requests.value = false
+ end
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ increment_active_requests
+ if block_requests?
+ block_request(env)
+ else
+ @app.call(env)
+ end
+ ensure
+ decrement_active_requests
+ end
+
+ private
+
+ def block_requests?
+ @@block_requests.true?
+ end
+
+ def block_request(env)
+ [503, {}, []]
+ end
+
+ def increment_active_requests
+ @@num_active_requests.increment
+ end
+
+ def decrement_active_requests
+ @@num_active_requests.decrement
+ end
+ end
+ end
+end
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 03daab12c8f..5099441dce2 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -34,6 +34,7 @@ feature 'Admin updates settings', feature: true do
fill_in 'Username', with: 'test_user'
fill_in 'service_push_channel', with: '#test_channel'
page.check('Notify only broken pipelines')
+ page.check('Notify only default branch')
check_all_events
click_on 'Save'
diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb
index c2545b0c259..decad589c23 100644
--- a/spec/features/participants_autocomplete_spec.rb
+++ b/spec/features/participants_autocomplete_spec.rb
@@ -1,101 +1,62 @@
require 'spec_helper'
-feature 'Member autocomplete', feature: true do
- include WaitForAjax
-
+feature 'Member autocomplete', :js do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
- let(:participant) { create(:user) }
let(:author) { create(:user) }
+ let(:note) { create(:note, noteable: noteable, project: noteable.project) }
before do
- allow_any_instance_of(Commit).to receive(:author).and_return(author)
- login_as user
+ note # actually create the note
+ login_as(user)
end
- shared_examples "open suggestions" do
- it 'displays suggestions' do
- expect(page).to have_selector('.atwho-view', visible: true)
- end
-
- it 'suggests author' do
- page.within('.atwho-view', visible: true) do
- expect(page).to have_content(author.username)
+ shared_examples "open suggestions when typing @" do
+ before do
+ page.within('.new-note') do
+ find('#note_note').send_keys('@')
end
end
- it 'suggests participant' do
+ it 'suggests noteable author and note author' do
page.within('.atwho-view', visible: true) do
- expect(page).to have_content(participant.username)
+ expect(page).to have_content(author.username)
+ expect(page).to have_content(note.author.username)
end
end
end
- context 'adding a new note on a Issue', js: true do
+ context 'adding a new note on a Issue' do
+ let(:noteable) { create(:issue, author: author, project: project) }
before do
- issue = create(:issue, author: author, project: project)
- create(:note, note: 'Ultralight Beam', noteable: issue,
- project: project, author: participant)
- visit_issue(project, issue)
+ visit namespace_project_issue_path(project.namespace, project, noteable)
end
- context 'when typing @' do
- include_examples "open suggestions"
- before do
- open_member_suggestions
- end
- end
+ include_examples "open suggestions when typing @"
end
- context 'adding a new note on a Merge Request ', js: true do
+ context 'adding a new note on a Merge Request' do
+ let(:noteable) do
+ create(:merge_request, source_project: project,
+ target_project: project, author: author)
+ end
before do
- merge = create(:merge_request, source_project: project, target_project: project, author: author)
- create(:note, note: 'Ultralight Beam', noteable: merge,
- project: project, author: participant)
- visit_merge_request(project, merge)
+ visit namespace_project_merge_request_path(project.namespace, project, noteable)
end
- context 'when typing @' do
- include_examples "open suggestions"
- before do
- open_member_suggestions
- end
- end
+ include_examples "open suggestions when typing @"
end
- context 'adding a new note on a Commit ', js: true do
- let(:commit) { project.commit }
+ context 'adding a new note on a Commit' do
+ let(:noteable) { project.commit }
+ let(:note) { create(:note_on_commit, project: project, commit_id: project.commit.id) }
before do
- allow(commit).to receive(:author).and_return(author)
- create(:note_on_commit, author: participant, project: project, commit_id: project.repository.commit.id, note: 'No More Parties in LA')
- visit_commit(project, commit)
- end
-
- context 'when typing @' do
- include_examples "open suggestions"
- before do
- open_member_suggestions
- end
- end
- end
+ allow_any_instance_of(Commit).to receive(:author).and_return(author)
- def open_member_suggestions
- page.within('.new-note') do
- find('#note_note').send_keys('@')
+ visit namespace_project_commit_path(project.namespace, project, noteable)
end
- wait_for_ajax
- end
-
- def visit_issue(project, issue)
- visit namespace_project_issue_path(project.namespace, project, issue)
- end
-
- def visit_merge_request(project, merge)
- visit namespace_project_merge_request_path(project.namespace, project, merge)
- end
- def visit_commit(project, commit)
- visit namespace_project_commit_path(project.namespace, project, commit)
+ include_examples "open suggestions when typing @"
end
end
diff --git a/spec/features/projects/deploy_keys_spec.rb b/spec/features/projects/deploy_keys_spec.rb
new file mode 100644
index 00000000000..0b997f130ea
--- /dev/null
+++ b/spec/features/projects/deploy_keys_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe 'Project deploy keys', feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project_empty_repo) }
+
+ before do
+ project.team << [user, :master]
+ login_as(user)
+ end
+
+ describe 'removing key' do
+ before do
+ create(:deploy_keys_project, project: project)
+ end
+
+ it 'removes association between project and deploy key' do
+ visit namespace_project_settings_repository_path(project.namespace, project)
+
+ page.within '.deploy-keys' do
+ expect { click_on 'Remove' }
+ .to change { project.deploy_keys.count }.by(-1)
+ end
+ end
+ end
+end
diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index a033ac04da6..e21f4ca2bc0 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -1,12 +1,12 @@
/* eslint-disable comma-dangle, one-var, no-unused-vars */
/* global BoardService */
/* global boardsMockInterceptor */
-/* global Cookies */
/* global listObj */
/* global listObjDuplicate */
/* global ListIssue */
import Vue from 'vue';
+import Cookies from 'js-cookie';
require('~/lib/utils/url_utility');
require('~/boards/models/issue');
diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
index 113161c21c6..848c7656a8d 100644
--- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
@@ -58,7 +58,7 @@ const FilteredSearchSpecHelper = require('../helpers/filtered_search_spec_helper
});
describe('search', () => {
- const defaultParams = '?scope=all&utf8=✓&state=opened';
+ const defaultParams = '?scope=all&utf8=%E2%9C%93&state=opened';
it('should search with a single word', (done) => {
input.value = 'searchTerm';
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 16df1ad4f28..15465588223 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -8,7 +8,6 @@ jasmine.getJSONFixtures().fixturesPath = 'base/spec/javascripts/fixtures';
require('~/commons/index.js');
window.$ = window.jQuery = require('jquery');
window._ = require('underscore');
-window.Cookies = require('js-cookie');
// stub expected globals
window.gl = window.gl || {};
diff --git a/spec/javascripts/user_callout_spec.js b/spec/javascripts/user_callout_spec.js
index 205e72af600..2398149d3ad 100644
--- a/spec/javascripts/user_callout_spec.js
+++ b/spec/javascripts/user_callout_spec.js
@@ -1,7 +1,7 @@
-const UserCallout = require('~/user_callout');
+import Cookies from 'js-cookie';
+import UserCallout from '~/user_callout';
const USER_CALLOUT_COOKIE = 'user_callout_dismissed';
-const Cookie = window.Cookies;
describe('UserCallout', function () {
const fixtureName = 'static/user_callout.html.raw';
@@ -9,7 +9,7 @@ describe('UserCallout', function () {
beforeEach(() => {
loadFixtures(fixtureName);
- Cookie.remove(USER_CALLOUT_COOKIE);
+ Cookies.remove(USER_CALLOUT_COOKIE);
this.userCallout = new UserCallout();
this.closeButton = $('.close-user-callout');
@@ -18,25 +18,25 @@ describe('UserCallout', function () {
});
it('does not show when cookie is set not defined', () => {
- expect(Cookie.get(USER_CALLOUT_COOKIE)).toBeUndefined();
+ expect(Cookies.get(USER_CALLOUT_COOKIE)).toBeUndefined();
expect(this.userCalloutContainer.is(':visible')).toBe(true);
});
it('shows when cookie is set to false', () => {
- Cookie.set(USER_CALLOUT_COOKIE, 'false');
+ Cookies.set(USER_CALLOUT_COOKIE, 'false');
- expect(Cookie.get(USER_CALLOUT_COOKIE)).toBeDefined();
+ expect(Cookies.get(USER_CALLOUT_COOKIE)).toBeDefined();
expect(this.userCalloutContainer.is(':visible')).toBe(true);
});
it('hides when user clicks on the dismiss-icon', () => {
this.closeButton.click();
- expect(Cookie.get(USER_CALLOUT_COOKIE)).toBe('true');
+ expect(Cookies.get(USER_CALLOUT_COOKIE)).toBe('true');
});
it('hides when user clicks on the "check it out" button', () => {
this.userCalloutBtn.click();
- expect(Cookie.get(USER_CALLOUT_COOKIE)).toBe('true');
+ expect(Cookies.get(USER_CALLOUT_COOKIE)).toBe('true');
});
});
@@ -46,7 +46,7 @@ describe('UserCallout when cookie is present', function () {
beforeEach(() => {
loadFixtures(fixtureName);
- Cookie.set(USER_CALLOUT_COOKIE, 'true');
+ Cookies.set(USER_CALLOUT_COOKIE, 'true');
this.userCallout = new UserCallout();
this.userCalloutContainer = $('.user-callout');
});
diff --git a/spec/mailers/emails/profile_spec.rb b/spec/mailers/emails/profile_spec.rb
index e1877d5fde0..5ca936f28f0 100644
--- a/spec/mailers/emails/profile_spec.rb
+++ b/spec/mailers/emails/profile_spec.rb
@@ -5,6 +5,16 @@ describe Notify do
include EmailSpec::Matchers
include_context 'gitlab email notification'
+ shared_examples 'a new user email' do
+ it 'is sent to the new user with the correct subject and body' do
+ aggregate_failures do
+ is_expected.to deliver_to new_user_address
+ is_expected.to have_subject(/^Account was created for you$/i)
+ is_expected.to have_body_text(new_user_address)
+ end
+ end
+ end
+
describe 'profile notifications' do
describe 'for new users, the email' do
let(:example_site_path) { root_path }
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 6ee91576676..4b72eb2eaa3 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -24,14 +24,14 @@ describe Notify do
let(:previous_assignee) { create(:user, name: 'Previous Assignee') }
shared_examples 'an assignee email' do
- it 'is sent as the author' do
- sender = subject.header[:from].addrs[0]
- expect(sender.display_name).to eq(current_user.name)
- expect(sender.address).to eq(gitlab_sender)
- end
+ it 'is sent to the assignee as the author' do
+ sender = subject.header[:from].addrs.first
- it 'is sent to the assignee' do
- is_expected.to deliver_to assignee.email
+ aggregate_failures do
+ expect(sender.display_name).to eq(current_user.name)
+ expect(sender.address).to eq(gitlab_sender)
+ expect(subject).to deliver_to(assignee.email)
+ end
end
end
@@ -49,12 +49,11 @@ describe Notify do
it_behaves_like 'it should show Gmail Actions View Issue link'
it_behaves_like 'an unsubscribeable thread'
- it 'has the correct subject' do
- is_expected.to have_referable_subject(issue)
- end
-
- it 'contains a link to the new issue' do
- is_expected.to have_body_text namespace_project_issue_path(project.namespace, project, issue)
+ it 'has the correct subject and body' do
+ aggregate_failures do
+ is_expected.to have_referable_subject(issue)
+ is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue))
+ end
end
context 'when enabled email_author_in_body' do
@@ -63,7 +62,7 @@ describe Notify do
end
it 'contains a link to note author' do
- is_expected.to have_html_escaped_body_text issue.author_name
+ is_expected.to have_html_escaped_body_text(issue.author_name)
is_expected.to have_body_text 'wrote:'
end
end
@@ -95,20 +94,13 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
- it 'has the correct subject' do
- is_expected.to have_referable_subject(issue, reply: true)
- end
-
- it 'contains the name of the previous assignee' do
- is_expected.to have_html_escaped_body_text previous_assignee.name
- end
-
- it 'contains the name of the new assignee' do
- is_expected.to have_html_escaped_body_text assignee.name
- end
-
- it 'contains a link to the issue' do
- is_expected.to have_body_text namespace_project_issue_path(project.namespace, project, issue)
+ it 'has the correct subject and body' do
+ aggregate_failures do
+ is_expected.to have_referable_subject(issue, reply: true)
+ is_expected.to have_html_escaped_body_text(previous_assignee.name)
+ is_expected.to have_html_escaped_body_text(assignee.name)
+ is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue))
+ end
end
end
@@ -129,16 +121,12 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
- it 'has the correct subject' do
- is_expected.to have_referable_subject(issue, reply: true)
- end
-
- it 'contains the names of the added labels' do
- is_expected.to have_body_text 'foo, bar, and baz'
- end
-
- it 'contains a link to the issue' do
- is_expected.to have_body_text namespace_project_issue_path(project.namespace, project, issue)
+ it 'has the correct subject and body' do
+ aggregate_failures do
+ is_expected.to have_referable_subject(issue, reply: true)
+ is_expected.to have_body_text('foo, bar, and baz')
+ is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue))
+ end
end
end
@@ -158,20 +146,13 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
- it 'has the correct subject' do
- is_expected.to have_referable_subject(issue, reply: true)
- end
-
- it 'contains the new status' do
- is_expected.to have_body_text status
- end
-
- it 'contains the user name' do
- is_expected.to have_html_escaped_body_text current_user.name
- end
-
- it 'contains a link to the issue' do
- is_expected.to have_body_text(namespace_project_issue_path project.namespace, project, issue)
+ it 'has the correct subject and body' do
+ aggregate_failures do
+ is_expected.to have_referable_subject(issue, reply: true)
+ is_expected.to have_body_text(status)
+ is_expected.to have_html_escaped_body_text(current_user.name)
+ is_expected.to have_body_text(namespace_project_issue_path project.namespace, project, issue)
+ end
end
end
@@ -189,18 +170,15 @@ describe Notify do
is_expected.to have_body_text 'Issue was moved to another project'
end
- it 'has the correct subject' do
- is_expected.to have_referable_subject(issue, reply: true)
- end
-
- it 'contains link to new issue' do
+ it 'has the correct subject and body' do
new_issue_url = namespace_project_issue_path(new_issue.project.namespace,
new_issue.project, new_issue)
- is_expected.to have_body_text new_issue_url
- end
- it 'contains a link to the original issue' do
- is_expected.to have_body_text namespace_project_issue_path(project.namespace, project, issue)
+ aggregate_failures do
+ is_expected.to have_referable_subject(issue, reply: true)
+ is_expected.to have_body_text(new_issue_url)
+ is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue))
+ end
end
end
end
@@ -220,20 +198,13 @@ describe Notify do
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'an unsubscribeable thread'
- it 'has the correct subject' do
- is_expected.to have_referable_subject(merge_request)
- end
-
- it 'contains a link to the new merge request' do
- is_expected.to have_body_text namespace_project_merge_request_path(project.namespace, project, merge_request)
- end
-
- it 'contains the source branch for the merge request' do
- is_expected.to have_body_text merge_request.source_branch
- end
-
- it 'contains the target branch for the merge request' do
- is_expected.to have_body_text merge_request.target_branch
+ it 'has the correct subject and body' do
+ aggregate_failures do
+ is_expected.to have_referable_subject(merge_request)
+ is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request))
+ is_expected.to have_body_text(merge_request.source_branch)
+ is_expected.to have_body_text(merge_request.target_branch)
+ end
end
context 'when enabled email_author_in_body' do
@@ -275,20 +246,13 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
- it 'has the correct subject' do
- is_expected.to have_referable_subject(merge_request, reply: true)
- end
-
- it 'contains the name of the previous assignee' do
- is_expected.to have_html_escaped_body_text previous_assignee.name
- end
-
- it 'contains the name of the new assignee' do
- is_expected.to have_html_escaped_body_text assignee.name
- end
-
- it 'contains a link to the merge request' do
- is_expected.to have_body_text namespace_project_merge_request_path(project.namespace, project, merge_request)
+ it 'has the correct subject and body' do
+ aggregate_failures do
+ is_expected.to have_referable_subject(merge_request, reply: true)
+ is_expected.to have_html_escaped_body_text(previous_assignee.name)
+ is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request))
+ is_expected.to have_html_escaped_body_text(assignee.name)
+ end
end
end
@@ -309,16 +273,10 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
- it 'has the correct subject' do
+ it 'has the correct subject and body' do
is_expected.to have_referable_subject(merge_request, reply: true)
- end
-
- it 'contains the names of the added labels' do
- is_expected.to have_body_text 'foo, bar, and baz'
- end
-
- it 'contains a link to the merge request' do
- is_expected.to have_body_text namespace_project_merge_request_path(project.namespace, project, merge_request)
+ is_expected.to have_body_text('foo, bar, and baz')
+ is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request))
end
end
@@ -338,20 +296,13 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
- it 'has the correct subject' do
- is_expected.to have_referable_subject(merge_request, reply: true)
- end
-
- it 'contains the new status' do
- is_expected.to have_body_text status
- end
-
- it 'contains the user name' do
- is_expected.to have_html_escaped_body_text current_user.name
- end
-
- it 'contains a link to the merge request' do
- is_expected.to have_body_text namespace_project_merge_request_path(project.namespace, project, merge_request)
+ it 'has the correct subject and body' do
+ aggregate_failures do
+ is_expected.to have_referable_subject(merge_request, reply: true)
+ is_expected.to have_body_text(status)
+ is_expected.to have_html_escaped_body_text(current_user.name)
+ is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request))
+ end
end
end
@@ -371,16 +322,12 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
- it 'has the correct subject' do
- is_expected.to have_referable_subject(merge_request, reply: true)
- end
-
- it 'contains the new status' do
- is_expected.to have_body_text 'merged'
- end
-
- it 'contains a link to the merge request' do
- is_expected.to have_body_text namespace_project_merge_request_path(project.namespace, project, merge_request)
+ it 'has the correct subject and body' do
+ aggregate_failures do
+ is_expected.to have_referable_subject(merge_request, reply: true)
+ is_expected.to have_body_text('merged')
+ is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request))
+ end
end
end
end
@@ -395,16 +342,10 @@ describe Notify do
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
- it 'has the correct subject' do
- is_expected.to have_subject "#{project.name} | Project was moved"
- end
-
- it 'contains name of project' do
+ it 'has the correct subject and body' do
+ is_expected.to have_subject("#{project.name} | Project was moved")
is_expected.to have_html_escaped_body_text project.name_with_namespace
- end
-
- it 'contains new user role' do
- is_expected.to have_body_text project.ssh_url_to_repo
+ is_expected.to have_body_text(project.ssh_url_to_repo)
end
end
@@ -597,14 +538,14 @@ describe Notify do
shared_examples 'a note email' do
it_behaves_like 'it should have Gmail Actions links'
- it 'is sent as the author' do
+ it 'is sent to the given recipient as the author' do
sender = subject.header[:from].addrs[0]
- expect(sender.display_name).to eq(note_author.name)
- expect(sender.address).to eq(gitlab_sender)
- end
- it 'is sent to the given recipient' do
- is_expected.to deliver_to recipient.notification_email
+ aggregate_failures do
+ expect(sender.display_name).to eq(note_author.name)
+ expect(sender.address).to eq(gitlab_sender)
+ expect(subject).to deliver_to(recipient.notification_email)
+ end
end
it 'contains the message from the note' do
@@ -641,12 +582,11 @@ describe Notify do
it_behaves_like 'it should show Gmail Actions View Commit link'
it_behaves_like 'a user cannot unsubscribe through footer link'
- it 'has the correct subject' do
- is_expected.to have_subject "Re: #{project.name} | #{commit.title.strip} (#{commit.short_id})"
- end
-
- it 'contains a link to the commit' do
- is_expected.to have_body_text commit.short_id
+ it 'has the correct subject and body' do
+ aggregate_failures do
+ is_expected.to have_subject("Re: #{project.name} | #{commit.title.strip} (#{commit.short_id})")
+ is_expected.to have_body_text(commit.short_id)
+ end
end
end
@@ -664,12 +604,11 @@ describe Notify do
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'an unsubscribeable thread'
- it 'has the correct subject' do
- is_expected.to have_referable_subject(merge_request, reply: true)
- end
-
- it 'contains a link to the merge request note' do
- is_expected.to have_body_text note_on_merge_request_path
+ it 'has the correct subject and body' do
+ aggregate_failures do
+ is_expected.to have_referable_subject(merge_request, reply: true)
+ is_expected.to have_body_text note_on_merge_request_path
+ end
end
end
@@ -687,12 +626,11 @@ describe Notify do
it_behaves_like 'it should show Gmail Actions View Issue link'
it_behaves_like 'an unsubscribeable thread'
- it 'has the correct subject' do
- is_expected.to have_referable_subject(issue, reply: true)
- end
-
- it 'contains a link to the issue note' do
- is_expected.to have_body_text note_on_issue_path
+ it 'has the correct subject and body' do
+ aggregate_failures do
+ is_expected.to have_referable_subject(issue, reply: true)
+ is_expected.to have_body_text(note_on_issue_path)
+ end
end
end
end
@@ -717,14 +655,14 @@ describe Notify do
it_behaves_like 'it should have Gmail Actions links'
- it 'is sent as the author' do
+ it 'is sent to the given recipient as the author' do
sender = subject.header[:from].addrs[0]
- expect(sender.display_name).to eq(note_author.name)
- expect(sender.address).to eq(gitlab_sender)
- end
- it 'is sent to the given recipient' do
- is_expected.to deliver_to recipient.notification_email
+ aggregate_failures do
+ expect(sender.display_name).to eq(note_author.name)
+ expect(sender.address).to eq(gitlab_sender)
+ expect(subject).to deliver_to(recipient.notification_email)
+ end
end
it 'contains the message from the note' do
@@ -934,21 +872,20 @@ describe Notify do
is_expected.to deliver_to 'new-email@mail.com'
end
- it 'has the correct subject' do
- is_expected.to have_subject 'Confirmation instructions | A Nice Suffix'
- end
-
- it 'includes a link to the site' do
- is_expected.to have_body_text example_site_path
+ it 'has the correct subject and body' do
+ aggregate_failures do
+ is_expected.to have_subject('Confirmation instructions | A Nice Suffix')
+ is_expected.to have_body_text(example_site_path)
+ end
end
end
describe 'email on push for a created branch' do
let(:example_site_path) { root_path }
let(:user) { create(:user) }
- let(:tree_path) { namespace_project_tree_path(project.namespace, project, "master") }
+ let(:tree_path) { namespace_project_tree_path(project.namespace, project, "empty-branch") }
- subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :create) }
+ subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/empty-branch', action: :create) }
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
@@ -961,12 +898,11 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
- it 'has the correct subject' do
- is_expected.to have_subject "[Git][#{project.full_path}] Pushed new branch master"
- end
-
- it 'contains a link to the branch' do
- is_expected.to have_body_text tree_path
+ it 'has the correct subject and body' do
+ aggregate_failures do
+ is_expected.to have_subject("[Git][#{project.full_path}] Pushed new branch empty-branch")
+ is_expected.to have_body_text(tree_path)
+ end
end
end
@@ -988,12 +924,11 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
- it 'has the correct subject' do
- is_expected.to have_subject "[Git][#{project.full_path}] Pushed new tag v1.0"
- end
-
- it 'contains a link to the tag' do
- is_expected.to have_body_text tree_path
+ it 'has the correct subject and body' do
+ aggregate_failures do
+ is_expected.to have_subject("[Git][#{project.full_path}] Pushed new tag v1.0")
+ is_expected.to have_body_text(tree_path)
+ end
end
end
@@ -1064,24 +999,14 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
- it 'has the correct subject' do
- is_expected.to have_subject "[Git][#{project.full_path}][master] #{commits.length} commits: Ruby files modified"
- end
-
- it 'includes commits list' do
- is_expected.to have_body_text 'Change some files'
- end
-
- it 'includes diffs with character-level highlighting' do
- is_expected.to have_body_text 'def</span> <span class="nf">archive_formats_regex'
- end
-
- it 'contains a link to the diff' do
- is_expected.to have_body_text diff_path
- end
-
- it 'does not contain the misleading footer' do
- is_expected.not_to have_body_text 'you are a member of'
+ it 'has the correct subject and body' do
+ aggregate_failures do
+ is_expected.to have_subject("[Git][#{project.full_path}][master] #{commits.length} commits: Ruby files modified")
+ is_expected.to have_body_text('Change some files')
+ is_expected.to have_body_text('def</span> <span class="nf">archive_formats_regex')
+ is_expected.to have_body_text(diff_path)
+ is_expected.not_to have_body_text('you are a member of')
+ end
end
context "when set to send from committer email if domain matches" do
@@ -1098,13 +1023,13 @@ describe Notify do
end
it "is sent from the committer email" do
- sender = subject.header[:from].addrs[0]
- expect(sender.address).to eq(user.email)
- end
+ from = subject.header[:from].addrs.first
+ reply = subject.header[:reply_to].addrs.first
- it "is set to reply to the committer email" do
- sender = subject.header[:reply_to].addrs[0]
- expect(sender.address).to eq(user.email)
+ aggregate_failures do
+ expect(from.address).to eq(user.email)
+ expect(reply.address).to eq(user.email)
+ end
end
end
@@ -1115,13 +1040,13 @@ describe Notify do
end
it "is sent from the default email" do
- sender = subject.header[:from].addrs[0]
- expect(sender.address).to eq(gitlab_sender)
- end
+ from = subject.header[:from].addrs.first
+ reply = subject.header[:reply_to].addrs.first
- it "is set to reply to the default email" do
- sender = subject.header[:reply_to].addrs[0]
- expect(sender.address).to eq(gitlab_sender_reply_to)
+ aggregate_failures do
+ expect(from.address).to eq(gitlab_sender)
+ expect(reply.address).to eq(gitlab_sender_reply_to)
+ end
end
end
@@ -1132,13 +1057,13 @@ describe Notify do
end
it "is sent from the default email" do
- sender = subject.header[:from].addrs[0]
- expect(sender.address).to eq(gitlab_sender)
- end
+ from = subject.header[:from].addrs.first
+ reply = subject.header[:reply_to].addrs.first
- it "is set to reply to the default email" do
- sender = subject.header[:reply_to].addrs[0]
- expect(sender.address).to eq(gitlab_sender_reply_to)
+ aggregate_failures do
+ expect(from.address).to eq(gitlab_sender)
+ expect(reply.address).to eq(gitlab_sender_reply_to)
+ end
end
end
end
@@ -1166,20 +1091,13 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
- it 'has the correct subject' do
- is_expected.to have_subject "[Git][#{project.full_path}][master] #{commits.first.title}"
- end
-
- it 'includes commits list' do
- is_expected.to have_body_text 'Change some files'
- end
-
- it 'includes diffs with character-level highlighting' do
- is_expected.to have_body_text 'def</span> <span class="nf">archive_formats_regex'
- end
-
- it 'contains a link to the diff' do
- is_expected.to have_body_text diff_path
+ it 'has the correct subject and body' do
+ aggregate_failures do
+ is_expected.to have_subject("[Git][#{project.full_path}][master] #{commits.first.title}")
+ is_expected.to have_body_text('Change some files')
+ is_expected.to have_body_text('def</span> <span class="nf">archive_formats_regex')
+ is_expected.to have_body_text(diff_path)
+ end
end
end
diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb
index 22115c6566d..d841bdaa292 100644
--- a/spec/services/boards/issues/list_service_spec.rb
+++ b/spec/services/boards/issues/list_service_spec.rb
@@ -30,6 +30,7 @@ describe Boards::Issues::ListService, services: true do
let!(:closed_issue2) { create(:labeled_issue, :closed, project: project, labels: [p3]) }
let!(:closed_issue3) { create(:issue, :closed, project: project) }
let!(:closed_issue4) { create(:labeled_issue, :closed, project: project, labels: [p1]) }
+ let!(:closed_issue5) { create(:labeled_issue, :closed, project: project, labels: [development]) }
before do
project.team << [user, :developer]
@@ -57,7 +58,7 @@ describe Boards::Issues::ListService, services: true do
issues = described_class.new(project, user, params).execute
- expect(issues).to eq [closed_issue4, closed_issue2, closed_issue3, closed_issue1]
+ expect(issues).to eq [closed_issue4, closed_issue2, closed_issue5, closed_issue3, closed_issue1]
end
it 'returns opened issues that have label list applied when listing issues from a label list' do
diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb
index 0475f38fe5e..7a1ac027310 100644
--- a/spec/services/issuable/bulk_update_service_spec.rb
+++ b/spec/services/issuable/bulk_update_service_spec.rb
@@ -138,7 +138,7 @@ describe Issuable::BulkUpdateService, services: true do
let(:labels) { [bug, regression] }
it 'updates the labels of all issues passed to the labels passed' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(eq(labels.map(&:id)))
+ expect(issues.map(&:reload).map(&:label_ids)).to all(match_array(labels.map(&:id)))
end
it 'does not update issues not passed in' do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index ceb3209331f..5ab8f0d981a 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -35,7 +35,8 @@ RSpec.configure do |config|
config.include Warden::Test::Helpers, type: :request
config.include LoginHelpers, type: :feature
config.include SearchHelpers, type: :feature
- config.include WaitForAjax, type: :feature
+ config.include WaitForRequests, :js
+ config.include WaitForAjax, :js
config.include StubConfiguration
config.include EmailHelpers, type: :mailer
config.include TestEnv
diff --git a/spec/support/notify_shared_examples.rb b/spec/support/notify_shared_examples.rb
index a3724b801b3..16a425f2ca2 100644
--- a/spec/support/notify_shared_examples.rb
+++ b/spec/support/notify_shared_examples.rb
@@ -27,24 +27,14 @@ shared_examples 'a multiple recipients email' do
end
shared_examples 'an email sent from GitLab' do
- it 'is sent from GitLab' do
+ it 'has the characteristics of an email sent from GitLab' do
sender = subject.header[:from].addrs[0]
- expect(sender.display_name).to eq(gitlab_sender_display_name)
- expect(sender.address).to eq(gitlab_sender)
- end
-
- it 'has a Reply-To address' do
reply_to = subject.header[:reply_to].addresses
- expect(reply_to).to eq([gitlab_sender_reply_to])
- end
-
- context 'when custom suffix for email subject is set' do
- before do
- stub_config_setting(email_subject_suffix: 'A Nice Suffix')
- end
- it 'ends the subject with the suffix' do
- is_expected.to have_subject /\ \| A Nice Suffix$/
+ aggregate_failures do
+ expect(sender.display_name).to eq(gitlab_sender_display_name)
+ expect(sender.address).to eq(gitlab_sender)
+ expect(reply_to).to eq([gitlab_sender_reply_to])
end
end
end
@@ -56,43 +46,40 @@ shared_examples 'an email that contains a header with author username' do
end
shared_examples 'an email with X-GitLab headers containing project details' do
- it 'has X-GitLab-Project* headers' do
- is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
- is_expected.to have_header 'X-GitLab-Project-Id', /#{project.id}/
- is_expected.to have_header 'X-GitLab-Project-Path', /#{project.path_with_namespace}/
+ it 'has X-GitLab-Project headers' do
+ aggregate_failures do
+ is_expected.to have_header('X-GitLab-Project', /#{project.name}/)
+ is_expected.to have_header('X-GitLab-Project-Id', /#{project.id}/)
+ is_expected.to have_header('X-GitLab-Project-Path', /#{project.path_with_namespace}/)
+ end
end
end
shared_examples 'a new thread email with reply-by-email enabled' do
- let(:regex) { /\A<reply\-(.*)@#{Gitlab.config.gitlab.host}>\Z/ }
-
- it 'has a Message-ID header' do
- is_expected.to have_header 'Message-ID', "<#{model.class.model_name.singular_route_key}_#{model.id}@#{Gitlab.config.gitlab.host}>"
- end
+ it 'has the characteristics of a threaded email' do
+ host = Gitlab.config.gitlab.host
+ route_key = "#{model.class.model_name.singular_route_key}_#{model.id}"
- it 'has a References header' do
- is_expected.to have_header 'References', regex
+ aggregate_failures do
+ is_expected.to have_header('Message-ID', "<#{route_key}@#{host}>")
+ is_expected.to have_header('References', /\A<reply\-.*@#{host}>\Z/ )
+ end
end
end
shared_examples 'a thread answer email with reply-by-email enabled' do
include_examples 'an email with X-GitLab headers containing project details'
- let(:regex) { /\A<#{model.class.model_name.singular_route_key}_#{model.id}@#{Gitlab.config.gitlab.host}> <reply\-(.*)@#{Gitlab.config.gitlab.host}>\Z/ }
-
- it 'has a Message-ID header' do
- is_expected.to have_header 'Message-ID', /\A<(.*)@#{Gitlab.config.gitlab.host}>\Z/
- end
-
- it 'has a In-Reply-To header' do
- is_expected.to have_header 'In-Reply-To', "<#{model.class.model_name.singular_route_key}_#{model.id}@#{Gitlab.config.gitlab.host}>"
- end
- it 'has a References header' do
- is_expected.to have_header 'References', regex
- end
+ it 'has the characteristics of a threaded reply' do
+ host = Gitlab.config.gitlab.host
+ route_key = "#{model.class.model_name.singular_route_key}_#{model.id}"
- it 'has a subject that begins with Re: ' do
- is_expected.to have_subject /^Re: /
+ aggregate_failures do
+ is_expected.to have_header('Message-ID', /\A<.*@#{host}>\Z/)
+ is_expected.to have_header('In-Reply-To', "<#{route_key}@#{host}>")
+ is_expected.to have_header('References', /\A<#{route_key}@#{host}> <reply\-.*@#{host}>\Z/ )
+ is_expected.to have_subject(/^Re: /)
+ end
end
end
@@ -136,80 +123,77 @@ shared_examples 'an answer to an existing thread with reply-by-email enabled' do
end
end
-shared_examples 'a new user email' do
- it 'is sent to the new user' do
- is_expected.to deliver_to new_user_address
- end
-
- it 'has the correct subject' do
- is_expected.to have_subject /^Account was created for you$/i
- end
-
- it 'contains the new user\'s login name' do
- is_expected.to have_body_text /#{new_user_address}/
- end
-end
-
shared_examples 'it should have Gmail Actions links' do
- it { is_expected.to have_body_text '<script type="application/ld+json">' }
- it { is_expected.to have_body_text /ViewAction/ }
+ it do
+ aggregate_failures do
+ is_expected.to have_body_text('<script type="application/ld+json">')
+ is_expected.to have_body_text('ViewAction')
+ end
+ end
end
shared_examples 'it should not have Gmail Actions links' do
- it { is_expected.not_to have_body_text '<script type="application/ld+json">' }
- it { is_expected.not_to have_body_text /ViewAction/ }
+ it do
+ aggregate_failures do
+ is_expected.not_to have_body_text('<script type="application/ld+json">')
+ is_expected.not_to have_body_text('ViewAction')
+ end
+ end
end
shared_examples 'it should show Gmail Actions View Issue link' do
it_behaves_like 'it should have Gmail Actions links'
- it { is_expected.to have_body_text /View Issue/ }
+ it { is_expected.to have_body_text('View Issue') }
end
shared_examples 'it should show Gmail Actions View Merge request link' do
it_behaves_like 'it should have Gmail Actions links'
- it { is_expected.to have_body_text /View Merge request/ }
+ it { is_expected.to have_body_text('View Merge request') }
end
shared_examples 'it should show Gmail Actions View Commit link' do
it_behaves_like 'it should have Gmail Actions links'
- it { is_expected.to have_body_text /View Commit/ }
+ it { is_expected.to have_body_text('View Commit') }
end
shared_examples 'an unsubscribeable thread' do
it_behaves_like 'an unsubscribeable thread with incoming address without %{key}'
- it 'has a List-Unsubscribe header in the correct format' do
- is_expected.to have_header 'List-Unsubscribe', /unsubscribe/
- is_expected.to have_header 'List-Unsubscribe', /mailto/
- is_expected.to have_header 'List-Unsubscribe', /^<.+,.+>$/
+ it 'has a List-Unsubscribe header in the correct format, and a body link' do
+ aggregate_failures do
+ is_expected.to have_header('List-Unsubscribe', /unsubscribe/)
+ is_expected.to have_header('List-Unsubscribe', /mailto/)
+ is_expected.to have_header('List-Unsubscribe', /^<.+,.+>$/)
+ is_expected.to have_body_text('unsubscribe')
+ end
end
-
- it { is_expected.to have_body_text /unsubscribe/ }
end
shared_examples 'an unsubscribeable thread with incoming address without %{key}' do
include_context 'reply-by-email is enabled with incoming address without %{key}'
- it 'has a List-Unsubscribe header in the correct format' do
- is_expected.to have_header 'List-Unsubscribe', /unsubscribe/
- is_expected.not_to have_header 'List-Unsubscribe', /mailto/
- is_expected.to have_header 'List-Unsubscribe', /^<[^,]+>$/
+ it 'has a List-Unsubscribe header in the correct format, and a body link' do
+ aggregate_failures do
+ is_expected.to have_header('List-Unsubscribe', /unsubscribe/)
+ is_expected.not_to have_header('List-Unsubscribe', /mailto/)
+ is_expected.to have_header('List-Unsubscribe', /^<[^,]+>$/)
+ is_expected.to have_body_text('unsubscribe')
+ end
end
-
- it { is_expected.to have_body_text /unsubscribe/ }
end
shared_examples 'a user cannot unsubscribe through footer link' do
- it 'does not have a List-Unsubscribe header' do
- is_expected.not_to have_header 'List-Unsubscribe', /unsubscribe/
+ it 'does not have a List-Unsubscribe header or a body link' do
+ aggregate_failures do
+ is_expected.not_to have_header('List-Unsubscribe', /unsubscribe/)
+ is_expected.not_to have_body_text('unsubscribe')
+ end
end
-
- it { is_expected.not_to have_body_text /unsubscribe/ }
end
shared_examples 'an email with a labels subscriptions link in its footer' do
- it { is_expected.to have_body_text /label subscriptions/ }
+ it { is_expected.to have_body_text('label subscriptions') }
end
diff --git a/spec/support/slack_mattermost_notifications_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb
index 704922b6cf4..b902fe90707 100644
--- a/spec/support/slack_mattermost_notifications_shared_examples.rb
+++ b/spec/support/slack_mattermost_notifications_shared_examples.rb
@@ -324,5 +324,24 @@ RSpec.shared_examples 'slack or mattermost notifications' do
it_behaves_like 'call Slack/Mattermost API'
end
end
+
+ context 'only notify for the default branch' do
+ context 'when enabled' do
+ let(:pipeline) do
+ create(:ci_pipeline, project: project, status: 'failed', ref: 'not-the-default-branch')
+ end
+
+ before do
+ chat_service.notify_only_default_branch = true
+ end
+
+ it 'does not call the Slack/Mattermost API for pipeline events' do
+ data = Gitlab::DataBuilder::Pipeline.build(pipeline)
+ result = chat_service.execute(data)
+
+ expect(result).to be_falsy
+ end
+ end
+ end
end
end
diff --git a/spec/support/wait_for_requests.rb b/spec/support/wait_for_requests.rb
new file mode 100644
index 00000000000..0bfa7f72ff8
--- /dev/null
+++ b/spec/support/wait_for_requests.rb
@@ -0,0 +1,32 @@
+module WaitForRequests
+ extend self
+
+ # This is inspired by http://www.salsify.com/blog/engineering/tearing-capybara-ajax-tests
+ def wait_for_requests_complete
+ Gitlab::Testing::RequestBlockerMiddleware.block_requests!
+ wait_for('pending AJAX requests complete') do
+ Gitlab::Testing::RequestBlockerMiddleware.num_active_requests.zero?
+ end
+ ensure
+ Gitlab::Testing::RequestBlockerMiddleware.allow_requests!
+ end
+
+ # Waits until the passed block returns true
+ def wait_for(condition_name, max_wait_time: Capybara.default_max_wait_time, polling_interval: 0.01)
+ wait_until = Time.now + max_wait_time.seconds
+ loop do
+ break if yield
+ if Time.now > wait_until
+ raise "Condition not met: #{condition_name}"
+ else
+ sleep(polling_interval)
+ end
+ end
+ end
+end
+
+RSpec.configure do |config|
+ config.after(:each, :js) do
+ wait_for_requests_complete
+ end
+end
diff --git a/vendor/assets/javascripts/js.cookie.js b/vendor/assets/javascripts/js.cookie.js
deleted file mode 100644
index 92dbba162c4..00000000000
--- a/vendor/assets/javascripts/js.cookie.js
+++ /dev/null
@@ -1,156 +0,0 @@
-/*!
- * JavaScript Cookie v2.1.3
- * https://github.com/js-cookie/js-cookie
- *
- * Copyright 2006, 2015 Klaus Hartl & Fagner Brack
- * Released under the MIT license
- */
-;(function (factory) {
- var registeredInModuleLoader = false;
- if (typeof define === 'function' && define.amd) {
- define(factory);
- registeredInModuleLoader = true;
- }
- if (typeof exports === 'object') {
- module.exports = factory();
- registeredInModuleLoader = true;
- }
- if (!registeredInModuleLoader) {
- var OldCookies = window.Cookies;
- var api = window.Cookies = factory();
- api.noConflict = function () {
- window.Cookies = OldCookies;
- return api;
- };
- }
-}(function () {
- function extend () {
- var i = 0;
- var result = {};
- for (; i < arguments.length; i++) {
- var attributes = arguments[ i ];
- for (var key in attributes) {
- result[key] = attributes[key];
- }
- }
- return result;
- }
-
- function init (converter) {
- function api (key, value, attributes) {
- var result;
- if (typeof document === 'undefined') {
- return;
- }
-
- // Write
-
- if (arguments.length > 1) {
- attributes = extend({
- path: '/'
- }, api.defaults, attributes);
-
- if (typeof attributes.expires === 'number') {
- var expires = new Date();
- expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5);
- attributes.expires = expires;
- }
-
- try {
- result = JSON.stringify(value);
- if (/^[\{\[]/.test(result)) {
- value = result;
- }
- } catch (e) {}
-
- if (!converter.write) {
- value = encodeURIComponent(String(value))
- .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent);
- } else {
- value = converter.write(value, key);
- }
-
- key = encodeURIComponent(String(key));
- key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent);
- key = key.replace(/[\(\)]/g, escape);
-
- return (document.cookie = [
- key, '=', value,
- attributes.expires ? '; expires=' + attributes.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
- attributes.path ? '; path=' + attributes.path : '',
- attributes.domain ? '; domain=' + attributes.domain : '',
- attributes.secure ? '; secure' : ''
- ].join(''));
- }
-
- // Read
-
- if (!key) {
- result = {};
- }
-
- // To prevent the for loop in the first place assign an empty array
- // in case there are no cookies at all. Also prevents odd result when
- // calling "get()"
- var cookies = document.cookie ? document.cookie.split('; ') : [];
- var rdecode = /(%[0-9A-Z]{2})+/g;
- var i = 0;
-
- for (; i < cookies.length; i++) {
- var parts = cookies[i].split('=');
- var cookie = parts.slice(1).join('=');
-
- if (cookie.charAt(0) === '"') {
- cookie = cookie.slice(1, -1);
- }
-
- try {
- var name = parts[0].replace(rdecode, decodeURIComponent);
- cookie = converter.read ?
- converter.read(cookie, name) : converter(cookie, name) ||
- cookie.replace(rdecode, decodeURIComponent);
-
- if (this.json) {
- try {
- cookie = JSON.parse(cookie);
- } catch (e) {}
- }
-
- if (key === name) {
- result = cookie;
- break;
- }
-
- if (!key) {
- result[name] = cookie;
- }
- } catch (e) {}
- }
-
- return result;
- }
-
- api.set = api;
- api.get = function (key) {
- return api.call(api, key);
- };
- api.getJSON = function () {
- return api.apply({
- json: true
- }, [].slice.call(arguments));
- };
- api.defaults = {};
-
- api.remove = function (key, attributes) {
- api(key, '', extend(attributes, {
- expires: -1
- }));
- };
-
- api.withConverter = init;
-
- return api;
- }
-
- return init(function () {});
-})); \ No newline at end of file