summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock2
-rw-r--r--app/assets/javascripts/ide/components/external_link.vue41
-rw-r--r--app/assets/javascripts/ide/components/ide_file_buttons.vue84
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue6
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss10
-rw-r--r--app/assets/stylesheets/framework/gitlab_theme.scss11
-rw-r--r--app/assets/stylesheets/framework/secondary_navigation_elements.scss13
-rw-r--r--app/controllers/concerns/issues_action.rb15
-rw-r--r--app/controllers/profiles_controller.rb6
-rw-r--r--app/controllers/projects/issues_controller.rb15
-rw-r--r--app/finders/issues_finder.rb6
-rw-r--r--app/helpers/calendar_helper.rb8
-rw-r--r--app/helpers/rss_helper.rb2
-rw-r--r--app/models/concerns/issuable.rb2
-rw-r--r--app/models/issue.rb17
-rw-r--r--app/models/merge_request.rb1
-rw-r--r--app/models/user.rb8
-rw-r--r--app/presenters/commit_status_presenter.rb3
-rw-r--r--app/serializers/job_entity.rb2
-rw-r--r--app/services/boards/issues/list_service.rb8
-rw-r--r--app/views/dashboard/issues.html.haml3
-rw-r--r--app/views/dashboard/issues_calendar.ics.haml1
-rw-r--r--app/views/groups/issues.html.haml5
-rw-r--r--app/views/groups/issues_calendar.ics.haml1
-rw-r--r--app/views/issues/_issues_calendar.ics.ruby15
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml14
-rw-r--r--app/views/projects/issues/_nav_btns.html.haml4
-rw-r--r--app/views/projects/issues/calendar.ics.haml1
-rw-r--r--app/views/shared/icons/_icon_calendar.svg1
-rw-r--r--app/views/shared/issuable/_feed_buttons.html.haml4
-rw-r--r--app/views/shared/milestones/_tabs.html.haml20
-rw-r--r--changelogs/unreleased/41587-osw-mr-metrics-migration-cleanup.yml5
-rw-r--r--changelogs/unreleased/44184-issues_ical_feed.yml5
-rw-r--r--changelogs/unreleased/45520-remove-links-from-web-ide.yml5
-rw-r--r--changelogs/unreleased/46552-fixes-redundant-message-for-failure-reasons.yml5
-rw-r--r--changelogs/unreleased/issue_45082.yml5
-rw-r--r--changelogs/unreleased/rails5-fix-46230.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-api-perf-n-plus-one.yml5
-rw-r--r--config/routes/dashboard.rb1
-rw-r--r--config/routes/group.rb3
-rw-r--r--config/routes/profile.rb2
-rw-r--r--config/routes/project.rb1
-rw-r--r--db/migrate/20180408143354_rename_users_rss_token_to_feed_token.rb15
-rw-r--r--db/post_migrate/20180408143355_cleanup_users_rss_token_rename.rb13
-rw-r--r--db/post_migrate/20180521162137_migrate_remaining_mr_metrics_populating_background_migration.rb44
-rw-r--r--db/schema.rb4
-rw-r--r--doc/api/merge_requests.md106
-rw-r--r--doc/development/new_fe_guide/tips.md8
-rw-r--r--doc/development/testing_guide/best_practices.md4
-rw-r--r--doc/user/project/issues/due_dates.md8
-rw-r--r--lib/api/issues.rb2
-rw-r--r--lib/api/merge_requests.rb49
-rw-r--r--lib/gitlab/auth/request_authenticator.rb2
-rw-r--r--lib/gitlab/auth/user_auth_finders.rb18
-rw-r--r--lib/gitlab/contributions_calendar.rb2
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb2
-rw-r--r--lib/gitlab/import_export/import_export.yml1
-rw-r--r--lib/gitlab/import_export/relation_factory.rb14
-rw-r--r--lib/support/nginx/gitlab16
-rw-r--r--lib/support/nginx/gitlab-ssl16
-rw-r--r--lib/tasks/tokens.rake10
-rw-r--r--package.json1
-rw-r--r--spec/controllers/application_controller_spec.rb28
-rw-r--r--spec/factories/users.rb4
-rw-r--r--spec/features/atom/dashboard_issues_spec.rb12
-rw-r--r--spec/features/atom/dashboard_spec.rb6
-rw-r--r--spec/features/atom/issues_spec.rb13
-rw-r--r--spec/features/atom/users_spec.rb6
-rw-r--r--spec/features/dashboard/activity_spec.rb4
-rw-r--r--spec/features/dashboard/issues_filter_spec.rb6
-rw-r--r--spec/features/dashboard/issues_spec.rb4
-rw-r--r--spec/features/dashboard/projects_spec.rb2
-rw-r--r--spec/features/groups/activity_spec.rb8
-rw-r--r--spec/features/groups/issues_spec.rb16
-rw-r--r--spec/features/groups/show_spec.rb4
-rw-r--r--spec/features/ics/dashboard_issues_spec.rb65
-rw-r--r--spec/features/ics/group_issues_spec.rb67
-rw-r--r--spec/features/ics/project_issues_spec.rb66
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb4
-rw-r--r--spec/features/issues_spec.rb14
-rw-r--r--spec/features/profile_spec.rb12
-rw-r--r--spec/features/projects/activity/rss_spec.rb4
-rw-r--r--spec/features/projects/commits/rss_spec.rb8
-rw-r--r--spec/features/projects/issues/rss_spec.rb8
-rw-r--r--spec/features/projects/milestones/milestone_spec.rb8
-rw-r--r--spec/features/projects/show/rss_spec.rb4
-rw-r--r--spec/features/projects/tree/rss_spec.rb4
-rw-r--r--spec/features/users/rss_spec.rb4
-rw-r--r--spec/helpers/calendar_helper_spec.rb20
-rw-r--r--spec/helpers/rss_helper_spec.rb8
-rw-r--r--spec/javascripts/ide/components/external_link_spec.js35
-rw-r--r--spec/javascripts/ide/components/ide_file_buttons_spec.js61
-rw-r--r--spec/lib/gitlab/auth/request_authenticator_spec.rb10
-rw-r--r--spec/lib/gitlab/auth/user_auth_finders_spec.rb46
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb6
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb4
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml5
-rw-r--r--spec/lib/gitlab/import_export/relation_factory_spec.rb19
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml13
-rw-r--r--spec/migrations/migrate_remaining_mr_metrics_populating_background_migration_spec.rb36
-rw-r--r--spec/models/concerns/token_authenticatable_spec.rb2
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb12
-rw-r--r--spec/models/user_spec.rb12
-rw-r--r--spec/presenters/ci/build_presenter_spec.rb4
-rw-r--r--spec/requests/api/merge_requests_spec.rb242
-rw-r--r--spec/requests/rack_attack_global_spec.rb2
-rw-r--r--spec/routing/routing_spec.rb10
-rw-r--r--spec/serializers/job_entity_spec.rb25
-rw-r--r--spec/support/features/rss_shared_examples.rb24
-rw-r--r--spec/support/gitlab-git-test.git/packed-refs1
-rw-r--r--spec/support/helpers/seed_repo.rb1
-rw-r--r--spec/support/import_export/configuration_helper.rb2
-rw-r--r--spec/support/shared_examples/requests/api/merge_requests_list.rb280
-rw-r--r--spec/tasks/tokens_spec.rb4
118 files changed, 1353 insertions, 628 deletions
diff --git a/.gitignore b/.gitignore
index c7d1648615d..51b77d5ac9e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -64,6 +64,7 @@ eslint-report.html
/tags
/tmp/*
/vendor/bundle/*
+/vendor/gitaly-ruby
/builds*
/shared/*
/.gitlab_workhorse_secret
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 89eba2c5b85..e49057b3302 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.103.0
+0.104.0
diff --git a/Gemfile b/Gemfile
index 55858420242..68c7b3dcb08 100644
--- a/Gemfile
+++ b/Gemfile
@@ -144,6 +144,9 @@ gem 'truncato', '~> 0.7.9'
gem 'bootstrap_form', '~> 2.7.0'
gem 'nokogiri', '~> 1.8.2'
+# Calendar rendering
+gem 'icalendar'
+
# Diffs
gem 'diffy', '~> 3.1.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index fe20570ae89..4d2bd62bec0 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -410,6 +410,7 @@ GEM
httpclient (2.8.3)
i18n (0.9.5)
concurrent-ruby (~> 1.0)
+ icalendar (2.4.1)
ice_nine (0.11.2)
influxdb (0.2.3)
cause
@@ -1060,6 +1061,7 @@ DEPENDENCIES
html-pipeline (~> 2.7.1)
html2text
httparty (~> 0.13.3)
+ icalendar
influxdb (~> 0.2)
jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2)
diff --git a/app/assets/javascripts/ide/components/external_link.vue b/app/assets/javascripts/ide/components/external_link.vue
new file mode 100644
index 00000000000..cf3316a8179
--- /dev/null
+++ b/app/assets/javascripts/ide/components/external_link.vue
@@ -0,0 +1,41 @@
+<script>
+import Icon from '~/vue_shared/components/icon.vue';
+
+export default {
+ components: {
+ Icon,
+ },
+ props: {
+ file: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ showButtons() {
+ return this.file.permalink;
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ v-if="showButtons"
+ class="pull-right ide-btn-group"
+ >
+ <a
+ :href="file.permalink"
+ target="_blank"
+ :title="s__('IDE|Open in file view')"
+ rel="noopener noreferrer"
+ >
+ <span class="vertical-align-middle">Open in file view</span>
+ <icon
+ name="external-link"
+ css-classes="vertical-align-middle space-right"
+ :size="16"
+ />
+ </a>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/ide_file_buttons.vue b/app/assets/javascripts/ide/components/ide_file_buttons.vue
deleted file mode 100644
index 30b00abf6ed..00000000000
--- a/app/assets/javascripts/ide/components/ide_file_buttons.vue
+++ /dev/null
@@ -1,84 +0,0 @@
-<script>
-import { __ } from '~/locale';
-import tooltip from '~/vue_shared/directives/tooltip';
-import Icon from '~/vue_shared/components/icon.vue';
-
-export default {
- components: {
- Icon,
- },
- directives: {
- tooltip,
- },
- props: {
- file: {
- type: Object,
- required: true,
- },
- },
- computed: {
- showButtons() {
- return (
- this.file.rawPath || this.file.blamePath || this.file.commitsPath || this.file.permalink
- );
- },
- rawDownloadButtonLabel() {
- return this.file.binary ? __('Download') : __('Raw');
- },
- },
-};
-</script>
-
-<template>
- <div
- v-if="showButtons"
- class="float-right ide-btn-group"
- >
- <a
- v-tooltip
- v-if="!file.binary"
- :href="file.blamePath"
- :title="__('Blame')"
- class="btn btn-sm btn-transparent blame"
- >
- <icon
- name="blame"
- :size="16"
- />
- </a>
- <a
- v-tooltip
- :href="file.commitsPath"
- :title="__('History')"
- class="btn btn-sm btn-transparent history"
- >
- <icon
- name="history"
- :size="16"
- />
- </a>
- <a
- v-tooltip
- :href="file.permalink"
- :title="__('Permalink')"
- class="btn btn-sm btn-transparent permalink"
- >
- <icon
- name="link"
- :size="16"
- />
- </a>
- <a
- v-tooltip
- :href="file.rawPath"
- target="_blank"
- class="btn btn-sm btn-transparent prepend-left-10 raw"
- rel="noopener noreferrer"
- :title="rawDownloadButtonLabel">
- <icon
- name="download"
- :size="16"
- />
- </a>
- </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index a281ecb95c8..93453989c08 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -6,12 +6,12 @@ import ContentViewer from '~/vue_shared/components/content_viewer/content_viewer
import { activityBarViews, viewerTypes } from '../constants';
import monacoLoader from '../monaco_loader';
import Editor from '../lib/editor';
-import IdeFileButtons from './ide_file_buttons.vue';
+import ExternalLink from './external_link.vue';
export default {
components: {
ContentViewer,
- IdeFileButtons,
+ ExternalLink,
},
props: {
file: {
@@ -224,7 +224,7 @@ export default {
</a>
</li>
</ul>
- <ide-file-buttons
+ <external-link
:file="file"
/>
</div>
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index e1a47f3d686..a40f4ea4f4b 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -148,8 +148,14 @@ table {
}
}
-.nav-tabs .nav-link {
- border: 0;
+.nav-tabs {
+ .nav-link {
+ border: 0;
+ }
+
+ .nav-item {
+ margin-bottom: 0;
+ }
}
pre code {
diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss
index d6ae8cbb416..3bbb50117bc 100644
--- a/app/assets/stylesheets/framework/gitlab_theme.scss
+++ b/app/assets/stylesheets/framework/gitlab_theme.scss
@@ -169,11 +169,14 @@
color: $color-800;
}
- .nav-links li a.active {
- border-bottom: 2px solid $color-500;
+ .nav-links li {
+ &.active a,
+ a.active {
+ border-bottom: 2px solid $color-500;
- .badge.badge-pill {
- font-weight: $gl-font-weight-bold;
+ .badge.badge-pill {
+ font-weight: $gl-font-weight-bold;
+ }
}
}
diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
index c3c64adf3da..847fc8c0792 100644
--- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss
+++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
@@ -31,14 +31,15 @@
color: $black;
}
}
+ }
- &.active {
- color: $black;
- font-weight: $gl-font-weight-bold;
+ &.active a,
+ a.active {
+ color: $black;
+ font-weight: $gl-font-weight-bold;
- .badge.badge-pill {
- color: $black;
- }
+ .badge.badge-pill {
+ color: $black;
}
}
}
diff --git a/app/controllers/concerns/issues_action.rb b/app/controllers/concerns/issues_action.rb
index 3b11a373368..b6eb7d292fc 100644
--- a/app/controllers/concerns/issues_action.rb
+++ b/app/controllers/concerns/issues_action.rb
@@ -17,10 +17,23 @@ module IssuesAction
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def issues_calendar
+ @issues = issuables_collection
+ .non_archived
+ .with_due_date
+ .limit(100)
+
+ respond_to do |format|
+ format.ics { response.headers['Content-Disposition'] = 'inline' }
+ end
+ end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+
private
def finder_type
(super if defined?(super)) ||
- (IssuesFinder if action_name == 'issues')
+ (IssuesFinder if %w(issues issues_calendar).include?(action_name))
end
end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 9f5ad23a20f..074db361949 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -34,12 +34,12 @@ class ProfilesController < Profiles::ApplicationController
redirect_to profile_personal_access_tokens_path
end
- def reset_rss_token
+ def reset_feed_token
Users::UpdateService.new(current_user, user: @user).execute! do |user|
- user.reset_rss_token!
+ user.reset_feed_token!
end
- flash[:notice] = "RSS token was successfully reset"
+ flash[:notice] = 'Feed token was successfully reset'
redirect_to profile_personal_access_tokens_path
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index d69015c8665..35c36c725e2 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -10,8 +10,8 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update]
before_action :check_issues_available!
- before_action :issue, except: [:index, :new, :create, :bulk_update]
- before_action :set_issuables_index, only: [:index]
+ before_action :issue, except: [:index, :calendar, :new, :create, :bulk_update]
+ before_action :set_issuables_index, only: [:index, :calendar]
# Allow write(create) issue
before_action :authorize_create_issue!, only: [:new, :create]
@@ -39,6 +39,17 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
+ def calendar
+ @issues = @issuables
+ .non_archived
+ .with_due_date
+ .limit(100)
+
+ respond_to do |format|
+ format.ics { response.headers['Content-Disposition'] = 'inline' }
+ end
+ end
+
def new
params[:issue] ||= ActionController::Parameters.new(
assignee_ids: ""
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index 1787b4899cd..3626670d141 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -75,6 +75,8 @@ class IssuesFinder < IssuableFinder
items = items.due_between(Date.today.beginning_of_week, Date.today.end_of_week)
elsif filter_by_due_this_month?
items = items.due_between(Date.today.beginning_of_month, Date.today.end_of_month)
+ elsif filter_by_due_next_month_and_previous_two_weeks?
+ items = items.due_between(Date.today - 2.weeks, (Date.today + 1.month).end_of_month)
end
end
@@ -97,6 +99,10 @@ class IssuesFinder < IssuableFinder
due_date? && params[:due_date] == Issue::DueThisMonth.name
end
+ def filter_by_due_next_month_and_previous_two_weeks?
+ due_date? && params[:due_date] == Issue::DueNextMonthAndPreviousTwoWeeks.name
+ end
+
def due_date?
params[:due_date].present?
end
diff --git a/app/helpers/calendar_helper.rb b/app/helpers/calendar_helper.rb
new file mode 100644
index 00000000000..c54b91b0ce5
--- /dev/null
+++ b/app/helpers/calendar_helper.rb
@@ -0,0 +1,8 @@
+module CalendarHelper
+ def calendar_url_options
+ { format: :ics,
+ feed_token: current_user.try(:feed_token),
+ due_date: Issue::DueNextMonthAndPreviousTwoWeeks.name,
+ sort: 'closest_future_date' }
+ end
+end
diff --git a/app/helpers/rss_helper.rb b/app/helpers/rss_helper.rb
index 9ac4df88dc3..7d4fa83a67a 100644
--- a/app/helpers/rss_helper.rb
+++ b/app/helpers/rss_helper.rb
@@ -1,5 +1,5 @@
module RssHelper
def rss_url_options
- { format: :atom, rss_token: current_user.try(:rss_token) }
+ { format: :atom, feed_token: current_user.try(:feed_token) }
end
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index b45395343cc..44150b37708 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -97,8 +97,6 @@ module Issuable
strip_attributes :title
- after_save :ensure_metrics, unless: :imported?
-
# We want to use optimistic lock for cases when only title or description are involved
# http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
def locking_enabled?
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 0332bfa9371..41a290f34b4 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -14,12 +14,13 @@ class Issue < ActiveRecord::Base
ignore_column :assignee_id, :branch_name, :deleted_at
- DueDateStruct = Struct.new(:title, :name).freeze
- NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
- AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze
- Overdue = DueDateStruct.new('Overdue', 'overdue').freeze
- DueThisWeek = DueDateStruct.new('Due This Week', 'week').freeze
- DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze
+ DueDateStruct = Struct.new(:title, :name).freeze
+ NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
+ AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze
+ Overdue = DueDateStruct.new('Overdue', 'overdue').freeze
+ DueThisWeek = DueDateStruct.new('Due This Week', 'week').freeze
+ DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze
+ DueNextMonthAndPreviousTwoWeeks = DueDateStruct.new('Due Next Month And Previous Two Weeks', 'next_month_and_previous_two_weeks').freeze
belongs_to :project
belongs_to :moved_to, class_name: 'Issue'
@@ -46,6 +47,7 @@ class Issue < ActiveRecord::Base
scope :unassigned, -> { where('NOT EXISTS (SELECT TRUE FROM issue_assignees WHERE issue_id = issues.id)') }
scope :assigned_to, ->(u) { where('EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = ? AND issue_id = issues.id)', u.id)}
+ scope :with_due_date, -> { where('due_date IS NOT NULL') }
scope :without_due_date, -> { where(due_date: nil) }
scope :due_before, ->(date) { where('issues.due_date < ?', date) }
scope :due_between, ->(from_date, to_date) { where('issues.due_date >= ?', from_date).where('issues.due_date <= ?', to_date) }
@@ -53,12 +55,14 @@ class Issue < ActiveRecord::Base
scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
+ scope :order_closest_future_date, -> { reorder('CASE WHEN due_date >= CURRENT_DATE THEN 0 ELSE 1 END ASC, ABS(CURRENT_DATE - due_date) ASC') }
scope :preload_associations, -> { preload(:labels, project: :namespace) }
scope :public_only, -> { where(confidential: false) }
after_save :expire_etag_cache
+ after_save :ensure_metrics, unless: :imported?
attr_spammable :title, spam_title: true
attr_spammable :description, spam_description: true
@@ -119,6 +123,7 @@ class Issue < ActiveRecord::Base
def self.sort_by_attribute(method, excluded_labels: [])
case method.to_s
+ when 'closest_future_date' then order_closest_future_date
when 'due_date' then order_due_date_asc
when 'due_date_asc' then order_due_date_asc
when 'due_date_desc' then order_due_date_desc
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index bc97fc3a5d9..79fc155fd3c 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -58,6 +58,7 @@ class MergeRequest < ActiveRecord::Base
after_create :ensure_merge_request_diff, unless: :importing?
after_update :clear_memoized_shas
after_update :reload_diff_if_branch_changed
+ after_save :ensure_metrics
# When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests
diff --git a/app/models/user.rb b/app/models/user.rb
index 0a838d34054..e219ab800ad 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -26,7 +26,7 @@ class User < ActiveRecord::Base
ignore_column :authentication_token
add_authentication_token_field :incoming_email_token
- add_authentication_token_field :rss_token
+ add_authentication_token_field :feed_token
default_value_for :admin, false
default_value_for(:external) { Gitlab::CurrentSettings.user_default_external }
@@ -1167,11 +1167,11 @@ class User < ActiveRecord::Base
save
end
- # each existing user needs to have an `rss_token`.
+ # each existing user needs to have an `feed_token`.
# we do this on read since migrating all existing users is not a feasible
# solution.
- def rss_token
- ensure_rss_token!
+ def feed_token
+ ensure_feed_token!
end
def sync_attribute?(attribute)
diff --git a/app/presenters/commit_status_presenter.rb b/app/presenters/commit_status_presenter.rb
index c7f7aa836bd..9a7aaf4ef32 100644
--- a/app/presenters/commit_status_presenter.rb
+++ b/app/presenters/commit_status_presenter.rb
@@ -1,11 +1,10 @@
class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
CALLOUT_FAILURE_MESSAGES = {
unknown_failure: 'There is an unknown failure, please try again',
- script_failure: 'There has been a script failure. Check the job log for more information',
api_failure: 'There has been an API failure, please try again',
stuck_or_timeout_failure: 'There has been a timeout failure or the job got stuck. Check your timeout limits or try again',
runner_system_failure: 'There has been a runner system failure, please try again',
- missing_dependency_failure: 'There has been a missing dependency failure, check the job log for more information'
+ missing_dependency_failure: 'There has been a missing dependency failure'
}.freeze
presents :build
diff --git a/app/serializers/job_entity.rb b/app/serializers/job_entity.rb
index 3076fed1674..960e7291ae6 100644
--- a/app/serializers/job_entity.rb
+++ b/app/serializers/job_entity.rb
@@ -26,7 +26,7 @@ class JobEntity < Grape::Entity
expose :created_at
expose :updated_at
expose :detailed_status, as: :status, with: StatusEntity
- expose :callout_message, if: -> (*) { failed? }
+ expose :callout_message, if: -> (*) { failed? && !build.script_failure? }
expose :recoverable, if: -> (*) { failed? }
private
diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb
index ac70a99c2c5..5a961ac89e4 100644
--- a/app/services/boards/issues/list_service.rb
+++ b/app/services/boards/issues/list_service.rb
@@ -63,7 +63,7 @@ module Boards
def without_board_labels(issues)
return issues unless board_label_ids.any?
- issues.where.not(issues_label_links.limit(1).arel.exists)
+ issues.where.not('EXISTS (?)', issues_label_links.limit(1))
end
def issues_label_links
@@ -71,10 +71,8 @@ module Boards
end
def with_list_label(issues)
- issues.where(
- LabelLink.where("label_links.target_type = 'Issue' AND label_links.target_id = issues.id")
- .where("label_links.label_id = ?", list.label_id).limit(1).arel.exists
- )
+ issues.where('EXISTS (?)', LabelLink.where("label_links.target_type = 'Issue' AND label_links.target_id = issues.id")
+ .where("label_links.label_id = ?", list.label_id).limit(1))
end
end
end
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 4bf04dadf01..86a21e24ac9 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -7,8 +7,7 @@
.top-area
= render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set
.nav-controls
- = link_to safe_params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe' do
- = icon('rss')
+ = render 'shared/issuable/feed_buttons'
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
= render 'shared/issuable/filter', type: :issues
diff --git a/app/views/dashboard/issues_calendar.ics.haml b/app/views/dashboard/issues_calendar.ics.haml
new file mode 100644
index 00000000000..59573e5fecf
--- /dev/null
+++ b/app/views/dashboard/issues_calendar.ics.haml
@@ -0,0 +1 @@
+= render 'issues/issues_calendar', issues: @issues
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 662db18cf86..8037cf4b69d 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -8,10 +8,7 @@
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls
- = link_to safe_params.merge(rss_url_options), class: 'btn' do
- = icon('rss')
- %span.icon-label
- Subscribe
+ = render 'shared/issuable/feed_buttons'
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", type: :issues
= render 'shared/issuable/search_bar', type: :issues
diff --git a/app/views/groups/issues_calendar.ics.haml b/app/views/groups/issues_calendar.ics.haml
new file mode 100644
index 00000000000..59573e5fecf
--- /dev/null
+++ b/app/views/groups/issues_calendar.ics.haml
@@ -0,0 +1 @@
+= render 'issues/issues_calendar', issues: @issues
diff --git a/app/views/issues/_issues_calendar.ics.ruby b/app/views/issues/_issues_calendar.ics.ruby
new file mode 100644
index 00000000000..3563635d33d
--- /dev/null
+++ b/app/views/issues/_issues_calendar.ics.ruby
@@ -0,0 +1,15 @@
+cal = Icalendar::Calendar.new
+cal.prodid = '-//GitLab//NONSGML GitLab//EN'
+cal.x_wr_calname = 'GitLab Issues'
+
+@issues.includes(project: :namespace).each do |issue|
+ cal.event do |event|
+ event.dtstart = Icalendar::Values::Date.new(issue.due_date)
+ event.summary = "#{issue.title} (in #{issue.project.full_path})"
+ event.description = "Find out more at #{issue_url(issue)}"
+ event.url = issue_url(issue)
+ event.transp = 'TRANSPARENT'
+ end
+end
+
+cal.to_ical
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index d253e8e456e..d111113c646 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -34,18 +34,18 @@
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
- RSS token
+ Feed token
%p
- Your RSS token is used to authenticate you when your RSS reader loads a personalized RSS feed, and is included in your personal RSS feed URLs.
+ Your feed token is used to authenticate you when your RSS reader loads a personalized RSS feed or when when your calendar application loads a personalized calendar, and is included in those feed URLs.
%p
It cannot be used to access any other data.
- .col-lg-8.rss-token-reset
- = label_tag :rss_token, 'RSS token', class: "label-light"
- = text_field_tag :rss_token, current_user.rss_token, class: 'form-control', readonly: true, onclick: 'this.select()'
+ .col-lg-8.feed-token-reset
+ = label_tag :feed_token, 'Feed token', class: "label-light"
+ = text_field_tag :feed_token, current_user.feed_token, class: 'form-control', readonly: true, onclick: 'this.select()'
%p.form-text.text-muted
- Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds as if they were you.
+ Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds or your calendar feed as if they were you.
You should
- = link_to 'reset it', [:reset, :rss_token, :profile], method: :put, data: { confirm: 'Are you sure? Any RSS URLs currently in use will stop working.' }
+ = link_to 'reset it', [:reset, :feed_token, :profile], method: :put, data: { confirm: 'Are you sure? Any RSS or calendar URLs currently in use will stop working.' }
if that ever happens.
- if incoming_email_token_enabled?
diff --git a/app/views/projects/issues/_nav_btns.html.haml b/app/views/projects/issues/_nav_btns.html.haml
index 297b928f020..0dd2d2e6c5d 100644
--- a/app/views/projects/issues/_nav_btns.html.haml
+++ b/app/views/projects/issues/_nav_btns.html.haml
@@ -1,5 +1,5 @@
-= link_to safe_params.merge(rss_url_options), class: 'btn btn-default append-right-10 has-tooltip', title: 'Subscribe' do
- = icon('rss')
+= render 'shared/issuable/feed_buttons'
+
- if @can_bulk_update
= button_tag "Edit issues", class: "btn btn-default append-right-10 js-bulk-update-toggle"
- if show_new_issue_link?(@project)
diff --git a/app/views/projects/issues/calendar.ics.haml b/app/views/projects/issues/calendar.ics.haml
new file mode 100644
index 00000000000..59573e5fecf
--- /dev/null
+++ b/app/views/projects/issues/calendar.ics.haml
@@ -0,0 +1 @@
+= render 'issues/issues_calendar', issues: @issues
diff --git a/app/views/shared/icons/_icon_calendar.svg b/app/views/shared/icons/_icon_calendar.svg
new file mode 100644
index 00000000000..4d0a703f9a0
--- /dev/null
+++ b/app/views/shared/icons/_icon_calendar.svg
@@ -0,0 +1 @@
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M15 5v7a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V5a2 2 0 0 1 2-2h1V2a1 1 0 1 1 2 0v1h4V2a1 1 0 1 1 2 0v1h1a2 2 0 0 1 2 2zM3 6v6a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V6H3zm2 2h2a1 1 0 1 1 0 2H5a1 1 0 1 1 0-2z" fill="#000" fill-rule="evenodd"/></svg> \ No newline at end of file
diff --git a/app/views/shared/issuable/_feed_buttons.html.haml b/app/views/shared/issuable/_feed_buttons.html.haml
new file mode 100644
index 00000000000..d4834090413
--- /dev/null
+++ b/app/views/shared/issuable/_feed_buttons.html.haml
@@ -0,0 +1,4 @@
+= link_to safe_params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe to RSS feed' do
+ = icon('rss')
+= link_to safe_params.merge(calendar_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe to calendar' do
+ = custom_icon('icon_calendar')
diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml
index e6a65161ed6..55460acab8f 100644
--- a/app/views/shared/milestones/_tabs.html.haml
+++ b/app/views/shared/milestones/_tabs.html.haml
@@ -5,25 +5,25 @@
.fade-right= icon('angle-right')
%ul.nav-links.scrolling-tabs.js-milestone-tabs.nav.nav-tabs
- if issues_accessible
- %li.active
- = link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
+ %li.nav-item
+ = link_to '#tab-issues', class: 'nav-link active', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
Issues
%span.badge.badge-pill= milestone.issues_visible_to_user(current_user).size
- %li
- = link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do
+ %li.nav-item
+ = link_to '#tab-merge-requests', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do
Merge Requests
%span.badge.badge-pill= milestone.merge_requests.size
- else
- %li.active
- = link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do
+ %li.nav-item
+ = link_to '#tab-merge-requests', class: 'nav-link active', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do
Merge Requests
%span.badge.badge-pill= milestone.merge_requests.size
- %li
- = link_to '#tab-participants', 'data-toggle' => 'tab', 'data-endpoint': milestone_participants_tab_path(milestone) do
+ %li.nav-item
+ = link_to '#tab-participants', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_participants_tab_path(milestone) do
Participants
%span.badge.badge-pill= milestone.participants.count
- %li
- = link_to '#tab-labels', 'data-toggle' => 'tab', 'data-endpoint': milestone_labels_tab_path(milestone) do
+ %li.nav-item
+ = link_to '#tab-labels', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_labels_tab_path(milestone) do
Labels
%span.badge.badge-pill= milestone.labels.count
diff --git a/changelogs/unreleased/41587-osw-mr-metrics-migration-cleanup.yml b/changelogs/unreleased/41587-osw-mr-metrics-migration-cleanup.yml
new file mode 100644
index 00000000000..f953d380808
--- /dev/null
+++ b/changelogs/unreleased/41587-osw-mr-metrics-migration-cleanup.yml
@@ -0,0 +1,5 @@
+---
+title: Take two for MR metrics population background migration
+merge_request: 19097
+author:
+type: other
diff --git a/changelogs/unreleased/44184-issues_ical_feed.yml b/changelogs/unreleased/44184-issues_ical_feed.yml
new file mode 100644
index 00000000000..8151d82625a
--- /dev/null
+++ b/changelogs/unreleased/44184-issues_ical_feed.yml
@@ -0,0 +1,5 @@
+---
+title: Export assigned issues in iCalendar feed
+merge_request: 17783
+author: Imre Farkas
+type: added
diff --git a/changelogs/unreleased/45520-remove-links-from-web-ide.yml b/changelogs/unreleased/45520-remove-links-from-web-ide.yml
new file mode 100644
index 00000000000..81d5c26992f
--- /dev/null
+++ b/changelogs/unreleased/45520-remove-links-from-web-ide.yml
@@ -0,0 +1,5 @@
+---
+title: Change the IDE file buttons for an "Open in file view" button
+merge_request: 19129
+author: Sam Beckham
+type: changed
diff --git a/changelogs/unreleased/46552-fixes-redundant-message-for-failure-reasons.yml b/changelogs/unreleased/46552-fixes-redundant-message-for-failure-reasons.yml
new file mode 100644
index 00000000000..43427aaa242
--- /dev/null
+++ b/changelogs/unreleased/46552-fixes-redundant-message-for-failure-reasons.yml
@@ -0,0 +1,5 @@
+---
+title: Removes redundant script failure message from Job page
+merge_request: 19138
+author:
+type: changed
diff --git a/changelogs/unreleased/issue_45082.yml b/changelogs/unreleased/issue_45082.yml
new file mode 100644
index 00000000000..b916a36c17b
--- /dev/null
+++ b/changelogs/unreleased/issue_45082.yml
@@ -0,0 +1,5 @@
+---
+title: Add merge requests list endpoint for groups
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/rails5-fix-46230.yml b/changelogs/unreleased/rails5-fix-46230.yml
new file mode 100644
index 00000000000..8ec28604483
--- /dev/null
+++ b/changelogs/unreleased/rails5-fix-46230.yml
@@ -0,0 +1,5 @@
+---
+title: Use strings as properties key in kubernetes service spec.
+merge_request: 19265
+author: Jasper Maes
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-issue-api-perf-n-plus-one.yml b/changelogs/unreleased/sh-fix-issue-api-perf-n-plus-one.yml
new file mode 100644
index 00000000000..57ba081326f
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-issue-api-perf-n-plus-one.yml
@@ -0,0 +1,5 @@
+---
+title: Eliminate cached N+1 queries for projects in Issue API
+merge_request:
+author:
+type: performance
diff --git a/config/routes/dashboard.rb b/config/routes/dashboard.rb
index d2437285cdf..f1e8c2b9d82 100644
--- a/config/routes/dashboard.rb
+++ b/config/routes/dashboard.rb
@@ -1,4 +1,5 @@
resource :dashboard, controller: 'dashboard', only: [] do
+ get :issues, action: :issues_calendar, constraints: lambda { |req| req.format == :ics }
get :issues
get :merge_requests
get :activity
diff --git a/config/routes/group.rb b/config/routes/group.rb
index fff0914c3cd..b09eb3c1b5b 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -5,9 +5,10 @@ end
constraints(::Constraints::GroupUrlConstrainer.new) do
scope(path: 'groups/*id',
controller: :groups,
- constraints: { id: Gitlab::PathRegex.full_namespace_route_regex, format: /(html|json|atom)/ }) do
+ constraints: { id: Gitlab::PathRegex.full_namespace_route_regex, format: /(html|json|atom|ics)/ }) do
scope(path: '-') do
get :edit, as: :edit_group
+ get :issues, as: :issues_group_calendar, action: :issues_calendar, constraints: lambda { |req| req.format == :ics }
get :issues, as: :issues_group
get :merge_requests, as: :merge_requests_group
get :projects, as: :projects_group
diff --git a/config/routes/profile.rb b/config/routes/profile.rb
index a9ba5ac2c0b..c1cac3905f1 100644
--- a/config/routes/profile.rb
+++ b/config/routes/profile.rb
@@ -7,7 +7,7 @@ resource :profile, only: [:show, :update] do
get :applications, to: 'oauth/applications#index'
put :reset_incoming_email_token
- put :reset_rss_token
+ put :reset_feed_token
put :update_username
end
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 5a1be1a8b73..6dfbd7ecd1f 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -353,6 +353,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
+ get :issues, to: 'issues#calendar', constraints: lambda { |req| req.format == :ics }
resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do
member do
post :toggle_subscription
diff --git a/db/migrate/20180408143354_rename_users_rss_token_to_feed_token.rb b/db/migrate/20180408143354_rename_users_rss_token_to_feed_token.rb
new file mode 100644
index 00000000000..007cbebaf1b
--- /dev/null
+++ b/db/migrate/20180408143354_rename_users_rss_token_to_feed_token.rb
@@ -0,0 +1,15 @@
+class RenameUsersRssTokenToFeedToken < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ rename_column_concurrently :users, :rss_token, :feed_token
+ end
+
+ def down
+ cleanup_concurrent_column_rename :users, :feed_token, :rss_token
+ end
+end
diff --git a/db/post_migrate/20180408143355_cleanup_users_rss_token_rename.rb b/db/post_migrate/20180408143355_cleanup_users_rss_token_rename.rb
new file mode 100644
index 00000000000..bff83379087
--- /dev/null
+++ b/db/post_migrate/20180408143355_cleanup_users_rss_token_rename.rb
@@ -0,0 +1,13 @@
+class CleanupUsersRssTokenRename < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ def up
+ cleanup_concurrent_column_rename :users, :rss_token, :feed_token
+ end
+
+ def down
+ rename_column_concurrently :users, :feed_token, :rss_token
+ end
+end
diff --git a/db/post_migrate/20180521162137_migrate_remaining_mr_metrics_populating_background_migration.rb b/db/post_migrate/20180521162137_migrate_remaining_mr_metrics_populating_background_migration.rb
new file mode 100644
index 00000000000..0282688fa40
--- /dev/null
+++ b/db/post_migrate/20180521162137_migrate_remaining_mr_metrics_populating_background_migration.rb
@@ -0,0 +1,44 @@
+class MigrateRemainingMrMetricsPopulatingBackgroundMigration < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 5_000
+ MIGRATION = 'PopulateMergeRequestMetricsWithEventsData'
+ DELAY_INTERVAL = 10.minutes
+
+ disable_ddl_transaction!
+
+ class MergeRequest < ActiveRecord::Base
+ self.table_name = 'merge_requests'
+
+ include ::EachBatch
+ end
+
+ def up
+ # Perform any ongoing background migration that might still be running. This
+ # avoids scheduling way too many of the same jobs on self-hosted instances
+ # if they're updating GitLab across multiple versions. The "Take one"
+ # migration was executed on 10.4 on
+ # SchedulePopulateMergeRequestMetricsWithEventsData.
+ Gitlab::BackgroundMigration.steal(MIGRATION)
+
+ metrics_not_exists_clause = <<~SQL
+ NOT EXISTS (SELECT 1 FROM merge_request_metrics
+ WHERE merge_request_metrics.merge_request_id = merge_requests.id)
+ SQL
+
+ relation = MergeRequest.where(metrics_not_exists_clause)
+
+ # We currently have ~400_000 MR records without metrics on GitLab.com.
+ # This means it'll schedule ~80 jobs (5000 MRs each) with a 10 minutes gap,
+ # so this should take ~14 hours for all background migrations to complete.
+ #
+ queue_background_migration_jobs_by_range_at_intervals(relation,
+ MIGRATION,
+ DELAY_INTERVAL,
+ batch_size: BATCH_SIZE)
+ end
+
+ def down
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 42fea8e4380..f8663574580 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -2082,9 +2082,9 @@ ActiveRecord::Schema.define(version: 20180529093006) do
t.date "last_activity_on"
t.boolean "notified_of_own_activity"
t.string "preferred_language"
- t.string "rss_token"
t.integer "theme_id", limit: 2
t.integer "accepted_term_id"
+ t.string "feed_token"
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
@@ -2092,12 +2092,12 @@ ActiveRecord::Schema.define(version: 20180529093006) do
add_index "users", ["created_at"], name: "index_users_on_created_at", using: :btree
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["email"], name: "index_users_on_email_trigram", using: :gin, opclasses: {"email"=>"gin_trgm_ops"}
+ add_index "users", ["feed_token"], name: "index_users_on_feed_token", using: :btree
add_index "users", ["ghost"], name: "index_users_on_ghost", using: :btree
add_index "users", ["incoming_email_token"], name: "index_users_on_incoming_email_token", using: :btree
add_index "users", ["name"], name: "index_users_on_name", using: :btree
add_index "users", ["name"], name: "index_users_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
- add_index "users", ["rss_token"], name: "index_users_on_rss_token", using: :btree
add_index "users", ["state"], name: "index_users_on_state", using: :btree
add_index "users", ["username"], name: "index_users_on_username", using: :btree
add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"}
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 8849f490c4f..051d2a10bc6 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -240,6 +240,112 @@ Parameters:
]
```
+## List group merge requests
+
+Get all merge requests for this group and its subgroups.
+The `state` parameter can be used to get only merge requests with a given state (`opened`, `closed`, or `merged`) or all of them (`all`).
+The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests.
+
+```
+GET /groups/:id/merge_requests
+GET /groups/:id/merge_requests?state=opened
+GET /groups/:id/merge_requests?state=all
+GET /groups/:id/merge_requests?milestone=release
+GET /groups/:id/merge_requests?labels=bug,reproduced
+GET /groups/:id/merge_requests?my_reaction_emoji=star
+```
+
+`group_id` represents the ID of the group which contains the project where the MR resides.
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| ------------------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
+| `id` | integer | yes | The ID of a group |
+| `state` | string | no | Return all merge requests or just those that are `opened`, `closed`, or `merged` |
+| `order_by` | string | no | Return merge requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `sort` | string | no | Return merge requests sorted in `asc` or `desc` order. Default is `desc` |
+| `milestone` | string | no | Return merge requests for a specific milestone |
+| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
+| `labels` | string | no | Return merge requests matching a comma separated list of labels |
+| `created_after` | datetime | no | Return merge requests created on or after the given time |
+| `created_before` | datetime | no | Return merge requests created on or before the given time |
+| `updated_after` | datetime | no | Return merge requests updated on or after the given time |
+| `updated_before` | datetime | no | Return merge requests updated on or before the given time |
+| `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> |
+| `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ |
+| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ |
+| `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ |
+| `source_branch` | string | no | Return merge requests with the given source branch |
+| `target_branch` | string | no | Return merge requests with the given target branch |
+| `search` | string | no | Search merge requests against their `title` and `description` |
+
+```json
+[
+ {
+ "id": 1,
+ "iid": 1,
+ "target_branch": "master",
+ "source_branch": "test1",
+ "project_id": 3,
+ "title": "test1",
+ "state": "opened",
+ "created_at": "2017-04-29T08:46:00Z",
+ "updated_at": "2017-04-29T08:46:00Z",
+ "upvotes": 0,
+ "downvotes": 0,
+ "author": {
+ "id": 1,
+ "username": "admin",
+ "email": "admin@example.com",
+ "name": "Administrator",
+ "state": "active",
+ "created_at": "2012-04-29T08:46:00Z"
+ },
+ "assignee": {
+ "id": 1,
+ "username": "admin",
+ "email": "admin@example.com",
+ "name": "Administrator",
+ "state": "active",
+ "created_at": "2012-04-29T08:46:00Z"
+ },
+ "source_project_id": 2,
+ "target_project_id": 3,
+ "labels": [ ],
+ "description": "fixed login page css paddings",
+ "work_in_progress": false,
+ "milestone": {
+ "id": 5,
+ "iid": 1,
+ "project_id": 3,
+ "title": "v2.0",
+ "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.",
+ "state": "closed",
+ "created_at": "2015-02-02T19:49:26.013Z",
+ "updated_at": "2015-02-02T19:49:26.013Z",
+ "due_date": null
+ },
+ "merge_when_pipeline_succeeds": true,
+ "merge_status": "can_be_merged",
+ "sha": "8888888888888888888888888888888888888888",
+ "merge_commit_sha": null,
+ "user_notes_count": 1,
+ "changes_count": "1",
+ "should_remove_source_branch": true,
+ "force_remove_source_branch": false,
+ "web_url": "http://example.com/example/example/merge_requests/1",
+ "discussion_locked": false,
+ "time_stats": {
+ "time_estimate": 0,
+ "total_time_spent": 0,
+ "human_time_estimate": null,
+ "human_total_time_spent": null
+ }
+ }
+]
+```
+
## Get single MR
Shows information about a single merge request.
diff --git a/doc/development/new_fe_guide/tips.md b/doc/development/new_fe_guide/tips.md
index f0cdf52d618..881ad1662ae 100644
--- a/doc/development/new_fe_guide/tips.md
+++ b/doc/development/new_fe_guide/tips.md
@@ -1,3 +1,9 @@
# Tips
-> TODO: Add tips
+## Clearing production compiled assets
+
+To clear production compiled assets created with `yarn webpack-prod` you can run:
+
+```
+yarn clean
+```
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index a76a5096b69..1a926a660f1 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -120,6 +120,10 @@ Add `screenshot_and_save_page` in a `:js` spec to screenshot what Capybara
Add `screenshot_and_open_image` in a `:js` spec to screenshot what Capybara
"sees", and automatically open the image.
+The HTML dumps created by this are missing CSS.
+This results in them looking very different from the actual application.
+There is a [small hack](https://gitlab.com/gitlab-org/gitlab-ce/snippets/1718469) to add CSS which makes debugging easier.
+
### Fast unit tests
Some classes are well-isolated from Rails and you should be able to test them
diff --git a/doc/user/project/issues/due_dates.md b/doc/user/project/issues/due_dates.md
index 1bf8b776c2e..93306437c6c 100644
--- a/doc/user/project/issues/due_dates.md
+++ b/doc/user/project/issues/due_dates.md
@@ -39,5 +39,13 @@ The day before an open issue is due, an email will be sent to all participants
of the issue. Both the due date and the day before are calculated using the
server's timezone.
+Issues with due dates can also be exported as an iCalendar feed. The URL of the
+feed can be added to calendar applications. The feed is accessible by clicking
+on the _Subscribe to calendar_ button on the following pages:
+- on the **Assigned Issues** page that is linked on the right-hand side of the
+ GitLab header
+- on the **Project Issues** page
+- on the **Group Issues** page
+
[ce-3614]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3614
[permissions]: ../../permissions.md#project
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 6d75e8817c4..b64f465ce56 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -16,7 +16,7 @@ module API
args[:scope] = args[:scope].underscore if args[:scope]
issues = IssuesFinder.new(current_user, args).execute
- .preload(:assignees, :labels, :notes, :timelogs)
+ .preload(:assignees, :labels, :notes, :timelogs, :project)
issues.reorder(args[:order_by] => args[:sort])
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 1ba9a09346f..b1e510d72de 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -59,6 +59,18 @@ module API
end
end
+ def serializer_options_for(merge_requests)
+ options = { with: Entities::MergeRequestBasic, current_user: current_user }
+
+ if params[:view] == 'simple'
+ options[:with] = Entities::MergeRequestSimple
+ else
+ options[:issuable_metadata] = issuable_meta_data(merge_requests, 'MergeRequest')
+ end
+
+ options
+ end
+
params :merge_requests_params do
optional :state, type: String, values: %w[opened closed merged all], default: 'all',
desc: 'Return opened, closed, merged, or all merge requests'
@@ -98,16 +110,26 @@ module API
authenticate! unless params[:scope] == 'all'
merge_requests = find_merge_requests
- options = { with: Entities::MergeRequestBasic,
- current_user: current_user }
+ present merge_requests, serializer_options_for(merge_requests)
+ end
+ end
- if params[:view] == 'simple'
- options[:with] = Entities::MergeRequestSimple
- else
- options[:issuable_metadata] = issuable_meta_data(merge_requests, 'MergeRequest')
- end
+ params do
+ requires :id, type: String, desc: 'The ID of a group'
+ end
+ resource :groups, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
+ desc 'Get a list of group merge requests' do
+ success Entities::MergeRequestBasic
+ end
+ params do
+ use :merge_requests_params
+ end
+ get ":id/merge_requests" do
+ group = find_group!(params[:id])
- present merge_requests, options
+ merge_requests = find_merge_requests(group_id: group.id, include_subgroups: true)
+
+ present merge_requests, serializer_options_for(merge_requests)
end
end
@@ -159,15 +181,8 @@ module API
merge_requests = find_merge_requests(project_id: user_project.id)
- options = { with: Entities::MergeRequestBasic,
- current_user: current_user,
- project: user_project }
-
- if params[:view] == 'simple'
- options[:with] = Entities::MergeRequestSimple
- else
- options[:issuable_metadata] = issuable_meta_data(merge_requests, 'MergeRequest')
- end
+ options = serializer_options_for(merge_requests)
+ options[:project] = user_project
present merge_requests, options
end
diff --git a/lib/gitlab/auth/request_authenticator.rb b/lib/gitlab/auth/request_authenticator.rb
index a0b5cd868c3..66de52506ce 100644
--- a/lib/gitlab/auth/request_authenticator.rb
+++ b/lib/gitlab/auth/request_authenticator.rb
@@ -16,7 +16,7 @@ module Gitlab
end
def find_sessionless_user
- find_user_from_access_token || find_user_from_rss_token
+ find_user_from_access_token || find_user_from_feed_token
rescue Gitlab::Auth::AuthenticationError
nil
end
diff --git a/lib/gitlab/auth/user_auth_finders.rb b/lib/gitlab/auth/user_auth_finders.rb
index 4dc23f977da..c7993665421 100644
--- a/lib/gitlab/auth/user_auth_finders.rb
+++ b/lib/gitlab/auth/user_auth_finders.rb
@@ -25,13 +25,15 @@ module Gitlab
current_request.env['warden']&.authenticate if verified_request?
end
- def find_user_from_rss_token
- return unless current_request.path.ends_with?('.atom') || current_request.format.atom?
+ def find_user_from_feed_token
+ return unless rss_request? || ics_request?
- token = current_request.params[:rss_token].presence
+ # NOTE: feed_token was renamed from rss_token but both needs to be supported because
+ # users might have already added the feed to their RSS reader before the rename
+ token = current_request.params[:feed_token].presence || current_request.params[:rss_token].presence
return unless token
- User.find_by_rss_token(token) || raise(UnauthorizedError)
+ User.find_by_feed_token(token) || raise(UnauthorizedError)
end
def find_user_from_access_token
@@ -104,6 +106,14 @@ module Gitlab
def current_request
@current_request ||= ensure_action_dispatch_request(request)
end
+
+ def rss_request?
+ current_request.path.ends_with?('.atom') || current_request.format.atom?
+ end
+
+ def ics_request?
+ current_request.path.ends_with?('.ics') || current_request.format.ics?
+ end
end
end
end
diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb
index d7369060cc5..4c28489f45a 100644
--- a/lib/gitlab/contributions_calendar.rb
+++ b/lib/gitlab/contributions_calendar.rb
@@ -85,7 +85,7 @@ module Gitlab
.select(t[:project_id], t[:target_type], t[:action], "date(created_at + #{date_interval}) AS date", 'count(id) as total_amount')
.group(t[:project_id], t[:target_type], t[:action], "date(created_at + #{date_interval})")
.where(conditions)
- .having(t[:project_id].in(Arel::Nodes::SqlLiteral.new(authed_projects.to_sql)))
+ .where("events.project_id in (#{authed_projects.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
end
end
end
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index 1f5f88bf792..a4cc64de80d 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -78,7 +78,7 @@ module Gitlab
def tree_entry(ref, path, limit = nil)
request = Gitaly::TreeEntryRequest.new(
repository: @gitaly_repo,
- revision: ref,
+ revision: encode_binary(ref),
path: encode_binary(path),
limit: limit.to_i
)
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 36c7534cd7a..da3667faf7a 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -28,6 +28,7 @@ project_tree:
- project_members:
- :user
- merge_requests:
+ - :metrics
- notes:
- :author
- events:
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index b736b2c3fe5..c5cf290f191 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -18,9 +18,10 @@ module Gitlab
label: :project_label,
custom_attributes: 'ProjectCustomAttribute',
project_badges: 'Badge',
+ metrics: 'MergeRequest::Metrics',
ci_cd_settings: 'ProjectCiCdSetting' }.freeze
- USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id created_by_id last_edited_by_id merge_user_id resolved_by_id closed_by_id].freeze
+ USER_REFERENCES = %w[author_id assignee_id updated_by_id merged_by_id latest_closed_by_id user_id created_by_id last_edited_by_id merge_user_id resolved_by_id closed_by_id].freeze
PROJECT_REFERENCES = %w[project_id source_project_id target_project_id].freeze
@@ -36,6 +37,15 @@ module Gitlab
new(*args).create
end
+ def self.relation_class(relation_name)
+ # There are scenarios where the model is pluralized (e.g.
+ # MergeRequest::Metrics), and we don't want to force it to singular
+ # with #classify.
+ relation_name.to_s.classify.constantize
+ rescue NameError
+ relation_name.to_s.constantize
+ end
+
def initialize(relation_sym:, relation_hash:, members_mapper:, user:, project:, excluded_keys: [])
@relation_name = OVERRIDES[relation_sym] || relation_sym
@relation_hash = relation_hash.except('noteable_id')
@@ -195,7 +205,7 @@ module Gitlab
end
def relation_class
- @relation_class ||= @relation_name.to_s.classify.constantize
+ @relation_class ||= self.class.relation_class(@relation_name)
end
def imported_object
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index 0e27a28ea6e..72eb8adcce2 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -31,27 +31,27 @@ map $http_upgrade $connection_upgrade_gitlab {
log_format gitlab_access $remote_addr - $remote_user [$time_local] "$request_method $gitlab_filtered_request_uri $server_protocol" $status $body_bytes_sent "$gitlab_filtered_http_referer" "$http_user_agent";
## Remove private_token from the request URI
-# In: /foo?private_token=unfiltered&authenticity_token=unfiltered&rss_token=unfiltered&...
-# Out: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
+# In: /foo?private_token=unfiltered&authenticity_token=unfiltered&feed_token=unfiltered&...
+# Out: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&feed_token=unfiltered&...
map $request_uri $gitlab_temp_request_uri_1 {
default $request_uri;
~(?i)^(?<start>.*)(?<temp>[\?&]private[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
}
## Remove authenticity_token from the request URI
-# In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
-# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
+# In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&feed_token=unfiltered&...
+# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=unfiltered&...
map $gitlab_temp_request_uri_1 $gitlab_temp_request_uri_2 {
default $gitlab_temp_request_uri_1;
~(?i)^(?<start>.*)(?<temp>[\?&]authenticity[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
}
-## Remove rss_token from the request URI
-# In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
-# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=[FILTERED]&...
+## Remove feed_token from the request URI
+# In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=unfiltered&...
+# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=[FILTERED]&...
map $gitlab_temp_request_uri_2 $gitlab_filtered_request_uri {
default $gitlab_temp_request_uri_2;
- ~(?i)^(?<start>.*)(?<temp>[\?&]rss[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
+ ~(?i)^(?<start>.*)(?<temp>[\?&]feed[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
}
## A version of the referer without the query string
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index 8218d68f9ba..2e3799d5e1b 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -36,27 +36,27 @@ map $http_upgrade $connection_upgrade_gitlab_ssl {
log_format gitlab_ssl_access $remote_addr - $remote_user [$time_local] "$request_method $gitlab_ssl_filtered_request_uri $server_protocol" $status $body_bytes_sent "$gitlab_ssl_filtered_http_referer" "$http_user_agent";
## Remove private_token from the request URI
-# In: /foo?private_token=unfiltered&authenticity_token=unfiltered&rss_token=unfiltered&...
-# Out: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
+# In: /foo?private_token=unfiltered&authenticity_token=unfiltered&feed_token=unfiltered&...
+# Out: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&feed_token=unfiltered&...
map $request_uri $gitlab_ssl_temp_request_uri_1 {
default $request_uri;
~(?i)^(?<start>.*)(?<temp>[\?&]private[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
}
## Remove authenticity_token from the request URI
-# In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
-# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
+# In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&feed_token=unfiltered&...
+# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=unfiltered&...
map $gitlab_ssl_temp_request_uri_1 $gitlab_ssl_temp_request_uri_2 {
default $gitlab_ssl_temp_request_uri_1;
~(?i)^(?<start>.*)(?<temp>[\?&]authenticity[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
}
-## Remove rss_token from the request URI
-# In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
-# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=[FILTERED]&...
+## Remove feed_token from the request URI
+# In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=unfiltered&...
+# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=[FILTERED]&...
map $gitlab_ssl_temp_request_uri_2 $gitlab_ssl_filtered_request_uri {
default $gitlab_ssl_temp_request_uri_2;
- ~(?i)^(?<start>.*)(?<temp>[\?&]rss[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
+ ~(?i)^(?<start>.*)(?<temp>[\?&]feed[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
}
## A version of the referer without the query string
diff --git a/lib/tasks/tokens.rake b/lib/tasks/tokens.rake
index 693597afdf8..81829668de8 100644
--- a/lib/tasks/tokens.rake
+++ b/lib/tasks/tokens.rake
@@ -6,9 +6,9 @@ namespace :tokens do
reset_all_users_token(:reset_incoming_email_token!)
end
- desc "Reset all GitLab RSS tokens"
- task reset_all_rss: :environment do
- reset_all_users_token(:reset_rss_token!)
+ desc "Reset all GitLab feed tokens"
+ task reset_all_feed: :environment do
+ reset_all_users_token(:reset_feed_token!)
end
def reset_all_users_token(reset_token_method)
@@ -31,8 +31,8 @@ class TmpUser < ActiveRecord::Base
save!(validate: false)
end
- def reset_rss_token!
- write_new_token(:rss_token)
+ def reset_feed_token!
+ write_new_token(:feed_token)
save!(validate: false)
end
end
diff --git a/package.json b/package.json
index 0113d49c8f2..7be3b8744e3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,7 @@
{
"private": true,
"scripts": {
+ "clean": "rm -rf public/assets tmp/cache/*-loader",
"dev-server": "nodemon -w 'config/webpack.config.js' --exec 'webpack-dev-server --config config/webpack.config.js'",
"eslint": "eslint --max-warnings 0 --ext .js,.vue .",
"eslint-fix": "eslint --max-warnings 0 --ext .js,.vue --fix .",
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index f0caac40afd..b048da1991c 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -146,35 +146,43 @@ describe ApplicationController do
end
end
- describe '#authenticate_user_from_rss_token' do
- describe "authenticating a user from an RSS token" do
+ describe '#authenticate_sessionless_user!' do
+ describe 'authenticating a user from a feed token' do
controller(described_class) do
def index
render text: 'authenticated'
end
end
- context "when the 'rss_token' param is populated with the RSS token" do
+ context "when the 'feed_token' param is populated with the feed token" do
context 'when the request format is atom' do
it "logs the user in" do
- get :index, rss_token: user.rss_token, format: :atom
+ get :index, feed_token: user.feed_token, format: :atom
expect(response).to have_gitlab_http_status 200
expect(response.body).to eq 'authenticated'
end
end
- context 'when the request format is not atom' do
+ context 'when the request format is ics' do
+ it "logs the user in" do
+ get :index, feed_token: user.feed_token, format: :ics
+ expect(response).to have_gitlab_http_status 200
+ expect(response.body).to eq 'authenticated'
+ end
+ end
+
+ context 'when the request format is neither atom nor ics' do
it "doesn't log the user in" do
- get :index, rss_token: user.rss_token
+ get :index, feed_token: user.feed_token
expect(response.status).not_to have_gitlab_http_status 200
expect(response.body).not_to eq 'authenticated'
end
end
end
- context "when the 'rss_token' param is populated with an invalid RSS token" do
+ context "when the 'feed_token' param is populated with an invalid feed token" do
it "doesn't log the user" do
- get :index, rss_token: "token"
+ get :index, feed_token: 'token', format: :atom
expect(response.status).not_to eq 200
expect(response.body).not_to eq 'authenticated'
end
@@ -454,7 +462,7 @@ describe ApplicationController do
end
it 'renders a 403 when the sessionless user did not accept the terms' do
- get :index, rss_token: user.rss_token, format: :atom
+ get :index, feed_token: user.feed_token, format: :atom
expect(response).to have_gitlab_http_status(403)
end
@@ -462,7 +470,7 @@ describe ApplicationController do
it 'renders a 200 when the sessionless user accepted the terms' do
accept_terms(user)
- get :index, rss_token: user.rss_token, format: :atom
+ get :index, feed_token: user.feed_token, format: :atom
expect(response).to have_gitlab_http_status(200)
end
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index 769fd656e7a..59db8cdc34b 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -12,10 +12,6 @@ FactoryBot.define do
user.notification_email = user.email
end
- before(:create) do |user|
- user.ensure_rss_token
- end
-
trait :admin do
admin true
end
diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb
index fb6c71ce997..da7749b42d2 100644
--- a/spec/features/atom/dashboard_issues_spec.rb
+++ b/spec/features/atom/dashboard_issues_spec.rb
@@ -31,20 +31,20 @@ describe "Dashboard Issues Feed" do
expect(body).to have_selector('title', text: "#{user.name} issues")
end
- it "renders atom feed via RSS token" do
- visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: user.id)
+ it "renders atom feed via feed token" do
+ visit issues_dashboard_path(:atom, feed_token: user.feed_token, assignee_id: user.id)
expect(response_headers['Content-Type']).to have_content('application/atom+xml')
expect(body).to have_selector('title', text: "#{user.name} issues")
end
it "renders atom feed with url parameters" do
- visit issues_dashboard_path(:atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id)
+ visit issues_dashboard_path(:atom, feed_token: user.feed_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]')
params = CGI.parse(URI.parse(link[:href]).query)
- expect(params).to include('rss_token' => [user.rss_token])
+ expect(params).to include('feed_token' => [user.feed_token])
expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s])
end
@@ -53,7 +53,7 @@ describe "Dashboard Issues Feed" do
let!(:issue2) { create(:issue, author: user, assignees: [assignee], project: project2, description: 'test desc') }
it "renders issue fields" do
- visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: assignee.id)
+ visit issues_dashboard_path(:atom, feed_token: user.feed_token, assignee_id: assignee.id)
entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue2.title}')]")
@@ -76,7 +76,7 @@ describe "Dashboard Issues Feed" do
end
it "renders issue label and milestone info" do
- visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: assignee.id)
+ visit issues_dashboard_path(:atom, feed_token: user.feed_token, assignee_id: assignee.id)
entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue1.title}')]")
diff --git a/spec/features/atom/dashboard_spec.rb b/spec/features/atom/dashboard_spec.rb
index c6683bb3bc9..462eab07a75 100644
--- a/spec/features/atom/dashboard_spec.rb
+++ b/spec/features/atom/dashboard_spec.rb
@@ -13,9 +13,9 @@ describe "Dashboard Feed" do
end
end
- context "projects atom feed via RSS token" do
+ context "projects atom feed via feed token" do
it "renders projects atom feed" do
- visit dashboard_projects_path(:atom, rss_token: user.rss_token)
+ visit dashboard_projects_path(:atom, feed_token: user.feed_token)
expect(body).to have_selector('feed title')
end
end
@@ -29,7 +29,7 @@ describe "Dashboard Feed" do
project.add_master(user)
issue_event(issue, user)
note_event(note, user)
- visit dashboard_projects_path(:atom, rss_token: user.rss_token)
+ visit dashboard_projects_path(:atom, feed_token: user.feed_token)
end
it "has issue opened event" do
diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb
index 525ce23aa56..ee3570a5b2b 100644
--- a/spec/features/atom/issues_spec.rb
+++ b/spec/features/atom/issues_spec.rb
@@ -45,10 +45,10 @@ describe 'Issues Feed' do
end
end
- context 'when authenticated via RSS token' do
+ context 'when authenticated via feed token' do
it 'renders atom feed' do
visit project_issues_path(project, :atom,
- rss_token: user.rss_token)
+ feed_token: user.feed_token)
expect(response_headers['Content-Type'])
.to have_content('application/atom+xml')
@@ -61,24 +61,23 @@ describe 'Issues Feed' do
end
it "renders atom feed with url parameters for project issues" do
- visit project_issues_path(project,
- :atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id)
+ visit project_issues_path(project, :atom, feed_token: user.feed_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]')
params = CGI.parse(URI.parse(link[:href]).query)
- expect(params).to include('rss_token' => [user.rss_token])
+ expect(params).to include('feed_token' => [user.feed_token])
expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s])
end
it "renders atom feed with url parameters for group issues" do
- visit issues_group_path(group, :atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id)
+ visit issues_group_path(group, :atom, feed_token: user.feed_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]')
params = CGI.parse(URI.parse(link[:href]).query)
- expect(params).to include('rss_token' => [user.rss_token])
+ expect(params).to include('feed_token' => [user.feed_token])
expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s])
end
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index 2d074c115dd..eeaaa40fe21 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -13,9 +13,9 @@ describe "User Feed" do
end
end
- context 'user atom feed via RSS token' do
+ context 'user atom feed via feed token' do
it "renders user atom feed" do
- visit user_path(user, :atom, rss_token: user.rss_token)
+ visit user_path(user, :atom, feed_token: user.feed_token)
expect(body).to have_selector('feed title')
end
end
@@ -51,7 +51,7 @@ describe "User Feed" do
issue_event(issue, user)
note_event(note, user)
merge_request_event(merge_request, user)
- visit user_path(user, :atom, rss_token: user.rss_token)
+ visit user_path(user, :atom, feed_token: user.feed_token)
end
it 'has issue opened event' do
diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb
index a74a8aac2b2..941208fa244 100644
--- a/spec/features/dashboard/activity_spec.rb
+++ b/spec/features/dashboard/activity_spec.rb
@@ -12,8 +12,8 @@ feature 'Dashboard > Activity' do
visit activity_dashboard_path
end
- it_behaves_like "it has an RSS button with current_user's RSS token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
+ it_behaves_like "it has an RSS button with current_user's feed token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end
context 'event filters', :js do
diff --git a/spec/features/dashboard/issues_filter_spec.rb b/spec/features/dashboard/issues_filter_spec.rb
index bab34ac9346..8d0b0be1bd4 100644
--- a/spec/features/dashboard/issues_filter_spec.rb
+++ b/spec/features/dashboard/issues_filter_spec.rb
@@ -47,15 +47,15 @@ feature 'Dashboard Issues filtering', :js do
it 'updates atom feed link' do
visit_issues(milestone_title: '', assignee_id: user.id)
- link = find('.nav-controls a[title="Subscribe"]')
+ link = find('.nav-controls a[title="Subscribe to RSS feed"]')
params = CGI.parse(URI.parse(link[:href]).query)
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
- expect(params).to include('rss_token' => [user.rss_token])
+ expect(params).to include('feed_token' => [user.feed_token])
expect(params).to include('milestone_title' => [''])
expect(params).to include('assignee_id' => [user.id.to_s])
- expect(auto_discovery_params).to include('rss_token' => [user.rss_token])
+ expect(auto_discovery_params).to include('feed_token' => [user.feed_token])
expect(auto_discovery_params).to include('milestone_title' => [''])
expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
end
diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb
index e41a2e4ce09..3cc7b38550d 100644
--- a/spec/features/dashboard/issues_spec.rb
+++ b/spec/features/dashboard/issues_spec.rb
@@ -56,8 +56,8 @@ RSpec.describe 'Dashboard Issues' do
expect(page).to have_current_path(issues_dashboard_url(assignee_id: current_user.id, state: 'closed'), url: true)
end
- it_behaves_like "it has an RSS button with current_user's RSS token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
+ it_behaves_like "it has an RSS button with current_user's feed token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end
describe 'new issue dropdown' do
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index 257a3822503..ef2f0b5b31a 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -10,7 +10,7 @@ feature 'Dashboard Projects' do
sign_in(user)
end
- it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" do
+ it_behaves_like "an autodiscoverable RSS feed with current_user's feed token" do
before do
visit dashboard_projects_path
end
diff --git a/spec/features/groups/activity_spec.rb b/spec/features/groups/activity_spec.rb
index 7bc809b3104..0d7d3771071 100644
--- a/spec/features/groups/activity_spec.rb
+++ b/spec/features/groups/activity_spec.rb
@@ -15,8 +15,8 @@ feature 'Group activity page' do
visit path
end
- it_behaves_like "it has an RSS button with current_user's RSS token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
+ it_behaves_like "it has an RSS button with current_user's feed token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end
context 'when project is in the group', :js do
@@ -39,7 +39,7 @@ feature 'Group activity page' do
visit path
end
- it_behaves_like "it has an RSS button without an RSS token"
- it_behaves_like "an autodiscoverable RSS feed without an RSS token"
+ it_behaves_like "it has an RSS button without a feed token"
+ it_behaves_like "an autodiscoverable RSS feed without a feed token"
end
end
diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb
index 90bf7ba49f6..111a24c0d94 100644
--- a/spec/features/groups/issues_spec.rb
+++ b/spec/features/groups/issues_spec.rb
@@ -16,17 +16,21 @@ feature 'Group issues page' do
let(:access_level) { ProjectFeature::ENABLED }
context 'when signed in' do
- let(:user) { user_in_group }
-
- it_behaves_like "it has an RSS button with current_user's RSS token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
+ let(:user) do
+ user_in_group.ensure_feed_token
+ user_in_group.save!
+ user_in_group
+ end
+
+ it_behaves_like "it has an RSS button with current_user's feed token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end
context 'when signed out' do
let(:user) { nil }
- it_behaves_like "it has an RSS button without an RSS token"
- it_behaves_like "an autodiscoverable RSS feed without an RSS token"
+ it_behaves_like "it has an RSS button without a feed token"
+ it_behaves_like "an autodiscoverable RSS feed without a feed token"
end
end
diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb
index 3a0424d60f8..b7a7aa0e174 100644
--- a/spec/features/groups/show_spec.rb
+++ b/spec/features/groups/show_spec.rb
@@ -14,7 +14,7 @@ feature 'Group show page' do
visit path
end
- it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
context 'when group does not exist' do
let(:path) { group_path('not-exist') }
@@ -29,7 +29,7 @@ feature 'Group show page' do
visit path
end
- it_behaves_like "an autodiscoverable RSS feed without an RSS token"
+ it_behaves_like "an autodiscoverable RSS feed without a feed token"
end
context 'when group has a public project', :js do
diff --git a/spec/features/ics/dashboard_issues_spec.rb b/spec/features/ics/dashboard_issues_spec.rb
new file mode 100644
index 00000000000..5d6cd44ad1c
--- /dev/null
+++ b/spec/features/ics/dashboard_issues_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe 'Dashboard Issues Calendar Feed' do
+ describe 'GET /issues' do
+ let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') }
+ let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') }
+ let!(:project) { create(:project) }
+
+ before do
+ project.add_master(user)
+ end
+
+ context 'when authenticated' do
+ it 'renders calendar feed' do
+ sign_in user
+ visit issues_dashboard_path(:ics)
+
+ expect(response_headers['Content-Type']).to have_content('text/calendar')
+ expect(response_headers['Content-Disposition']).to have_content('inline')
+ expect(body).to have_text('BEGIN:VCALENDAR')
+ end
+ end
+
+ context 'when authenticated via personal access token' do
+ it 'renders calendar feed' do
+ personal_access_token = create(:personal_access_token, user: user)
+
+ visit issues_dashboard_path(:ics, private_token: personal_access_token.token)
+
+ expect(response_headers['Content-Type']).to have_content('text/calendar')
+ expect(response_headers['Content-Disposition']).to have_content('inline')
+ expect(body).to have_text('BEGIN:VCALENDAR')
+ end
+ end
+
+ context 'when authenticated via feed token' do
+ it 'renders calendar feed' do
+ visit issues_dashboard_path(:ics, feed_token: user.feed_token)
+
+ expect(response_headers['Content-Type']).to have_content('text/calendar')
+ expect(response_headers['Content-Disposition']).to have_content('inline')
+ expect(body).to have_text('BEGIN:VCALENDAR')
+ end
+ end
+
+ context 'issue with due date' do
+ let!(:issue) do
+ create(:issue, author: user, assignees: [assignee], project: project, title: 'test title',
+ description: 'test desc', due_date: Date.tomorrow)
+ end
+
+ it 'renders issue fields' do
+ visit issues_dashboard_path(:ics, feed_token: user.feed_token)
+
+ expect(body).to have_text("SUMMARY:test title (in #{project.full_path})")
+ # line length for ics is 75 chars
+ expected_description = "DESCRIPTION:Find out more at #{issue_url(issue)}".insert(75, "\r\n")
+ expect(body).to have_text(expected_description)
+ expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}")
+ expect(body).to have_text("URL:#{issue_url(issue)}")
+ expect(body).to have_text('TRANSP:TRANSPARENT')
+ end
+ end
+ end
+end
diff --git a/spec/features/ics/group_issues_spec.rb b/spec/features/ics/group_issues_spec.rb
new file mode 100644
index 00000000000..0a049be2ffe
--- /dev/null
+++ b/spec/features/ics/group_issues_spec.rb
@@ -0,0 +1,67 @@
+require 'spec_helper'
+
+describe 'Group Issues Calendar Feed' do
+ describe 'GET /issues' do
+ let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') }
+ let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') }
+ let!(:group) { create(:group) }
+ let!(:project) { create(:project, group: group) }
+
+ before do
+ project.add_developer(user)
+ group.add_developer(user)
+ end
+
+ context 'when authenticated' do
+ it 'renders calendar feed' do
+ sign_in user
+ visit issues_group_path(group, :ics)
+
+ expect(response_headers['Content-Type']).to have_content('text/calendar')
+ expect(response_headers['Content-Disposition']).to have_content('inline')
+ expect(body).to have_text('BEGIN:VCALENDAR')
+ end
+ end
+
+ context 'when authenticated via personal access token' do
+ it 'renders calendar feed' do
+ personal_access_token = create(:personal_access_token, user: user)
+
+ visit issues_group_path(group, :ics, private_token: personal_access_token.token)
+
+ expect(response_headers['Content-Type']).to have_content('text/calendar')
+ expect(response_headers['Content-Disposition']).to have_content('inline')
+ expect(body).to have_text('BEGIN:VCALENDAR')
+ end
+ end
+
+ context 'when authenticated via feed token' do
+ it 'renders calendar feed' do
+ visit issues_group_path(group, :ics, feed_token: user.feed_token)
+
+ expect(response_headers['Content-Type']).to have_content('text/calendar')
+ expect(response_headers['Content-Disposition']).to have_content('inline')
+ expect(body).to have_text('BEGIN:VCALENDAR')
+ end
+ end
+
+ context 'issue with due date' do
+ let!(:issue) do
+ create(:issue, author: user, assignees: [assignee], project: project, title: 'test title',
+ description: 'test desc', due_date: Date.tomorrow)
+ end
+
+ it 'renders issue fields' do
+ visit issues_group_path(group, :ics, feed_token: user.feed_token)
+
+ expect(body).to have_text("SUMMARY:test title (in #{project.full_path})")
+ # line length for ics is 75 chars
+ expected_description = "DESCRIPTION:Find out more at #{issue_url(issue)}".insert(75, "\r\n")
+ expect(body).to have_text(expected_description)
+ expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}")
+ expect(body).to have_text("URL:#{issue_url(issue)}")
+ expect(body).to have_text('TRANSP:TRANSPARENT')
+ end
+ end
+ end
+end
diff --git a/spec/features/ics/project_issues_spec.rb b/spec/features/ics/project_issues_spec.rb
new file mode 100644
index 00000000000..b99e9607f1d
--- /dev/null
+++ b/spec/features/ics/project_issues_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe 'Project Issues Calendar Feed' do
+ describe 'GET /issues' do
+ let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') }
+ let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') }
+ let!(:project) { create(:project) }
+ let!(:issue) { create(:issue, author: user, assignees: [assignee], project: project) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ context 'when authenticated' do
+ it 'renders calendar feed' do
+ sign_in user
+ visit project_issues_path(project, :ics)
+
+ expect(response_headers['Content-Type']).to have_content('text/calendar')
+ expect(response_headers['Content-Disposition']).to have_content('inline')
+ expect(body).to have_text('BEGIN:VCALENDAR')
+ end
+ end
+
+ context 'when authenticated via personal access token' do
+ it 'renders calendar feed' do
+ personal_access_token = create(:personal_access_token, user: user)
+
+ visit project_issues_path(project, :ics, private_token: personal_access_token.token)
+
+ expect(response_headers['Content-Type']).to have_content('text/calendar')
+ expect(response_headers['Content-Disposition']).to have_content('inline')
+ expect(body).to have_text('BEGIN:VCALENDAR')
+ end
+ end
+
+ context 'when authenticated via feed token' do
+ it 'renders calendar feed' do
+ visit project_issues_path(project, :ics, feed_token: user.feed_token)
+
+ expect(response_headers['Content-Type']).to have_content('text/calendar')
+ expect(response_headers['Content-Disposition']).to have_content('inline')
+ expect(body).to have_text('BEGIN:VCALENDAR')
+ end
+ end
+
+ context 'issue with due date' do
+ let!(:issue) do
+ create(:issue, author: user, assignees: [assignee], project: project, title: 'test title',
+ description: 'test desc', due_date: Date.tomorrow)
+ end
+
+ it 'renders issue fields' do
+ visit project_issues_path(project, :ics, feed_token: user.feed_token)
+
+ expect(body).to have_text("SUMMARY:test title (in #{project.full_path})")
+ # line length for ics is 75 chars
+ expected_description = "DESCRIPTION:Find out more at #{issue_url(issue)}".insert(75, "\r\n")
+ expect(body).to have_text(expected_description)
+ expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}")
+ expect(body).to have_text("URL:#{issue_url(issue)}")
+ expect(body).to have_text('TRANSP:TRANSPARENT')
+ end
+ end
+ end
+end
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 483122ae463..bc42618306f 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -468,13 +468,13 @@ describe 'Filter issues', :js do
it "for #{type}" do
visit path
- link = find_link('Subscribe')
+ link = find_link('Subscribe to RSS feed')
params = CGI.parse(URI.parse(link[:href]).query)
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
expected = {
- 'rss_token' => [user.rss_token],
+ 'feed_token' => [user.feed_token],
'milestone_title' => [milestone.title],
'assignee_id' => [user.id.to_s]
}
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index e7f2e142b2d..c6dcd97631d 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -340,6 +340,20 @@ describe 'Issues' do
expect(page).to have_content('baz')
end
end
+
+ it 'filters by due next month and previous two weeks' do
+ foo.update(due_date: Date.today - 4.weeks)
+ bar.update(due_date: (Date.today + 2.months).beginning_of_month)
+ baz.update(due_date: Date.yesterday)
+
+ visit project_issues_path(project, due_date: Issue::DueNextMonthAndPreviousTwoWeeks.name)
+
+ page.within '.issues-holder' do
+ expect(page).not_to have_content('foo')
+ expect(page).not_to have_content('bar')
+ expect(page).to have_content('baz')
+ end
+ end
end
describe 'sorting by milestone' do
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index 15dcb30cbdd..2e0753c3bfb 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -56,21 +56,21 @@ describe 'Profile account page', :js do
end
end
- describe 'when I reset RSS token' do
+ describe 'when I reset feed token' do
before do
visit profile_personal_access_tokens_path
end
- it 'resets RSS token' do
- within('.rss-token-reset') do
- previous_token = find("#rss_token").value
+ it 'resets feed token' do
+ within('.feed-token-reset') do
+ previous_token = find("#feed_token").value
accept_confirm { click_link('reset it') }
- expect(find('#rss_token').value).not_to eq(previous_token)
+ expect(find('#feed_token').value).not_to eq(previous_token)
end
- expect(page).to have_content 'RSS token was successfully reset'
+ expect(page).to have_content 'Feed token was successfully reset'
end
end
diff --git a/spec/features/projects/activity/rss_spec.rb b/spec/features/projects/activity/rss_spec.rb
index cd1cfe07998..4ac34adde0e 100644
--- a/spec/features/projects/activity/rss_spec.rb
+++ b/spec/features/projects/activity/rss_spec.rb
@@ -15,7 +15,7 @@ feature 'Project Activity RSS' do
visit path
end
- it_behaves_like "it has an RSS button with current_user's RSS token"
+ it_behaves_like "it has an RSS button with current_user's feed token"
end
context 'when signed out' do
@@ -23,6 +23,6 @@ feature 'Project Activity RSS' do
visit path
end
- it_behaves_like "it has an RSS button without an RSS token"
+ it_behaves_like "it has an RSS button without a feed token"
end
end
diff --git a/spec/features/projects/commits/rss_spec.rb b/spec/features/projects/commits/rss_spec.rb
index 0d9c7355ddd..0bc207da970 100644
--- a/spec/features/projects/commits/rss_spec.rb
+++ b/spec/features/projects/commits/rss_spec.rb
@@ -12,8 +12,8 @@ feature 'Project Commits RSS' do
visit path
end
- it_behaves_like "it has an RSS button with current_user's RSS token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
+ it_behaves_like "it has an RSS button with current_user's feed token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end
context 'when signed out' do
@@ -21,7 +21,7 @@ feature 'Project Commits RSS' do
visit path
end
- it_behaves_like "it has an RSS button without an RSS token"
- it_behaves_like "an autodiscoverable RSS feed without an RSS token"
+ it_behaves_like "it has an RSS button without a feed token"
+ it_behaves_like "an autodiscoverable RSS feed without a feed token"
end
end
diff --git a/spec/features/projects/issues/rss_spec.rb b/spec/features/projects/issues/rss_spec.rb
index ff91aabc311..8b1f7d432ee 100644
--- a/spec/features/projects/issues/rss_spec.rb
+++ b/spec/features/projects/issues/rss_spec.rb
@@ -17,8 +17,8 @@ feature 'Project Issues RSS' do
visit path
end
- it_behaves_like "it has an RSS button with current_user's RSS token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
+ it_behaves_like "it has an RSS button with current_user's feed token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end
context 'when signed out' do
@@ -26,7 +26,7 @@ feature 'Project Issues RSS' do
visit path
end
- it_behaves_like "it has an RSS button without an RSS token"
- it_behaves_like "an autodiscoverable RSS feed without an RSS token"
+ it_behaves_like "it has an RSS button without a feed token"
+ it_behaves_like "an autodiscoverable RSS feed without a feed token"
end
end
diff --git a/spec/features/projects/milestones/milestone_spec.rb b/spec/features/projects/milestones/milestone_spec.rb
index 30de3e83fbb..20a52d6011f 100644
--- a/spec/features/projects/milestones/milestone_spec.rb
+++ b/spec/features/projects/milestones/milestone_spec.rb
@@ -17,8 +17,8 @@ feature 'Project milestone' do
it 'shows issues tab' do
within('#content-body') do
expect(page).to have_link 'Issues', href: '#tab-issues'
- expect(page).to have_selector '.nav-links li.active', count: 1
- expect(find('.nav-links li.active')).to have_content 'Issues'
+ expect(page).to have_selector '.nav-links li a.active', count: 1
+ expect(find('.nav-links li a.active')).to have_content 'Issues'
end
end
@@ -44,8 +44,8 @@ feature 'Project milestone' do
it 'hides issues tab' do
within('#content-body') do
expect(page).not_to have_link 'Issues', href: '#tab-issues'
- expect(page).to have_selector '.nav-links li.active', count: 1
- expect(find('.nav-links li.active')).to have_content 'Merge Requests'
+ expect(page).to have_selector '.nav-links li a.active', count: 1
+ expect(find('.nav-links li a.active')).to have_content 'Merge Requests'
end
end
diff --git a/spec/features/projects/show/rss_spec.rb b/spec/features/projects/show/rss_spec.rb
index d02eaf34533..52164d30c40 100644
--- a/spec/features/projects/show/rss_spec.rb
+++ b/spec/features/projects/show/rss_spec.rb
@@ -12,7 +12,7 @@ feature 'Projects > Show > RSS' do
visit path
end
- it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end
context 'when signed out' do
@@ -20,6 +20,6 @@ feature 'Projects > Show > RSS' do
visit path
end
- it_behaves_like "an autodiscoverable RSS feed without an RSS token"
+ it_behaves_like "an autodiscoverable RSS feed without a feed token"
end
end
diff --git a/spec/features/projects/tree/rss_spec.rb b/spec/features/projects/tree/rss_spec.rb
index 6407370ac0d..f52b3cc1d86 100644
--- a/spec/features/projects/tree/rss_spec.rb
+++ b/spec/features/projects/tree/rss_spec.rb
@@ -12,7 +12,7 @@ feature 'Project Tree RSS' do
visit path
end
- it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end
context 'when signed out' do
@@ -20,6 +20,6 @@ feature 'Project Tree RSS' do
visit path
end
- it_behaves_like "an autodiscoverable RSS feed without an RSS token"
+ it_behaves_like "an autodiscoverable RSS feed without a feed token"
end
end
diff --git a/spec/features/users/rss_spec.rb b/spec/features/users/rss_spec.rb
index 7c5abe54d56..c3734b5c808 100644
--- a/spec/features/users/rss_spec.rb
+++ b/spec/features/users/rss_spec.rb
@@ -10,7 +10,7 @@ feature 'User RSS' do
visit path
end
- it_behaves_like "it has an RSS button with current_user's RSS token"
+ it_behaves_like "it has an RSS button with current_user's feed token"
end
context 'when signed out' do
@@ -18,6 +18,6 @@ feature 'User RSS' do
visit path
end
- it_behaves_like "it has an RSS button without an RSS token"
+ it_behaves_like "it has an RSS button without a feed token"
end
end
diff --git a/spec/helpers/calendar_helper_spec.rb b/spec/helpers/calendar_helper_spec.rb
new file mode 100644
index 00000000000..828a9d9fea0
--- /dev/null
+++ b/spec/helpers/calendar_helper_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe CalendarHelper do
+ describe '#calendar_url_options' do
+ context 'when signed in' do
+ it "includes the current_user's feed_token" do
+ current_user = create(:user)
+ allow(helper).to receive(:current_user).and_return(current_user)
+ expect(helper.calendar_url_options).to include feed_token: current_user.feed_token
+ end
+ end
+
+ context 'when signed out' do
+ it "does not have a feed_token" do
+ allow(helper).to receive(:current_user).and_return(nil)
+ expect(helper.calendar_url_options[:feed_token]).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/helpers/rss_helper_spec.rb b/spec/helpers/rss_helper_spec.rb
index 269e1057e8d..a7f9bdf07e4 100644
--- a/spec/helpers/rss_helper_spec.rb
+++ b/spec/helpers/rss_helper_spec.rb
@@ -3,17 +3,17 @@ require 'spec_helper'
describe RssHelper do
describe '#rss_url_options' do
context 'when signed in' do
- it "includes the current_user's rss_token" do
+ it "includes the current_user's feed_token" do
current_user = create(:user)
allow(helper).to receive(:current_user).and_return(current_user)
- expect(helper.rss_url_options).to include rss_token: current_user.rss_token
+ expect(helper.rss_url_options).to include feed_token: current_user.feed_token
end
end
context 'when signed out' do
- it "does not have an rss_token" do
+ it "does not have a feed_token" do
allow(helper).to receive(:current_user).and_return(nil)
- expect(helper.rss_url_options[:rss_token]).to be_nil
+ expect(helper.rss_url_options[:feed_token]).to be_nil
end
end
end
diff --git a/spec/javascripts/ide/components/external_link_spec.js b/spec/javascripts/ide/components/external_link_spec.js
new file mode 100644
index 00000000000..b3d94c041fa
--- /dev/null
+++ b/spec/javascripts/ide/components/external_link_spec.js
@@ -0,0 +1,35 @@
+import Vue from 'vue';
+import externalLink from '~/ide/components/external_link.vue';
+import createVueComponent from '../../helpers/vue_mount_component_helper';
+import { file } from '../helpers';
+
+describe('ExternalLink', () => {
+ const activeFile = file();
+ let vm;
+
+ function createComponent() {
+ const ExternalLink = Vue.extend(externalLink);
+
+ activeFile.permalink = 'test';
+
+ return createVueComponent(ExternalLink, {
+ file: activeFile,
+ });
+ }
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders the external link with the correct href', done => {
+ activeFile.binary = true;
+ vm = createComponent();
+
+ vm.$nextTick(() => {
+ const openLink = vm.$el.querySelector('a');
+
+ expect(openLink.href).toMatch(`/${activeFile.permalink}`);
+ done();
+ });
+ });
+});
diff --git a/spec/javascripts/ide/components/ide_file_buttons_spec.js b/spec/javascripts/ide/components/ide_file_buttons_spec.js
deleted file mode 100644
index 8ac8d1b2acf..00000000000
--- a/spec/javascripts/ide/components/ide_file_buttons_spec.js
+++ /dev/null
@@ -1,61 +0,0 @@
-import Vue from 'vue';
-import repoFileButtons from '~/ide/components/ide_file_buttons.vue';
-import createVueComponent from '../../helpers/vue_mount_component_helper';
-import { file } from '../helpers';
-
-describe('RepoFileButtons', () => {
- const activeFile = file();
- let vm;
-
- function createComponent() {
- const RepoFileButtons = Vue.extend(repoFileButtons);
-
- activeFile.rawPath = 'test';
- activeFile.blamePath = 'test';
- activeFile.commitsPath = 'test';
-
- return createVueComponent(RepoFileButtons, {
- file: activeFile,
- });
- }
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('renders Raw, Blame, History and Permalink', done => {
- vm = createComponent();
-
- vm.$nextTick(() => {
- const raw = vm.$el.querySelector('.raw');
- const blame = vm.$el.querySelector('.blame');
- const history = vm.$el.querySelector('.history');
-
- expect(raw.href).toMatch(`/${activeFile.rawPath}`);
- expect(raw.getAttribute('data-original-title')).toEqual('Raw');
- expect(blame.href).toMatch(`/${activeFile.blamePath}`);
- expect(blame.getAttribute('data-original-title')).toEqual('Blame');
- expect(history.href).toMatch(`/${activeFile.commitsPath}`);
- expect(history.getAttribute('data-original-title')).toEqual('History');
- expect(vm.$el.querySelector('.permalink').getAttribute('data-original-title')).toEqual(
- 'Permalink',
- );
-
- done();
- });
- });
-
- it('renders Download', done => {
- activeFile.binary = true;
- vm = createComponent();
-
- vm.$nextTick(() => {
- const raw = vm.$el.querySelector('.raw');
-
- expect(raw.href).toMatch(`/${activeFile.rawPath}`);
- expect(raw.getAttribute('data-original-title')).toEqual('Download');
-
- done();
- });
- });
-});
diff --git a/spec/lib/gitlab/auth/request_authenticator_spec.rb b/spec/lib/gitlab/auth/request_authenticator_spec.rb
index ffcd90b9fcb..242ab4a91dd 100644
--- a/spec/lib/gitlab/auth/request_authenticator_spec.rb
+++ b/spec/lib/gitlab/auth/request_authenticator_spec.rb
@@ -39,19 +39,19 @@ describe Gitlab::Auth::RequestAuthenticator do
describe '#find_sessionless_user' do
let!(:access_token_user) { build(:user) }
- let!(:rss_token_user) { build(:user) }
+ let!(:feed_token_user) { build(:user) }
it 'returns access_token user first' do
allow_any_instance_of(described_class).to receive(:find_user_from_access_token).and_return(access_token_user)
- allow_any_instance_of(described_class).to receive(:find_user_from_rss_token).and_return(rss_token_user)
+ allow_any_instance_of(described_class).to receive(:find_user_from_feed_token).and_return(feed_token_user)
expect(subject.find_sessionless_user).to eq access_token_user
end
- it 'returns rss_token user if no access_token user found' do
- allow_any_instance_of(described_class).to receive(:find_user_from_rss_token).and_return(rss_token_user)
+ it 'returns feed_token user if no access_token user found' do
+ allow_any_instance_of(described_class).to receive(:find_user_from_feed_token).and_return(feed_token_user)
- expect(subject.find_sessionless_user).to eq rss_token_user
+ expect(subject.find_sessionless_user).to eq feed_token_user
end
it 'returns nil if no user found' do
diff --git a/spec/lib/gitlab/auth/user_auth_finders_spec.rb b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
index 2733eef6611..136646bd4ee 100644
--- a/spec/lib/gitlab/auth/user_auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
@@ -46,34 +46,54 @@ describe Gitlab::Auth::UserAuthFinders do
end
end
- describe '#find_user_from_rss_token' do
+ describe '#find_user_from_feed_token' do
context 'when the request format is atom' do
before do
env['HTTP_ACCEPT'] = 'application/atom+xml'
end
- it 'returns user if valid rss_token' do
- set_param(:rss_token, user.rss_token)
+ context 'when feed_token param is provided' do
+ it 'returns user if valid feed_token' do
+ set_param(:feed_token, user.feed_token)
- expect(find_user_from_rss_token).to eq user
- end
+ expect(find_user_from_feed_token).to eq user
+ end
+
+ it 'returns nil if feed_token is blank' do
+ expect(find_user_from_feed_token).to be_nil
+ end
+
+ it 'returns exception if invalid feed_token' do
+ set_param(:feed_token, 'invalid_token')
- it 'returns nil if rss_token is blank' do
- expect(find_user_from_rss_token).to be_nil
+ expect { find_user_from_feed_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
+ end
end
- it 'returns exception if invalid rss_token' do
- set_param(:rss_token, 'invalid_token')
+ context 'when rss_token param is provided' do
+ it 'returns user if valid rssd_token' do
+ set_param(:rss_token, user.feed_token)
- expect { find_user_from_rss_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
+ expect(find_user_from_feed_token).to eq user
+ end
+
+ it 'returns nil if rss_token is blank' do
+ expect(find_user_from_feed_token).to be_nil
+ end
+
+ it 'returns exception if invalid rss_token' do
+ set_param(:rss_token, 'invalid_token')
+
+ expect { find_user_from_feed_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
+ end
end
end
context 'when the request format is not atom' do
it 'returns nil' do
- set_param(:rss_token, user.rss_token)
+ set_param(:feed_token, user.feed_token)
- expect(find_user_from_rss_token).to be_nil
+ expect(find_user_from_feed_token).to be_nil
end
end
@@ -81,7 +101,7 @@ describe Gitlab::Auth::UserAuthFinders do
it 'the method call does not modify the original value' do
env['action_dispatch.request.formats'] = nil
- find_user_from_rss_token
+ find_user_from_feed_token
expect(env['action_dispatch.request.formats']).to be_nil
end
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index e2547ed0311..94eaf86ef80 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -22,6 +22,12 @@ describe Gitlab::Git::Blob, seed_helper: true do
it { expect(blob).to eq(nil) }
end
+ context 'utf-8 branch' do
+ let(:blob) { Gitlab::Git::Blob.find(repository, 'Ääh-test-utf-8', "files/ruby/popen.rb")}
+
+ it { expect(blob.id).to eq(SeedRepo::RubyBlob::ID) }
+ end
+
context 'blank path' do
let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, '') }
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 08c6d1e55e9..89be8a1b7f2 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -603,8 +603,8 @@ describe Gitlab::Git::Commit, seed_helper: true do
let(:commit) { described_class.find(repository, 'master') }
subject { commit.ref_names(repository) }
- it 'has 1 element' do
- expect(subject.size).to eq(1)
+ it 'has 2 element' do
+ expect(subject.size).to eq(2)
end
it { is_expected.to include("master") }
it { is_expected.not_to include("feature") }
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index af6a486ab20..dd5c498706d 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1374,7 +1374,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
describe '#branch_count' do
it 'returns the number of branches' do
- expect(repository.branch_count).to eq(10)
+ expect(repository.branch_count).to eq(11)
end
context 'with local and remote branches' do
@@ -2248,7 +2248,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
describe '#checksum' do
it 'calculates the checksum for non-empty repo' do
- expect(repository.checksum).to eq '54f21be4c32c02f6788d72207fa03ad3bce725e4'
+ expect(repository.checksum).to eq '4be7d24ce7e8d845502d599b72d567d23e6a40c0'
end
it 'returns 0000000000000000000000000000000000000000 for an empty repo' do
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index fb5fd300dbb..a129855dbd8 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -310,3 +310,8 @@ lfs_file_locks:
- user
project_badges:
- project
+metrics:
+- merge_request
+- latest_closed_by
+- merged_by
+- pipeline
diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb
index 5f0dfd64b15..cf9e0f71910 100644
--- a/spec/lib/gitlab/import_export/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb
@@ -119,6 +119,25 @@ describe Gitlab::ImportExport::RelationFactory do
end
end
+ context 'overrided model with pluralized name' do
+ let(:relation_sym) { :metrics }
+
+ let(:relation_hash) do
+ {
+ 'id' => 99,
+ 'merge_request_id' => 99,
+ 'merged_at' => Time.now,
+ 'merged_by_id' => 99,
+ 'latest_closed_at' => nil,
+ 'latest_closed_by_id' => nil
+ }
+ end
+
+ it 'does not raise errors' do
+ expect { created_object }.not_to raise_error
+ end
+ end
+
context 'Project references' do
let(:relation_sym) { :project_foo_model }
let(:relation_hash) do
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 3d5271cd030..74e7a45fd6c 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -205,6 +205,19 @@ MergeRequestDiffFile:
- b_mode
- too_large
- binary
+MergeRequest::Metrics:
+- id
+- created_at
+- updated_at
+- merge_request_id
+- pipeline_id
+- latest_closed_by_id
+- latest_closed_at
+- merged_by_id
+- merged_at
+- latest_build_started_at
+- latest_build_finished_at
+- first_deployed_to_production_at
Ci::Pipeline:
- id
- project_id
diff --git a/spec/migrations/migrate_remaining_mr_metrics_populating_background_migration_spec.rb b/spec/migrations/migrate_remaining_mr_metrics_populating_background_migration_spec.rb
new file mode 100644
index 00000000000..47dab18183c
--- /dev/null
+++ b/spec/migrations/migrate_remaining_mr_metrics_populating_background_migration_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20180521162137_migrate_remaining_mr_metrics_populating_background_migration.rb')
+
+describe MigrateRemainingMrMetricsPopulatingBackgroundMigration, :migration, :sidekiq do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:mrs) { table(:merge_requests) }
+
+ before do
+ namespaces.create!(id: 1, name: 'foo', path: 'foo')
+ projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1', namespace_id: 1)
+ projects.create!(id: 456, name: 'gitlab2', path: 'gitlab2', namespace_id: 1)
+ projects.create!(id: 789, name: 'gitlab3', path: 'gitlab3', namespace_id: 1)
+ mrs.create!(title: 'foo', target_branch: 'target', source_branch: 'source', target_project_id: 123)
+ mrs.create!(title: 'bar', target_branch: 'target', source_branch: 'source', target_project_id: 456)
+ mrs.create!(title: 'kux', target_branch: 'target', source_branch: 'source', target_project_id: 789)
+ end
+
+ it 'correctly schedules background migrations' do
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(10.minutes, mrs.first.id, mrs.second.id)
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(20.minutes, mrs.third.id, mrs.third.id)
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb
index dfb83578fce..9b804429138 100644
--- a/spec/models/concerns/token_authenticatable_spec.rb
+++ b/spec/models/concerns/token_authenticatable_spec.rb
@@ -12,7 +12,7 @@ shared_examples 'TokenAuthenticatable' do
end
describe User, 'TokenAuthenticatable' do
- let(:token_field) { :rss_token }
+ let(:token_field) { :feed_token }
it_behaves_like 'TokenAuthenticatable'
describe 'ensures authentication token' do
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
index 3be023a48c1..68ab9fd08ec 100644
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ b/spec/models/project_services/kubernetes_service_spec.rb
@@ -65,7 +65,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
before do
kubernetes_service.update_attribute(:active, false)
- kubernetes_service.properties[:namespace] = "foo"
+ kubernetes_service.properties['namespace'] = "foo"
end
it 'should not update attributes' do
@@ -82,7 +82,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
let(:kubernetes_service) { create(:kubernetes_service) }
it 'should update attributes' do
- kubernetes_service.properties[:namespace] = 'foo'
+ kubernetes_service.properties['namespace'] = 'foo'
expect(kubernetes_service.save).to be_truthy
end
end
@@ -92,7 +92,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
before do
kubernetes_service.active = false
- kubernetes_service.properties[:namespace] = 'foo'
+ kubernetes_service.properties['namespace'] = 'foo'
kubernetes_service.save
end
@@ -105,7 +105,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
end
it 'should update attributes' do
- expect(kubernetes_service.properties[:namespace]).to eq("foo")
+ expect(kubernetes_service.properties['namespace']).to eq("foo")
end
end
@@ -113,12 +113,12 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
let(:kubernetes_service) { create(:kubernetes_service, template: true, active: false) }
before do
- kubernetes_service.properties[:namespace] = 'foo'
+ kubernetes_service.properties['namespace'] = 'foo'
end
it 'should update attributes' do
expect(kubernetes_service.save).to be_truthy
- expect(kubernetes_service.properties[:namespace]).to eq('foo')
+ expect(kubernetes_service.properties['namespace']).to eq('foo')
end
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 6a2f4a39f09..16b409844fa 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -644,13 +644,13 @@ describe User do
end
end
- describe 'rss token' do
- it 'ensures an rss token on read' do
- user = create(:user, rss_token: nil)
- rss_token = user.rss_token
+ describe 'feed token' do
+ it 'ensures a feed token on read' do
+ user = create(:user, feed_token: nil)
+ feed_token = user.feed_token
- expect(rss_token).not_to be_blank
- expect(user.reload.rss_token).to eq rss_token
+ expect(feed_token).not_to be_blank
+ expect(user.reload.feed_token).to eq feed_token
end
end
diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb
index efd175247b5..6dfaa3b72f7 100644
--- a/spec/presenters/ci/build_presenter_spec.rb
+++ b/spec/presenters/ci/build_presenter_spec.rb
@@ -219,11 +219,11 @@ describe Ci::BuildPresenter do
end
describe '#callout_failure_message' do
- let(:build) { create(:ci_build, :failed, :script_failure) }
+ let(:build) { create(:ci_build, :failed, :api_failure) }
it 'returns a verbose failure reason' do
description = subject.callout_failure_message
- expect(description).to eq('There has been a script failure. Check the job log for more information')
+ expect(description).to eq('There has been an API failure, please try again')
end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 8b168816d6c..605761867bf 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -72,12 +72,6 @@ describe API::MergeRequests do
expect(response).to have_gitlab_http_status(401)
end
-
- it "returns authentication error when scope is created_by_me" do
- get api("/merge_requests"), scope: 'created_by_me'
-
- expect(response).to have_gitlab_http_status(401)
- end
end
context 'when authenticated' do
@@ -229,224 +223,46 @@ describe API::MergeRequests do
end
describe "GET /projects/:id/merge_requests" do
- context "when unauthenticated" do
- it 'returns merge requests for public projects' do
- get api("/projects/#{project.id}/merge_requests")
-
- expect_paginated_array_response
- end
-
- it "returns 404 for non public projects" do
- project = create(:project, :private)
- get api("/projects/#{project.id}/merge_requests")
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
-
- context "when authenticated" do
- it 'avoids N+1 queries' do
- control = ActiveRecord::QueryRecorder.new do
- get api("/projects/#{project.id}/merge_requests", user)
- end
+ let(:endpoint_path) { "/projects/#{project.id}/merge_requests" }
- create(:merge_request, state: 'closed', milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time)
-
- create(:merge_request, milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time)
-
- expect do
- get api("/projects/#{project.id}/merge_requests", user)
- end.not_to exceed_query_limit(control)
- end
-
- it "returns an array of all merge_requests" do
- get api("/projects/#{project.id}/merge_requests", user)
-
- expect_response_ordered_exactly(merge_request_merged, merge_request_closed, merge_request)
- expect(json_response.last['title']).to eq(merge_request.title)
- expect(json_response.last).to have_key('web_url')
- expect(json_response.last['sha']).to eq(merge_request.diff_head_sha)
- expect(json_response.last['merge_commit_sha']).to be_nil
- expect(json_response.last['merge_commit_sha']).to eq(merge_request.merge_commit_sha)
- expect(json_response.last['downvotes']).to eq(1)
- expect(json_response.last['upvotes']).to eq(1)
- expect(json_response.last['labels']).to eq([label2.title, label.title])
- expect(json_response.first['title']).to eq(merge_request_merged.title)
- expect(json_response.first['sha']).to eq(merge_request_merged.diff_head_sha)
- expect(json_response.first['merge_commit_sha']).not_to be_nil
- expect(json_response.first['merge_commit_sha']).to eq(merge_request_merged.merge_commit_sha)
- expect(json_response.first['squash']).to eq(merge_request_merged.squash)
- end
-
- it "returns an array of all merge_requests using simple mode" do
- get api("/projects/#{project.id}/merge_requests?view=simple", user)
-
- expect_response_ordered_exactly(merge_request_merged, merge_request_closed, merge_request)
- expect(json_response.last.keys).to match_array(%w(id iid title web_url created_at description project_id state updated_at))
- expect(json_response.last['iid']).to eq(merge_request.iid)
- expect(json_response.last['title']).to eq(merge_request.title)
- expect(json_response.last).to have_key('web_url')
- expect(json_response.first['iid']).to eq(merge_request_merged.iid)
- expect(json_response.first['title']).to eq(merge_request_merged.title)
- expect(json_response.first).to have_key('web_url')
- end
-
- it "returns an array of all merge_requests" do
- get api("/projects/#{project.id}/merge_requests?state", user)
-
- expect_response_ordered_exactly(merge_request_merged, merge_request_closed, merge_request)
- expect(json_response.last['title']).to eq(merge_request.title)
- end
-
- it "returns an array of open merge_requests" do
- get api("/projects/#{project.id}/merge_requests?state=opened", user)
-
- expect_response_ordered_exactly(merge_request)
- expect(json_response.last['title']).to eq(merge_request.title)
- end
+ it_behaves_like 'merge requests list'
- it "returns an array of closed merge_requests" do
- get api("/projects/#{project.id}/merge_requests?state=closed", user)
-
- expect_response_ordered_exactly(merge_request_closed)
- expect(json_response.first['title']).to eq(merge_request_closed.title)
- end
-
- it "returns an array of merged merge_requests" do
- get api("/projects/#{project.id}/merge_requests?state=merged", user)
-
- expect_response_ordered_exactly(merge_request_merged)
- expect(json_response.first['title']).to eq(merge_request_merged.title)
- end
-
- it 'returns merge_request by "iids" array' do
- get api("/projects/#{project.id}/merge_requests", user), iids: [merge_request.iid, merge_request_closed.iid]
-
- expect_response_ordered_exactly(merge_request_closed, merge_request)
- expect(json_response.first['title']).to eq merge_request_closed.title
- end
-
- it 'matches V4 response schema' do
- get api("/projects/#{project.id}/merge_requests", user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to match_response_schema('public_api/v4/merge_requests')
- end
-
- it 'returns an empty array if no issue matches milestone' do
- get api("/projects/#{project.id}/merge_requests", user), milestone: '1.0.0'
-
- expect_paginated_array_response
- expect(json_response.length).to eq(0)
- end
-
- it 'returns an empty array if milestone does not exist' do
- get api("/projects/#{project.id}/merge_requests", user), milestone: 'foo'
-
- expect_paginated_array_response
- expect(json_response.length).to eq(0)
- end
-
- it 'returns an array of merge requests in given milestone' do
- get api("/projects/#{project.id}/merge_requests", user), milestone: '0.9'
-
- expect(json_response.first['title']).to eq merge_request_closed.title
- expect(json_response.first['id']).to eq merge_request_closed.id
- end
-
- it 'returns an array of merge requests matching state in milestone' do
- get api("/projects/#{project.id}/merge_requests", user), milestone: '0.9', state: 'closed'
-
- expect_response_ordered_exactly(merge_request_closed)
- end
-
- it 'returns an array of labeled merge requests' do
- get api("/projects/#{project.id}/merge_requests?labels=#{label.title}", user)
-
- expect_paginated_array_response
- expect(json_response.length).to eq(1)
- expect(json_response.first['labels']).to eq([label2.title, label.title])
- end
-
- it 'returns an array of labeled merge requests where all labels match' do
- get api("/projects/#{project.id}/merge_requests?labels=#{label.title},foo,bar", user)
-
- expect_paginated_array_response
- expect(json_response.length).to eq(0)
- end
-
- it 'returns an empty array if no merge request matches labels' do
- get api("/projects/#{project.id}/merge_requests?labels=foo,bar", user)
-
- expect_paginated_array_response
- expect(json_response.length).to eq(0)
- end
-
- it 'returns an array of labeled merge requests that are merged for a milestone' do
- bug_label = create(:label, title: 'bug', color: '#FFAABB', project: project)
-
- mr1 = create(:merge_request, state: "merged", source_project: project, target_project: project, milestone: milestone)
- mr2 = create(:merge_request, state: "merged", source_project: project, target_project: project, milestone: milestone1)
- mr3 = create(:merge_request, state: "closed", source_project: project, target_project: project, milestone: milestone1)
- _mr = create(:merge_request, state: "merged", source_project: project, target_project: project, milestone: milestone1)
-
- create(:label_link, label: bug_label, target: mr1)
- create(:label_link, label: bug_label, target: mr2)
- create(:label_link, label: bug_label, target: mr3)
-
- get api("/projects/#{project.id}/merge_requests?labels=#{bug_label.title}&milestone=#{milestone1.title}&state=merged", user)
-
- expect_response_ordered_exactly(mr2)
- end
-
- context "with ordering" do
- let(:merge_requests) { [merge_request_merged, merge_request_closed, merge_request] }
-
- before do
- @mr_later = mr_with_later_created_and_updated_at_time
- @mr_earlier = mr_with_earlier_created_and_updated_at_time
- end
-
- it "returns an array of merge_requests in ascending order" do
- get api("/projects/#{project.id}/merge_requests?sort=asc", user)
-
- expect_response_ordered_exactly(*merge_requests.sort_by { |mr| mr['created_at'] })
- end
-
- it "returns an array of merge_requests in descending order" do
- get api("/projects/#{project.id}/merge_requests?sort=desc", user)
+ it "returns 404 for non public projects" do
+ project = create(:project, :private)
- expect_response_ordered_exactly(*merge_requests.sort_by { |mr| mr['created_at'] }.reverse)
- end
+ get api("/projects/#{project.id}/merge_requests")
- it "returns an array of merge_requests ordered by updated_at" do
- get api("/projects/#{project.id}/merge_requests?order_by=updated_at", user)
+ expect(response).to have_gitlab_http_status(404)
+ end
- expect_response_ordered_exactly(*merge_requests.sort_by { |mr| mr['updated_at'] }.reverse)
- end
+ it 'returns merge_request by "iids" array' do
+ get api(endpoint_path, user), iids: [merge_request.iid, merge_request_closed.iid]
- it "returns an array of merge_requests ordered by created_at" do
- get api("/projects/#{project.id}/merge_requests?order_by=created_at&sort=asc", user)
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(2)
+ expect(json_response.first['title']).to eq merge_request_closed.title
+ expect(json_response.first['id']).to eq merge_request_closed.id
+ end
+ end
- expect_response_ordered_exactly(*merge_requests.sort_by { |mr| mr['created_at'] })
- end
- end
+ describe "GET /groups/:id/merge_requests" do
+ let!(:group) { create(:group, :public) }
+ let!(:project) { create(:project, :public, :repository, creator: user, namespace: group, only_allow_merge_if_pipeline_succeeds: false) }
+ let(:endpoint_path) { "/groups/#{group.id}/merge_requests" }
- context 'source_branch param' do
- it 'returns merge requests with the given source branch' do
- get api('/merge_requests', user), source_branch: merge_request_closed.source_branch, state: 'all'
+ before do
+ group.add_reporter(user)
+ end
- expect_response_contain_exactly(merge_request_closed, merge_request_merged)
- end
- end
+ it_behaves_like 'merge requests list'
- context 'target_branch param' do
- it 'returns merge requests with the given target branch' do
- get api('/merge_requests', user), target_branch: merge_request_closed.target_branch, state: 'all'
+ context 'when have subgroups', :nested_groups do
+ let!(:group) { create(:group, :public) }
+ let!(:subgroup) { create(:group, parent: group) }
+ let!(:project) { create(:project, :public, :repository, creator: user, namespace: subgroup, only_allow_merge_if_pipeline_succeeds: false) }
- expect_response_contain_exactly(merge_request_closed, merge_request_merged)
- end
- end
+ it_behaves_like 'merge requests list'
end
end
diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb
index b18e922b063..c0a3ea397df 100644
--- a/spec/requests/rack_attack_global_spec.rb
+++ b/spec/requests/rack_attack_global_spec.rb
@@ -349,7 +349,7 @@ describe 'Rack Attack global throttles' do
end
def rss_url(user)
- "/dashboard/projects.atom?rss_token=#{user.rss_token}"
+ "/dashboard/projects.atom?feed_token=#{user.feed_token}"
end
def private_token_headers(user)
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index 9345671a1a7..dd8f6239587 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -162,8 +162,8 @@ describe ProfilesController, "routing" do
expect(get("/profile/audit_log")).to route_to('profiles#audit_log')
end
- it "to #reset_rss_token" do
- expect(put("/profile/reset_rss_token")).to route_to('profiles#reset_rss_token')
+ it "to #reset_feed_token" do
+ expect(put("/profile/reset_feed_token")).to route_to('profiles#reset_feed_token')
end
it "to #show" do
@@ -249,7 +249,11 @@ describe DashboardController, "routing" do
end
it "to #issues" do
- expect(get("/dashboard/issues")).to route_to('dashboard#issues')
+ expect(get("/dashboard/issues.html")).to route_to('dashboard#issues', format: 'html')
+ end
+
+ it "to #calendar_issues" do
+ expect(get("/dashboard/issues.ics")).to route_to('dashboard#issues_calendar', format: 'ics')
end
it "to #merge_requests" do
diff --git a/spec/serializers/job_entity_spec.rb b/spec/serializers/job_entity_spec.rb
index c90396ebb28..a5581a34517 100644
--- a/spec/serializers/job_entity_spec.rb
+++ b/spec/serializers/job_entity_spec.rb
@@ -131,7 +131,7 @@ describe JobEntity do
end
context 'when job failed' do
- let(:job) { create(:ci_build, :script_failure) }
+ let(:job) { create(:ci_build, :api_failure) }
it 'contains details' do
expect(subject[:status]).to include :icon, :favicon, :text, :label, :tooltip
@@ -142,20 +142,20 @@ describe JobEntity do
end
it 'should indicate the failure reason on tooltip' do
- expect(subject[:status][:tooltip]).to eq('failed <br> (script failure)')
+ expect(subject[:status][:tooltip]).to eq('failed <br> (API failure)')
end
it 'should include a callout message with a verbose output' do
- expect(subject[:callout_message]).to eq('There has been a script failure. Check the job log for more information')
+ expect(subject[:callout_message]).to eq('There has been an API failure, please try again')
end
it 'should state that it is not recoverable' do
- expect(subject[:recoverable]).to be_falsy
+ expect(subject[:recoverable]).to be_truthy
end
end
context 'when job is allowed to fail' do
- let(:job) { create(:ci_build, :allowed_to_fail, :script_failure) }
+ let(:job) { create(:ci_build, :allowed_to_fail, :api_failure) }
it 'contains details' do
expect(subject[:status]).to include :icon, :favicon, :text, :label, :tooltip
@@ -166,15 +166,24 @@ describe JobEntity do
end
it 'should indicate the failure reason on tooltip' do
- expect(subject[:status][:tooltip]).to eq('failed <br> (script failure) (allowed to fail)')
+ expect(subject[:status][:tooltip]).to eq('failed <br> (API failure) (allowed to fail)')
end
it 'should include a callout message with a verbose output' do
- expect(subject[:callout_message]).to eq('There has been a script failure. Check the job log for more information')
+ expect(subject[:callout_message]).to eq('There has been an API failure, please try again')
end
it 'should state that it is not recoverable' do
- expect(subject[:recoverable]).to be_falsy
+ expect(subject[:recoverable]).to be_truthy
+ end
+ end
+
+ context 'when the job failed with a script failure' do
+ let(:job) { create(:ci_build, :failed, :script_failure) }
+
+ it 'should not include callout message or recoverable keys' do
+ expect(subject).not_to include('callout_message')
+ expect(subject).not_to include('recoverable')
end
end
diff --git a/spec/support/features/rss_shared_examples.rb b/spec/support/features/rss_shared_examples.rb
index 50fbbc7f55b..0de92aedba5 100644
--- a/spec/support/features/rss_shared_examples.rb
+++ b/spec/support/features/rss_shared_examples.rb
@@ -1,23 +1,23 @@
-shared_examples "an autodiscoverable RSS feed with current_user's RSS token" do
- it "has an RSS autodiscovery link tag with current_user's RSS token" do
- expect(page).to have_css("link[type*='atom+xml'][href*='rss_token=#{user.rss_token}']", visible: false)
+shared_examples "an autodiscoverable RSS feed with current_user's feed token" do
+ it "has an RSS autodiscovery link tag with current_user's feed token" do
+ expect(page).to have_css("link[type*='atom+xml'][href*='feed_token=#{user.feed_token}']", visible: false)
end
end
-shared_examples "it has an RSS button with current_user's RSS token" do
- it "shows the RSS button with current_user's RSS token" do
- expect(page).to have_css("a:has(.fa-rss)[href*='rss_token=#{user.rss_token}']")
+shared_examples "it has an RSS button with current_user's feed token" do
+ it "shows the RSS button with current_user's feed token" do
+ expect(page).to have_css("a:has(.fa-rss)[href*='feed_token=#{user.feed_token}']")
end
end
-shared_examples "an autodiscoverable RSS feed without an RSS token" do
- it "has an RSS autodiscovery link tag without an RSS token" do
- expect(page).to have_css("link[type*='atom+xml']:not([href*='rss_token'])", visible: false)
+shared_examples "an autodiscoverable RSS feed without a feed token" do
+ it "has an RSS autodiscovery link tag without a feed token" do
+ expect(page).to have_css("link[type*='atom+xml']:not([href*='feed_token'])", visible: false)
end
end
-shared_examples "it has an RSS button without an RSS token" do
- it "shows the RSS button without an RSS token" do
- expect(page).to have_css("a:has(.fa-rss):not([href*='rss_token'])")
+shared_examples "it has an RSS button without a feed token" do
+ it "shows the RSS button without a feed token" do
+ expect(page).to have_css("a:has(.fa-rss):not([href*='feed_token'])")
end
end
diff --git a/spec/support/gitlab-git-test.git/packed-refs b/spec/support/gitlab-git-test.git/packed-refs
index ea50e4ad3f6..6a61e5df267 100644
--- a/spec/support/gitlab-git-test.git/packed-refs
+++ b/spec/support/gitlab-git-test.git/packed-refs
@@ -9,6 +9,7 @@
4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6 refs/heads/master
5937ac0a7beb003549fc5fd26fc247adbce4a52e refs/heads/merge-test
9596bc54a6f0c0c98248fe97077eb5ccf48a98d0 refs/heads/missing-gitmodules
+4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6 refs/heads/Ääh-test-utf-8
f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8 refs/tags/v1.0.0
^6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b refs/tags/v1.1.0
diff --git a/spec/support/helpers/seed_repo.rb b/spec/support/helpers/seed_repo.rb
index b4868e82cd7..71f1a86b0c1 100644
--- a/spec/support/helpers/seed_repo.rb
+++ b/spec/support/helpers/seed_repo.rb
@@ -98,6 +98,7 @@ module SeedRepo
master
merge-test
missing-gitmodules
+ Ääh-test-utf-8
].freeze
TAGS = %w[
v1.0.0
diff --git a/spec/support/import_export/configuration_helper.rb b/spec/support/import_export/configuration_helper.rb
index f752508d48c..bbac6ca6a9c 100644
--- a/spec/support/import_export/configuration_helper.rb
+++ b/spec/support/import_export/configuration_helper.rb
@@ -10,7 +10,7 @@ module ConfigurationHelper
def relation_class_for_name(relation_name)
relation_name = Gitlab::ImportExport::RelationFactory::OVERRIDES[relation_name.to_sym] || relation_name
- relation_name.to_s.classify.constantize
+ Gitlab::ImportExport::RelationFactory.relation_class(relation_name)
end
def parsed_attributes(relation_name, attributes)
diff --git a/spec/support/shared_examples/requests/api/merge_requests_list.rb b/spec/support/shared_examples/requests/api/merge_requests_list.rb
new file mode 100644
index 00000000000..d5e22b8cb56
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/merge_requests_list.rb
@@ -0,0 +1,280 @@
+shared_examples 'merge requests list' do
+ context 'when unauthenticated' do
+ it 'returns merge requests for public projects' do
+ get api(endpoint_path)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ end
+ end
+
+ context 'when authenticated' do
+ it 'avoids N+1 queries' do
+ control = ActiveRecord::QueryRecorder.new do
+ get api(endpoint_path, user)
+ end
+
+ create(:merge_request, state: 'closed', milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: 'Test', created_at: base_time)
+
+ create(:merge_request, milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: 'Test', created_at: base_time)
+
+ expect do
+ get api(endpoint_path, user)
+ end.not_to exceed_query_limit(control)
+ end
+
+ it 'returns an array of all merge_requests' do
+ get api(endpoint_path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.last['title']).to eq(merge_request.title)
+ expect(json_response.last).to have_key('web_url')
+ expect(json_response.last['sha']).to eq(merge_request.diff_head_sha)
+ expect(json_response.last['merge_commit_sha']).to be_nil
+ expect(json_response.last['merge_commit_sha']).to eq(merge_request.merge_commit_sha)
+ expect(json_response.last['downvotes']).to eq(1)
+ expect(json_response.last['upvotes']).to eq(1)
+ expect(json_response.last['labels']).to eq([label2.title, label.title])
+ expect(json_response.first['title']).to eq(merge_request_merged.title)
+ expect(json_response.first['sha']).to eq(merge_request_merged.diff_head_sha)
+ expect(json_response.first['merge_commit_sha']).not_to be_nil
+ expect(json_response.first['merge_commit_sha']).to eq(merge_request_merged.merge_commit_sha)
+ end
+
+ it 'returns an array of all merge_requests using simple mode' do
+ path = endpoint_path + '?view=simple'
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response.last.keys).to match_array(%w(id iid title web_url created_at description project_id state updated_at))
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.last['iid']).to eq(merge_request.iid)
+ expect(json_response.last['title']).to eq(merge_request.title)
+ expect(json_response.last).to have_key('web_url')
+ expect(json_response.first['iid']).to eq(merge_request_merged.iid)
+ expect(json_response.first['title']).to eq(merge_request_merged.title)
+ expect(json_response.first).to have_key('web_url')
+ end
+
+ it 'returns an array of all merge_requests' do
+ path = endpoint_path + '?state'
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.last['title']).to eq(merge_request.title)
+ end
+
+ it 'returns an array of open merge_requests' do
+ path = endpoint_path + '?state=opened'
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.last['title']).to eq(merge_request.title)
+ end
+
+ it 'returns an array of closed merge_requests' do
+ path = endpoint_path + '?state=closed'
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['title']).to eq(merge_request_closed.title)
+ end
+
+ it 'returns an array of merged merge_requests' do
+ path = endpoint_path + '?state=merged'
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['title']).to eq(merge_request_merged.title)
+ end
+
+ it 'matches V4 response schema' do
+ get api(endpoint_path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/merge_requests')
+ end
+
+ it 'returns an empty array if no issue matches milestone' do
+ get api(endpoint_path, user), milestone: '1.0.0'
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
+ end
+
+ it 'returns an empty array if milestone does not exist' do
+ get api(endpoint_path, user), milestone: 'foo'
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
+ end
+
+ it 'returns an array of merge requests in given milestone' do
+ get api(endpoint_path, user), milestone: '0.9'
+
+ expect(json_response.first['title']).to eq merge_request_closed.title
+ expect(json_response.first['id']).to eq merge_request_closed.id
+ end
+
+ it 'returns an array of merge requests matching state in milestone' do
+ get api(endpoint_path, user), milestone: '0.9', state: 'closed'
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(merge_request_closed.id)
+ end
+
+ it 'returns an array of labeled merge requests' do
+ path = endpoint_path + "?labels=#{label.title}"
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['labels']).to eq([label2.title, label.title])
+ end
+
+ it 'returns an array of labeled merge requests where all labels match' do
+ path = endpoint_path + "?labels=#{label.title},foo,bar"
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
+ end
+
+ it 'returns an empty array if no merge request matches labels' do
+ path = endpoint_path + '?labels=foo,bar'
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
+ end
+
+ it 'returns an array of labeled merge requests that are merged for a milestone' do
+ bug_label = create(:label, title: 'bug', color: '#FFAABB', project: project)
+
+ mr1 = create(:merge_request, state: 'merged', source_project: project, target_project: project, milestone: milestone)
+ mr2 = create(:merge_request, state: 'merged', source_project: project, target_project: project, milestone: milestone1)
+ mr3 = create(:merge_request, state: 'closed', source_project: project, target_project: project, milestone: milestone1)
+ _mr = create(:merge_request, state: 'merged', source_project: project, target_project: project, milestone: milestone1)
+
+ create(:label_link, label: bug_label, target: mr1)
+ create(:label_link, label: bug_label, target: mr2)
+ create(:label_link, label: bug_label, target: mr3)
+
+ path = endpoint_path + "?labels=#{bug_label.title}&milestone=#{milestone1.title}&state=merged"
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(mr2.id)
+ end
+
+ context 'with ordering' do
+ before do
+ @mr_later = mr_with_later_created_and_updated_at_time
+ @mr_earlier = mr_with_earlier_created_and_updated_at_time
+ end
+
+ it 'returns an array of merge_requests in ascending order' do
+ path = endpoint_path + '?sort=asc'
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ response_dates = json_response.map { |merge_request| merge_request['created_at'] }
+ expect(response_dates).to eq(response_dates.sort)
+ end
+
+ it 'returns an array of merge_requests in descending order' do
+ path = endpoint_path + '?sort=desc'
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ response_dates = json_response.map { |merge_request| merge_request['created_at'] }
+ expect(response_dates).to eq(response_dates.sort.reverse)
+ end
+
+ it 'returns an array of merge_requests ordered by updated_at' do
+ path = endpoint_path + '?order_by=updated_at'
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ response_dates = json_response.map { |merge_request| merge_request['updated_at'] }
+ expect(response_dates).to eq(response_dates.sort.reverse)
+ end
+
+ it 'returns an array of merge_requests ordered by created_at' do
+ path = endpoint_path + '?order_by=created_at&sort=asc'
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ response_dates = json_response.map { |merge_request| merge_request['created_at'] }
+ expect(response_dates).to eq(response_dates.sort)
+ end
+ end
+
+ context 'source_branch param' do
+ it 'returns merge requests with the given source branch' do
+ get api(endpoint_path, user), source_branch: merge_request_closed.source_branch, state: 'all'
+
+ expect_response_contain_exactly(merge_request_closed, merge_request_merged)
+ end
+ end
+
+ context 'target_branch param' do
+ it 'returns merge requests with the given target branch' do
+ get api(endpoint_path, user), target_branch: merge_request_closed.target_branch, state: 'all'
+
+ expect_response_contain_exactly(merge_request_closed, merge_request_merged)
+ end
+ end
+ end
+end
diff --git a/spec/tasks/tokens_spec.rb b/spec/tasks/tokens_spec.rb
index 51f7a536cbb..555a58e9aa1 100644
--- a/spec/tasks/tokens_spec.rb
+++ b/spec/tasks/tokens_spec.rb
@@ -13,9 +13,9 @@ describe 'tokens rake tasks' do
end
end
- describe 'reset_all_rss task' do
+ describe 'reset_all_feed task' do
it 'invokes create_hooks task' do
- expect { run_rake_task('tokens:reset_all_rss') }.to change { user.reload.rss_token }
+ expect { run_rake_task('tokens:reset_all_feed') }.to change { user.reload.feed_token }
end
end
end