Commit 5b3345a0 authored by Dennis Tang's avatar Dennis Tang

Merge remote-tracking branch 'origin/master' into 43446-new-cluster-page-tabs

# Conflicts:
#	app/views/projects/_merge_request_merge_method_settings.html.haml
parents abad4ff0 f153faa8
......@@ -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'
......
......@@ -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)
......
......@@ -126,7 +126,6 @@ export default {
</div>
<form
v-if="!isCompact"
class="form-horizontal"
@submit.prevent.stop="commitChanges"
ref="formEl"
>
......
......@@ -59,7 +59,7 @@ export default {
ref="form"
:action="deleteWikiUrl"
method="post"
class="form-horizontal js-requires-input"
class="js-requires-input"
>
<input
ref="method"
......
......@@ -148,8 +148,14 @@ table {
}
}
.nav-tabs .nav-link {
.nav-tabs {
.nav-link {
border: 0;
}
.nav-item {
margin-bottom: 0;
}
}
pre code {
......
......@@ -169,13 +169,16 @@
color: $color-800;
}
.nav-links li a.active {
.nav-links li {
&.active a,
a.active {
border-bottom: 2px solid $color-500;
.badge.badge-pill {
font-weight: $gl-font-weight-bold;
}
}
}
.branch-header-title {
color: $color-700;
......
......@@ -31,8 +31,10 @@
color: $black;
}
}
}
&.active {
&.active a,
a.active {
color: $black;
font-weight: $gl-font-weight-bold;
......@@ -41,7 +43,6 @@
}
}
}
}
}
.top-area {
......
......@@ -49,26 +49,11 @@
margin-top: 15px;
}
.snippet-embed-input {
height: 35px;
}
.embed-snippet {
padding-right: 0;
padding-top: $gl-padding;
.form-control {
cursor: auto;
width: 101%;
margin-left: -1px;
}
.embed-toggle-list li button {
padding: 8px 40px;
}
.embed-toggle,
.snippet-clipboard-btn {
height: 35px;
}
}
......@@ -373,6 +373,7 @@ $dropdown-chevron-size: 10px;
$dropdown-toggle-active-border-color: darken($border-color, 14%);
$dropdown-item-hover-bg: $gray-darker;
$dropdown-fade-mask-height: 32px;
$dropdown-member-form-control-width: 163px;
/*
* Filtered Search
......
......@@ -36,13 +36,12 @@
}
}
.form-horizontal {
margin-top: 20px;
.form-group {
margin-bottom: 0;
@include media-breakpoint-up(sm) {
display: -webkit-flex;
display: flex;
margin-top: 3px;
@include media-breakpoint-down(sm) {
display: block;
margin-left: 5px;
}
}
......@@ -62,10 +61,15 @@
}
.member-form-control {
@include media-breakpoint-down(xs) {
padding-bottom: 5px;
@include media-breakpoint-down(sm) {
width: $dropdown-member-form-control-width;
margin-left: 0;
padding-bottom: 5px;
}
@include media-breakpoint-down(xs) {
margin-right: 0;
width: auto;
}
}
......@@ -207,10 +211,6 @@
align-self: flex-start;
}
.form-horizontal ~ .btn {
margin-right: 0;
}
@include media-breakpoint-down(xs) {
display: block;
......@@ -220,6 +220,12 @@
display: block;
}
.controls > .btn:last-child {
margin-left: 5px;
margin-right: 5px;
width: auto;
}
.form-control {
width: 100%;
}
......@@ -232,10 +238,6 @@
.member-controls {
margin-top: 5px;
}
.form-horizontal {
margin-top: 10px;
}
}
}
......@@ -259,10 +261,6 @@
margin-top: 0;
}
.form-horizontal {
display: block;
}
.member-form-control {
margin: 5px 0;
}
......
......@@ -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
......@@ -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
......
......@@ -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: ""
......
......@@ -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
......
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
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
......@@ -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?
......
......@@ -20,6 +20,7 @@ class Issue < ActiveRecord::Base
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
......
......@@ -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
......
......@@ -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)
......
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
......
......@@ -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
......
......@@ -5,8 +5,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :gravatar_enabled do
= f.check_box :gravatar_enabled
= f.check_box :gravatar_enabled, class: 'form-check-input'
= f.label :gravatar_enabled, class: 'form-check-label' do
Gravatar enabled
.form-group.row
= f.label :default_projects_limit, class: 'col-form-label col-sm-2'
......@@ -25,15 +25,15 @@
= f.label :user_oauth_applications, 'User OAuth applications', class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.label :user_oauth_applications do
= f.check_box :user_oauth_applications
= f.check_box :user_oauth_applications, class: 'form-check-input'
= f.label :user_oauth_applications, class: 'form-check-label' do
Allow users to register any application to use GitLab as an OAuth provider
.form-group.row
= f.label :user_default_external, 'New users set to external', class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.label :user_default_external do
= f.check_box :user_default_external
= f.check_box :user_default_external, class: 'form-check-input'
= f.label :user_default_external, class: 'form-check-label' do
Newly registered users will by default be external
= f.submit 'Save changes', class: 'btn btn-success'
......@@ -9,8 +9,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :sidekiq_throttling_enabled do
= f.check_box :sidekiq_throttling_enabled
= f.check_box :sidekiq_throttling_enabled, class: 'form-check-input'
= f.label :sidekiq_throttling_enabled, class: 'form-check-label' do
Enable Sidekiq Job Throttling
.form-text.text-muted
Limit the amount of resources slow running jobs are assigned.
......
......@@ -5,8 +5,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :auto_devops_enabled do
= f.check_box :auto_devops_enabled
= f.check_box :auto_devops_enabled, class: 'form-check-input'
= f.label :auto_devops_enabled, class: 'form-check-label' do
Enabled Auto DevOps for projects by default
.form-text.text-muted
It will automatically build, test, and deploy applications based on a predefined CI/CD configuration
......@@ -20,8 +20,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :shared_runners_enabled do
= f.check_box :shared_runners_enabled
= f.check_box :shared_runners_enabled, class: 'form-check-input'
= f.label :shared_runners_enabled, class: 'form-check-label' do
Enable shared runners for new projects
.form-group.row
= f.label :shared_runners_text, class: 'col-form-label col-sm-2'
......
......@@ -5,8 +5,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :email_author_in_body do
= f.check_box :email_author_in_body
= f.check_box :email_author_in_body, class: 'form-check-input'
= f.label :email_author_in_body, class: 'form-check-label' do
Include author name in notification email body
.form-text.text-muted
Some email servers do not support overriding the email sender name.
......@@ -15,8 +15,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :html_emails_enabled do
= f.check_box :html_emails_enabled
= f.check_box :html_emails_enabled, class: 'form-check-input'
= f.label :html_emails_enabled, class: 'form-check-label' do
Enable HTML emails
.form-text.text-muted
By default GitLab sends emails in HTML and plain text formats so mail
......
......@@ -10,8 +10,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :help_page_hide_commercial_content do
= f.check_box :help_page_hide_commercial_content
= f.check_box :help_page_hide_commercial_content, class: 'form-check-input'
= f.label :help_page_hide_commercial_content, class: 'form-check-label' do
Hide marketing-related entries from help
.form-group.row
= f.label :help_page_support_url, 'Support page URL', class: 'col-form-label col-sm-2'
......
......@@ -11,8 +11,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :metrics_enabled do
= f.check_box :metrics_enabled
= f.check_box :metrics_enabled, class: 'form-check-input'
= f.label :metrics_enabled, class: 'form-check-label' do
Enable InfluxDB Metrics
.form-group.row
= f.label :metrics_host, 'InfluxDB host', class: 'col-form-label col-sm-2'
......
......@@ -5,8 +5,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :throttle_unauthenticated_enabled do
= f.check_box :throttle_unauthenticated_enabled
= f.check_box :throttle_unauthenticated_enabled, class: 'form-check-input'
= f.label :throttle_unauthenticated_enabled, class: 'form-check-label' do
Enable unauthenticated request rate limit
%span.form-text.text-muted
Helps reduce request volume (e.g. from crawlers or abusive bots)
......@@ -21,8 +21,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :throttle_authenticated_api_enabled do
= f.check_box :throttle_authenticated_api_enabled
= f.check_box :throttle_authenticated_api_enabled, class: 'form-check-input'
= f.label :throttle_authenticated_api_enabled, class: 'form-check-label' do
Enable authenticated API request rate limit
%span.form-text.text-muted
Helps reduce request volume (e.g. from crawlers or abusive bots)
......@@ -37,8 +37,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :throttle_authenticated_web_enabled do
= f.check_box :throttle_authenticated_web_enabled
= f.check_box :throttle_authenticated_web_enabled, class: 'form-check-input'
= f.label :throttle_authenticated_web_enabled, class: 'form-check-label' do
Enable authenticated web request rate limit
%span.form-text.text-muted
Helps reduce request volume (e.g. from crawlers or abusive bots)
......
......@@ -5,8 +5,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :koding_enabled do
= f.check_box :koding_enabled
= f.check_box :koding_enabled, class: 'form-check-input'
= f.label :koding_enabled, class: 'form-check-label' do
Enable Koding
.form-text.text-muted
Koding integration has been deprecated since GitLab 10.0. If you disable your Koding integration, you will not be able to enable it again.
......
......@@ -5,8 +5,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :sentry_enabled do
= f.check_box :sentry_enabled
= f.check_box :sentry_enabled, class: 'form-check-input'
= f.label :sentry_enabled, class: 'form-check-label' do
Enable Sentry
.form-text.text-muted
%p This setting requires a restart to take effect.
......@@ -21,8 +21,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :clientside_sentry_enabled do
= f.check_box :clientside_sentry_enabled
= f.check_box :clientside_sentry_enabled, class: 'form-check-input'
= f.label :clientside_sentry_enabled, class: 'form-check-label' do
Enable Clientside Sentry
.form-text.text-muted
Sentry can also be used for reporting and logging clientside exceptions.
......
......@@ -5,8 +5,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :allow_local_requests_from_hooks_and_services do
= f.check_box :allow_local_requests_from_hooks_and_services
= f.check_box :allow_local_requests_from_hooks_and_services, class: 'form-check-input'
= f.label :allow_local_requests_from_hooks_and_services, class: 'form-check-label' do
Allow requests to the local network from hooks and services
= f.submit 'Save changes', class: "btn btn-success"
......@@ -10,8 +10,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :pages_domain_verification_enabled do
= f.check_box :pages_domain_verification_enabled
= f.check_box :pages_domain_verification_enabled, class: 'form-check-input'
= f.label :pages_domain_verification_enabled, class: 'form-check-label' do
Require users to prove ownership of custom domains
.form-text.text-muted
Domain verification is an essential security measure for public GitLab
......
......@@ -5,8 +5,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :authorized_keys_enabled do
= f.check_box :authorized_keys_enabled
= f.check_box :authorized_keys_enabled, class: 'form-check-input'
= f.label :authorized_keys_enabled, class: 'form-check-label' do
Write to "authorized_keys" file
.form-text.text-muted
By default, we write to the "authorized_keys" file to support Git
......
......@@ -5,8 +5,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :performance_bar_enabled do
= f.check_box :performance_bar_enabled
= f.check_box :performance_bar_enabled, class: 'form-check-input'
= f.label :performance_bar_enabled, class: 'form-check-label' do
Enable the Performance Bar
.form-group.row
= f.label :performance_bar_allowed_group_path, 'Allowed group', class: 'col-form-label col-sm-2'
......
......@@ -5,8 +5,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :plantuml_enabled do
= f.check_box :plantuml_enabled
= f.check_box :plantuml_enabled, class: 'form-check-input'
= f.label :plantuml_enabled, class: 'form-check-label' do
Enable PlantUML
.form-group.row
= f.label :plantuml_url, 'PlantUML URL', class: 'col-form-label col-sm-2'
......
......@@ -14,8 +14,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :prometheus_metrics_enabled do
= f.check_box :prometheus_metrics_enabled
= f.check_box :prometheus_metrics_enabled, class: 'form-check-input'
= f.label :prometheus_metrics_enabled, class: 'form-check-label' do
Enable Prometheus Metrics
- unless Gitlab::Metrics.metrics_folder_present?
.form-text.text-muted
......
......@@ -7,8 +7,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :repository_checks_enabled do
= f.check_box :repository_checks_enabled
= f.check_box :repository_checks_enabled, class: 'form-check-input'
= f.label :repository_checks_enabled, class: 'form-check-label' do
Enable Repository Checks
.form-text.text-muted
GitLab will periodically run
......@@ -25,8 +25,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :housekeeping_enabled do
= f.check_box :housekeeping_enabled
= f.check_box :housekeeping_enabled, class: 'form-check-input'
= f.label :housekeeping_enabled, class: 'form-check-label' do
Enable automatic repository housekeeping (git repack, git gc)
.form-text.text-muted
If you keep automatic housekeeping disabled for a long time Git
......@@ -34,8 +34,8 @@
repositories will use more disk space. We recommend to always leave
this enabled.
.form-check
= f.label :housekeeping_bitmaps_enabled do
= f.check_box :housekeeping_bitmaps_enabled
= f.check_box :housekeeping_bitmaps_enabled, class: 'form-check-input'
= f.label :housekeeping_bitmaps_enabled, class: 'form-check-label' do
Enable Git pack file bitmap creation
.form-text.text-muted
Creating pack file bitmaps makes housekeeping take a little longer but
......
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_for @application_setting, url: admin_application_settings_path do |f|
= form_errors(@application_setting)
%fieldset
.form-group
= f.label :mirror_available, 'Enable mirror configuration', class: 'control-label col-sm-2'
.col-sm-10
.form-group.row
= f.label :mirror_available, 'Enable mirror configuration', class: 'control-label col-sm-4'
.col-sm-8
.form-check
= f.label :mirror_available do
= f.check_box :mirror_available
= f.check_box :mirror_available, class: 'form-check-input'
= f.label :mirror_available, class: 'form-check-label' do
Allow mirrors to be setup for projects
%span.form-text.text-muted
If disabled, only admins will be able to setup mirrors in projects.
......
......@@ -6,8 +6,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :hashed_storage_enabled do
= f.check_box :hashed_storage_enabled
= f.check_box :hashed_storage_enabled, class: 'form-check-input'
= f.label :hashed_storage_enabled, class: 'form-check-label' do
Create new projects using hashed storage paths
.form-text.text-muted
Enable immutable, hash-based paths and repository names to store repositories on disk. This prevents
......
......@@ -5,16 +5,16 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :password_authentication_enabled_for_web do
= f.check_box :password_authentication_enabled_for_web
= f.check_box :password_authentication_enabled_for_web, class: 'form-check-input'
= f.label :password_authentication_enabled_for_web, class: 'form-check-label' do
Password authentication enabled for web interface
.form-text.text-muted
When disabled, an external authentication provider must be used.
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :password_authentication_enabled_for_git do
= f.check_box :password_authentication_enabled_for_git
= f.check_box :password_authentication_enabled_for_git, class: 'form-check-input'
= f.label :password_authentication_enabled_for_git, class: 'form-check-label' do
Password authentication enabled for Git over HTTP(S)
.form-text.text-muted
When disabled, a Personal Access Token
......@@ -33,8 +33,8 @@
= f.label :two_factor_authentication, 'Two-factor authentication', class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.label :require_two_factor_authentication do
= f.check_box :require_two_factor_authentication
= f.check_box :require_two_factor_authentication, class: 'form-check-input'
= f.label :require_two_factor_authentication, class: 'form-check-label' do
Require all users to setup Two-factor authentication
.form-group.row
= f.label :two_factor_authentication, 'Two-factor grace period (hours)', class: 'col-form-label col-sm-2'
......
......@@ -5,14 +5,14 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :signup_enabled do
= f.check_box :signup_enabled
= f.check_box :signup_enabled, class: 'form-check-input'
= f.label :signup_enabled, class: 'form-check-label' do
Sign-up enabled
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :send_user_confirmation_email do
= f.check_box :send_user_confirmation_email
= f.check_box :send_user_confirmation_email, class: 'form-check-input'
= f.label :send_user_confirmation_email, class: 'form-check-label' do
Send confirmation email on sign-up
.form-group.row
= f.label :domain_whitelist, 'Whitelisted domains for sign-ups', class: 'col-form-label col-sm-2'
......@@ -23,19 +23,19 @@
= f.label :domain_blacklist_enabled, 'Domain Blacklist', class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.label :domain_blacklist_enabled do
= f.check_box :domain_blacklist_enabled
= f.check_box :domain_blacklist_enabled, class: 'form-check-input'
= f.label :domain_blacklist_enabled, class: 'form-check-label' do
Enable domain blacklist for sign ups
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= label_tag :blacklist_type_file do
= radio_button_tag :blacklist_type, :file
= radio_button_tag :blacklist_type, :file, class: 'form-check-input'
= label_tag :blacklist_type_file, class: 'form-check-label' do
.option-title
Upload blacklist file
.form-check
= label_tag :blacklist_type_raw do
= radio_button_tag :blacklist_type, :raw, @application_setting.domain_blacklist.present? || @application_setting.domain_blacklist.blank?
= radio_button_tag :blacklist_type, :raw, @application_setting.domain_blacklist.present? || @application_setting.domain_blacklist.blank?, class: 'form-check-input'
= label_tag :blacklist_type_raw, class: 'form-check-label' do
.option-title
Enter blacklist manually
.form-group.row.blacklist-file
......
......@@ -5,8 +5,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :recaptcha_enabled do
= f.check_box :recaptcha_enabled
= f.check_box :recaptcha_enabled, class: 'form-check-input'
= f.label :recaptcha_enabled, class: 'form-check-label' do
Enable reCAPTCHA
%span.form-text.text-muted#recaptcha_help_block Helps prevent bots from creating accounts
......@@ -26,8 +26,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :akismet_enabled do
= f.check_box :akismet_enabled
= f.check_box :akismet_enabled, class: 'form-check-input'
= f.label :akismet_enabled, class: 'form-check-label' do
Enable Akismet
%span.form-text.text-muted#akismet_help_block Helps prevent bots from creating issues
......@@ -42,8 +42,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :unique_ips_limit_enabled do
= f.check_box :unique_ips_limit_enabled
= f.check_box :unique_ips_limit_enabled, class: 'form-check-input'
= f.label :unique_ips_limit_enabled, class: 'form-check-label' do
Limit sign in from multiple ips
%span.form-text.text-muted#unique_ip_help_block
Helps prevent malicious users hide their activity
......
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_for @application_setting, url: admin_application_settings_path do |f|
= form_errors(@application_setting)
%fieldset
.form-group
.form-group.row
.col-sm-12
.form-check
= f.label :enforce_terms do
= f.check_box :enforce_terms
= f.check_box :enforce_terms, class: 'form-check-input'
= f.label :enforce_terms, class: 'form-check-label' do
= _("Require all users to accept Terms of Service when they access GitLab.")
.form-text.text-muted
= _("When enabled, users cannot use GitLab until the terms have been accepted.")
.form-group
.form-group.row
.col-sm-12
= f.label :terms do
= _("Terms of Service Agreement")
......
......@@ -5,8 +5,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :version_check_enabled do
= f.check_box :version_check_enabled
= f.check_box :version_check_enabled, class: 'form-check-input'
= f.label :version_check_enabled, class: 'form-check-label' do
Enable version check
.form-text.text-muted
GitLab will inform you if a new version is available.
......@@ -16,8 +16,8 @@
.offset-sm-2.col-sm-10
- can_be_configured = @application_setting.usage_ping_can_be_configured?
.form-check
= f.label :usage_ping_enabled do
= f.check_box :usage_ping_enabled, disabled: !can_be_configured
= f.check_box :usage_ping_enabled, disabled: !can_be_configured, class: 'form-check-input'
= f.label :usage_ping_enabled, class: 'form-check-label' do
Enable usage ping
.form-text.text-muted
- if can_be_configured
......
......@@ -46,8 +46,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= f.label :project_export_enabled do
= f.check_box :project_export_enabled
= f.check_box :project_export_enabled, class: 'form-check-input'
= f.label :project_export_enabled, class: 'form-check-label' do
Project export enabled
.form-group.row
......
......@@ -47,6 +47,6 @@
.form-group
= form.label :enable_ssl_verification, 'SSL verification', class: 'label-light checkbox'
.form-check
= form.label :enable_ssl_verification do
= form.check_box :enable_ssl_verification
= form.check_box :enable_ssl_verification, class: 'form-check-input'
= form.label :enable_ssl_verification, class: 'form-check-label' do
%strong Enable SSL verification
......@@ -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
......
= render 'issues/issues_calendar', issues: @issues
......@@ -5,8 +5,8 @@
Mattermost
.col-sm-10
.form-check.js-toggle-container
= f.label :create_chat_team do
.js-toggle-button= f.check_box(:create_chat_team, { checked: true }, true, false)
.js-toggle-button.form-check-input= f.check_box(:create_chat_team, { checked: true }, true, false)
= f.label :create_chat_team, class: 'form-check-label' do
Create a Mattermost team for this group
%br
%small.light.js-toggle-content
......
......@@ -2,8 +2,8 @@
= f.label :lfs_enabled, 'Large File Storage', class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.label :lfs_enabled do
= f.check_box :lfs_enabled, checked: @group.lfs_enabled?
= f.check_box :lfs_enabled, checked: @group.lfs_enabled?, class: 'form-check-input'
= f.label :lfs_enabled, class: 'form-check-label' do
%strong
Allow projects within this group to use Git LFS
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
......@@ -14,8 +14,8 @@
= f.label :require_two_factor_authentication, 'Two-factor authentication', class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.label :require_two_factor_authentication do
= f.check_box :require_two_factor_authentication
= f.check_box :require_two_factor_authentication, class: 'form-check-input'
= f.label :require_two_factor_authentication, class: 'form-check-label' do
%strong
Require all users in this group to setup Two-factor authentication
= link_to icon('question-circle'), help_page_path('security/two_factor_authentication', anchor: 'enforcing-2fa-for-all-users-in-a-group')
......
......@@ -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
......
= render 'issues/issues_calendar', issues: @issues
......@@ -13,8 +13,8 @@
= s_('GroupSettings|Share with group lock')
.col-sm-10
.form-check
= f.label :share_with_group_lock do
= f.check_box :share_with_group_lock, disabled: !can_change_share_with_group_lock?(@group)
= f.check_box :share_with_group_lock, disabled: !can_change_share_with_group_lock?(@group), class: 'form-check-input'
= f.label :share_with_group_lock, class: 'form-check-label' do
%strong
- group_link = link_to @group.name, group_path(@group)
= s_('GroupSettings|Prevent sharing a project within %{group} with other groups').html_safe % { group: group_link }
......
......@@ -472,8 +472,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
%label
%input{ :type => "checkbox" }/
%input.form-check-input{ :type => "checkbox" }/
%label.form-check-label
Remember me
.form-group.row
.offset-sm-2.col-sm-10
......@@ -492,8 +492,8 @@
%label{ :for => "exampleInputPassword1" } Password
%input#exampleInputPassword1.form-control{ :placeholder => "Password", :type => "password" }/
.form-check
%label
%input{ :type => "checkbox" }/
%input.form-check-input{ :type => "checkbox" }/
%label.form-check-label
Remember me
%button.btn.btn-default{ :type => "submit" } Sign in
......
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
......@@ -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?
......
......@@ -5,16 +5,16 @@
= label_tag :merge_method_merge, class: 'label-light' do
Merge method
.form-check
= label_tag :project_merge_method_merge do
= form.radio_button :merge_method, :merge, class: "js-merge-method-radio"
= form.radio_button :merge_method, :merge, class: "js-merge-method-radio form-check-input"
= label_tag :project_merge_method_merge, class: 'form-check-label' do
%strong Merge commit
%br
%span.descr
A merge commit is created for every merge, and merging is allowed as long as there are no conflicts.
.form-check
= label_tag :project_merge_method_rebase_merge do
= form.radio_button :merge_method, :rebase_merge, class: "js-merge-method-radio"
= form.radio_button :merge_method, :rebase_merge, class: "js-merge-method-radio form-check-input"
= label_tag :project_merge_method_rebase_merge, class: 'form-check-label' do
%strong Merge commit with semi-linear history
%br
%span.descr
......@@ -25,8 +25,8 @@
When fast-forward merge is not possible, the user is given the option to rebase.
.form-check
= label_tag :project_merge_method_ff do
= form.radio_button :merge_method, :ff, class: "js-merge-method-radio qa-radio-button-merge-ff"
= form.radio_button :merge_method, :ff, class: "js-merge-method-radio qa-radio-button-merge-ff form-check-input"
= label_tag :project_merge_method_ff, class: 'form-check-label' do
%strong Fast-forward merge
%br
%span.descr
......
......@@ -2,22 +2,22 @@
.form-group
.form-check.builds-feature{ class: ("hidden" if @project && @project.project_feature.send(:builds_access_level) == 0) }
= form.label :only_allow_merge_if_pipeline_succeeds do
= form.check_box :only_allow_merge_if_pipeline_succeeds
= form.check_box :only_allow_merge_if_pipeline_succeeds, class: 'form-check-input'
= form.label :only_allow_merge_if_pipeline_succeeds, class: 'form-check-label' do
%strong Only allow merge requests to be merged if the pipeline succeeds
%br
%span.descr
Pipelines need to be configured to enable this feature.
= link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_pipeline_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds'), target: '_blank'
.form-check
= form.label :only_allow_merge_if_all_discussions_are_resolved do
= form.check_box :only_allow_merge_if_all_discussions_are_resolved
= form.check_box :only_allow_merge_if_all_discussions_are_resolved, class: 'form-check-input'
= form.label :only_allow_merge_if_all_discussions_are_resolved, class: 'form-check-label' do
%strong Only allow merge requests to be merged if all discussions are resolved
.form-check
= form.label :resolve_outdated_diff_discussions do
= form.check_box :resolve_outdated_diff_discussions
= form.check_box :resolve_outdated_diff_discussions, class: 'form-check-input'
= form.label :resolve_outdated_diff_discussions, class: 'form-check-label' do
%strong Automatically resolve merge request diff discussions when they become outdated
.form-check
= form.label :printing_merge_request_link_enabled do
= form.check_box :printing_merge_request_link_enabled
= form.check_box :printing_merge_request_link_enabled, class: 'form-check-input'
= form.label :printing_merge_request_link_enabled, class: 'form-check-label' do
%strong Show link to create/view merge request when pushing from the command line
= 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)
......
= render 'issues/issues_calendar', issues: @issues
......@@ -10,8 +10,8 @@
= icon('search')
.inline.prepend-left-20
.form-check.light
= label_tag :filter_ref do
= check_box_tag :filter_ref, 1, @options[:filter_ref]
= check_box_tag :filter_ref, 1, @options[:filter_ref], class: 'form-check-input'
= label_tag :filter_ref, class: 'form-check-label' do
%span= _("Begin with the selected commit")
- if @commit
......
......@@ -11,22 +11,22 @@
= message.html_safe
= f.fields_for :auto_devops_attributes, @auto_devops do |form|
.form-check
= form.label :enabled_true do
= form.radio_button :enabled, 'true'
= form.radio_button :enabled, 'true', class: 'form-check-input'
= form.label :enabled_true, class: 'form-check-label' do
%strong= s_('CICD|Enable Auto DevOps')
%br
= s_('CICD|The Auto DevOps pipeline configuration will be used when there is no %{ci_file} in the project.').html_safe % { ci_file: ci_file_formatted }
.form-check
= form.label :enabled_false do
= form.radio_button :enabled, 'false'
= form.radio_button :enabled, 'false', class: 'form-check-input'
= form.label :enabled_false, class: 'form-check-label' do
%strong= s_('CICD|Disable Auto DevOps')
%br
= s_('CICD|An explicit %{ci_file} needs to be specified before you can begin using Continuous Integration and Delivery.').html_safe % { ci_file: ci_file_formatted }
.form-check
= form.label :enabled_ do
= form.radio_button :enabled, ''
= form.radio_button :enabled, '', class: 'form-check-input'
= form.label :enabled_, class: 'form-check-label' do
%strong= s_('CICD|Instance default (%{state})') % { state: "#{Gitlab::CurrentSettings.auto_devops_enabled? ? _('enabled') : _('disabled')}" }
%br
= s_('CICD|Follow the instance default to either have Auto DevOps enabled or disabled when there is no project specific %{ci_file}.').html_safe % { ci_file: ci_file_formatted }
......
......@@ -20,15 +20,15 @@
Choose between <code>clone</code> or <code>fetch</code> to get the recent application code
= link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'git-strategy'), target: '_blank'
.form-check
= f.label :build_allow_git_fetch_false do
= f.radio_button :build_allow_git_fetch, 'false'
= f.radio_button :build_allow_git_fetch, 'false', { class: 'form-check-input' }
= f.label :build_allow_git_fetch_false, class: 'form-check-label' do
%strong git clone
%br
%span.descr
Slower but makes sure the project workspace is pristine as it clones the repository from scratch for every job
.form-check
= f.label :build_allow_git_fetch_true do
= f.radio_button :build_allow_git_fetch, 'true'
= f.radio_button :build_allow_git_fetch, 'true', { class: 'form-check-input' }
= f.label :build_allow_git_fetch_true, class: 'form-check-label' do
%strong git fetch
%br
%span.descr
......@@ -53,8 +53,8 @@
%hr
.form-group
.form-check
= f.label :public_builds do
= f.check_box :public_builds
= f.check_box :public_builds, { class: 'form-check-input' }
= f.label :public_builds, class: 'form-check-label' do
%strong Public pipelines
.form-text.text-muted
Allow public access to pipelines and job details, including output logs and artifacts
......@@ -75,8 +75,8 @@
%hr
.form-group
.form-check
= f.label :auto_cancel_pending_pipelines do
= f.check_box :auto_cancel_pending_pipelines, {}, 'enabled', 'disabled'
= f.check_box :auto_cancel_pending_pipelines, { class: 'form-check-input' }, 'enabled', 'disabled'
= f.label :auto_cancel_pending_pipelines, class: 'form-check-label' do
%strong Auto-cancel redundant, pending pipelines
.form-text.text-muted
New pipelines will cancel older, pending pipelines on the same branch
......
.form-check
= form.label :request_access_enabled do
= form.check_box :request_access_enabled
= form.check_box :request_access_enabled, class: 'form-check-input'
= form.label :request_access_enabled, class: 'form-check-label' do
%strong Allow users to request access
%br
%span.descr Allow users to request access if visibility is public or internal.
.form-check
- nonce = SecureRandom.hex
= label_tag "create_merge_request-#{nonce}" do
= check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
= check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request form-check-input', id: "create_merge_request-#{nonce}"
= label_tag "create_merge_request-#{nonce}", class: 'form-check-label' do
- translation_variables = { new_merge_request: "<strong>#{_('new merge request')}</strong>" }
- translation = _('Start a %{new_merge_request} with these changes') % translation_variables
#{ translation.html_safe }
......
......@@ -2,9 +2,9 @@
- disallowed = disallowed_visibility_level?(form_model, level)
- restricted = restricted_visibility_levels.include?(level)
- disabled = disallowed || restricted
.form-check.pl-0{ class: [('disabled' if disabled), ('restricted' if restricted)] }
= form.label "#{model_method}_#{level}" do
= form.radio_button model_method, level, checked: (selected_level == level), disabled: disabled
.form-check{ class: [('disabled' if disabled), ('restricted' if restricted)] }
= form.radio_button model_method, level, checked: (selected_level == level), disabled: disabled, class: 'form-check-input'
= form.label "#{model_method}_#{level}", class: 'form-check-label' do
= visibility_level_icon(level)
.option-title
= visibility_level_label(level)
......
<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
= 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')
......@@ -23,8 +23,8 @@
.form-group.row
.offset-sm-2.col-sm-10
.form-check
= form.label :confidential do
= form.check_box :confidential
= form.check_box :confidential, class: 'form-check-input'
= form.label :confidential, class: 'form-check-label' do
This issue is confidential and should only be visible to team members with at least Reporter access.
= render 'shared/issuable/form/metadata', issuable: issuable, form: form
......
......@@ -12,8 +12,8 @@
= _('Contribution')
.col-sm-10
.form-check
= form.label :allow_maintainer_to_push do
= form.check_box :allow_maintainer_to_push, disabled: !issuable.can_allow_maintainer_to_push?(current_user)
= form.check_box :allow_maintainer_to_push, disabled: !issuable.can_allow_maintainer_to_push?(current_user), class: 'form-check-input'
= form.label :allow_maintainer_to_push, class: 'form-check-label' do
= _('Allow edits from maintainers.')
= link_to 'About this feature', help_page_path('user/project/merge_requests/maintainer_access')
.form-text.text-muted
......
......@@ -11,16 +11,16 @@
.col-sm-10.offset-sm-2
- if issuable.can_remove_source_branch?(current_user)
.form-check
= label_tag 'merge_request[force_remove_source_branch]' do
= hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil
= check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch?
= check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch?, class: 'form-check-input'
= label_tag 'merge_request[force_remove_source_branch]', class: 'form-check-label' do
Remove source branch when merge request is accepted.
.form-group
.col-sm-10.col-sm-offset-2
.checkbox
= label_tag 'merge_request[squash]' do
.form-check
= hidden_field_tag 'merge_request[squash]', '0', id: nil
= check_box_tag 'merge_request[squash]', '1', issuable.squash
= check_box_tag 'merge_request[squash]', '1', issuable.squash, class: 'form-check-input'
= label_tag 'merge_request[squash]', class: 'form-check-label' do
Squash commits when merge request is accepted.
= link_to 'About this feature', help_page_path('user/project/merge_requests/squash_and_merge')
......@@ -61,7 +61,7 @@
title: 'Resend invite'
- if user != current_user && member.can_update?
= form_for member, remote: true, html: { class: 'js-edit-member-form form-horizontal' } do |f|
= form_for member, remote: true, html: { class: 'js-edit-member-form form-group row append-right-5' } do |f|
= f.hidden_field :access_level
.member-form-control.dropdown.append-right-5
%button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button",
......
......@@ -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
......
......@@ -23,8 +23,8 @@
- field_id = "#{notifications_menu_identifier("modal", notification_setting)}_notification_setting[#{event}]"
.form-group
.form-check{ class: ("prepend-top-0" if index == 0) }
%label{ for: field_id }
= check_box("notification_setting", event, id: field_id, class: "js-custom-notification-event", checked: notification_setting.public_send(event))
= check_box("notification_setting", event, id: field_id, class: "js-custom-notification-event form-check-input", checked: notification_setting.public_send(event))
%label.form-check-label{ for: field_id }
%strong
= notification_event_name(event)
= icon("spinner spin", class: "custom-notification-event-loading")
......@@ -4,26 +4,26 @@
= label :active, "Active", class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.check_box :active
= f.check_box :active, { class: 'form-check-input' }
%span.light Paused Runners don't accept new jobs
.form-group.row
= label :protected, "Protected", class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.check_box :access_level, {}, 'ref_protected', 'not_protected'
= f.check_box :access_level, { class: 'form-check-input' }, 'ref_protected', 'not_protected'
%span.light This runner will only run on pipelines triggered on protected branches
.form-group.row
= label :run_untagged, 'Run untagged jobs', class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.check_box :run_untagged
= f.check_box :run_untagged, { class: 'form-check-input' }
%span.light Indicates whether this runner can pick jobs without tags
- unless runner.group_type?
.form-group.row
= label :locked, _('Lock to current projects'), class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.check_box :locked
= f.check_box :locked, { class: 'form-check-input' }
%span.light= _('When a runner is locked, it cannot be assigned to other projects')
.form-group.row
= label_tag :token, class: 'col-form-label col-sm-2' do
......
......@@ -32,10 +32,10 @@
- if public_snippet?
.embed-snippet
.input-group
.input-group-btn
%button.btn.embed-toggle{ 'data-toggle': 'dropdown', type: 'button' }
.input-group-prepend
%button.btn.btn-svg.embed-toggle.input-group-text{ 'data-toggle': 'dropdown', type: 'button' }
%span.js-embed-action= _("Embed")
= sprite_icon('angle-down', size: 12)
= sprite_icon('angle-down', size: 12, css_class: 'caret-down')
%ul.dropdown-menu.dropdown-menu-selectable.embed-toggle-list
%li
%button.js-embed-btn.btn.btn-transparent.is-active{ type: 'button' }
......@@ -44,7 +44,6 @@
%button.js-share-btn.btn.btn-transparent{ type: 'button' }
%strong.embed-toggle-list-item= _("Share")
%input.js-snippet-url-area.snippet-embed-input.form-control{ type: "text", autocomplete: 'off', value: snippet_embed }
.input-group-btn
%button.js-clipboard-btn.snippet-clipboard-btn.btn.btn-default.has-tooltip{ title: "Copy to clipboard", 'data-clipboard-target': '.js-snippet-url-area' }
= sprite_icon('duplicate', size: 16)
.input-group-append
= clipboard_button(title: s_('Copy to clipboard'), class: 'js-clipboard-btn snippet-clipboard-btn btn btn-default', target: '.js-snippet-url-area')
.clearfix
......@@ -10,80 +10,70 @@
Use this token to validate received payloads. It will be sent with the request in the X-Gitlab-Token HTTP header.
.form-group
= form.label :url, 'Trigger', class: 'label-light'
%ul.list-unstyled
%ul.list-unstyled.prepend-left-20
%li
= form.check_box :push_events, class: 'float-left'
.prepend-left-20
= form.label :push_events, class: 'list-label' do
= form.check_box :push_events, class: 'form-check-input'
= form.label :push_events, class: 'list-label form-check-label ml-1' do
%strong Push events
%p.light
%p.light.ml-1
This URL will be triggered by a push to the repository
%li
= form.check_box :tag_push_events, class: 'float-left'
.prepend-left-20
= form.label :tag_push_events, class: 'list-label' do
= form.check_box :tag_push_events, class: 'form-check-input'
= form.label :tag_push_events, class: 'list-label form-check-label ml-1' do
%strong Tag push events
%p.light
%p.light.ml-1
This URL will be triggered when a new tag is pushed to the repository
%li
= form.check_box :note_events, class: 'float-left'
.prepend-left-20
= form.label :note_events, class: 'list-label' do
= form.check_box :note_events, class: 'form-check-input'
= form.label :note_events, class: 'list-label form-check-label ml-1' do
%strong Comments
%p.light
%p.light.ml-1
This URL will be triggered when someone adds a comment
%li
= form.check_box :confidential_note_events, class: 'float-left'
.prepend-left-20
= form.label :confidential_note_events, class: 'list-label' do
= form.check_box :confidential_note_events, class: 'form-check-input'
= form.label :confidential_note_events, class: 'list-label form-check-label ml-1' do
%strong Confidential Comments
%p.light
%p.light.ml-1
This URL will be triggered when someone adds a comment on a confidential issue
%li
= form.check_box :issues_events, class: 'float-left'
.prepend-left-20
= form.label :issues_events, class: 'list-label' do
= form.check_box :issues_events, class: 'form-check-input'
= form.label :issues_events, class: 'list-label form-check-label ml-1' do
%strong Issues events
%p.light
%p.light.ml-1
This URL will be triggered when an issue is created/updated/merged
%li
= form.check_box :confidential_issues_events, class: 'float-left'
.prepend-left-20
= form.label :confidential_issues_events, class: 'list-label' do
= form.check_box :confidential_issues_events, class: 'form-check-input'
= form.label :confidential_issues_events, class: 'list-label form-check-label ml-1' do
%strong Confidential Issues events
%p.light
%p.light.ml-1
This URL will be triggered when a confidential issue is created/updated/merged
%li
= form.check_box :merge_requests_events, class: 'float-left'
.prepend-left-20
= form.label :merge_requests_events, class: 'list-label' do
= form.check_box :merge_requests_events, class: 'form-check-input'
= form.label :merge_requests_events, class: 'list-label form-check-label ml-1' do
%strong Merge request events
%p.light
%p.light.ml-1
This URL will be triggered when a merge request is created/updated/merged
%li
= form.check_box :job_events, class: 'float-left'
.prepend-left-20
= form.label :job_events, class: 'list-label' do
= form.check_box :job_events, class: 'form-check-input'
= form.label :job_events, class: 'list-label form-check-label ml-1' do
%strong Job events
%p.light
%p.light.ml-1
This URL will be triggered when the job status changes
%li
= form.check_box :pipeline_events, class: 'float-left'
.prepend-left-20
= form.label :pipeline_events, class: 'list-label' do
= form.check_box :pipeline_events, class: 'form-check-input'
= form.label :pipeline_events, class: 'list-label form-check-label ml-1' do
%strong Pipeline events
%p.light
%p.light.ml-1
This URL will be triggered when the pipeline status changes
%li
= form.check_box :wiki_page_events, class: 'float-left'
.prepend-left-20
= form.label :wiki_page_events, class: 'list-label' do
= form.check_box :wiki_page_events, class: 'form-check-input'
= form.label :wiki_page_events, class: 'list-label form-check-label ml-1' do
%strong Wiki Page events
%p.light
%p.light.ml-1
This URL will be triggered when a wiki page is created/updated
.form-group
= form.label :enable_ssl_verification, 'SSL verification', class: 'label-light checkbox'
.form-check
= form.label :enable_ssl_verification do
= form.check_box :enable_ssl_verification
= form.check_box :enable_ssl_verification, class: 'form-check-input'
= form.label :enable_ssl_verification, class: 'form-check-label ml-1' do
%strong Enable SSL verification
---
title: Take two for MR metrics population background migration
merge_request: 19097
author:
type: other
---
title: Export assigned issues in iCalendar feed
merge_request: 17783
author: Imre Farkas
type: added
---
title: Removes redundant script failure message from Job page
merge_request: 19138
author:
type: changed
---
title: Add merge requests list endpoint for groups
merge_request:
author:
type: other
---
title: Make ActiveRecordSubscriber rails 5 compatible
merge_request:
author:
type: other
---
title: Eliminate cached N+1 queries for projects in Issue API
merge_request:
author:
type: performance
resource :dashboard, controller: 'dashboard', only: [] do
get :issues, action: :issues_calendar, constraints: lambda { |req| req.format == :ics }
get :issues
get :merge_requests
get :activity
......
......@@ -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
......
......@@ -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
......
......@@ -342,6 +342,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
......
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
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
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
......@@ -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"}
......
......@@ -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.
......
# Tips
> TODO: Add tips
## Clearing production compiled assets
To clear production compiled assets created with `yarn webpack-prod` you can run:
```
yarn clean
```
......@@ -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
......
......@@ -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
......@@ -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
......
......@@ -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')
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
......
......@@ -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
......
......@@ -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
......@@ -28,6 +28,7 @@ project_tree:
- project_members:
- :user
- merge_requests:
- :metrics
- notes:
- :author
- events:
......
......@@ -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
......
......@@ -4,7 +4,7 @@ module Gitlab
attach_to :active_record
def sql(event)
unless event.payload[:name] == 'CACHE'
unless event.payload.fetch(:cached, event.payload[:name] == 'CACHE')
Transaction.current&.increment
end
end
......
......@@ -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
......
......@@ -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
......
......@@ -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
{
"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 .",
......
......@@ -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
......
......@@ -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
......
......@@ -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}')]")
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......@@ -16,17 +16,21 @@ feature 'Group issues page' do
let(:access_level) { ProjectFeature::ENABLED }
context 'when signed in' do
let(:user) { user_in_group }
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 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
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
......
......@@ -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
......
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
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
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
......@@ -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]
}
......
......@@ -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
......
......@@ -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
......
......@@ -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
......@@ -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
......@@ -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
......@@ -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
......
......@@ -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
......@@ -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
......@@ -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
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
......@@ -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
......
......@@ -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
......
......@@ -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
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')
expect { find_user_from_feed_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
end
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_feed_token).to eq user
end
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 be_nil
end
it 'returns exception if invalid rss_token' do
set_param(:rss_token, 'invalid_token')
expect { find_user_from_rss_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
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
......
......@@ -310,3 +310,8 @@ lfs_file_locks:
- user
project_badges:
- project
metrics:
- merge_request
- latest_closed_by
- merged_by
- pipeline
......@@ -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
......
......@@ -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
......
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
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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")
let(:endpoint_path) { "/projects/#{project.id}/merge_requests" }
expect_paginated_array_response
end
it_behaves_like 'merge requests list'
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
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 "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)
get api(endpoint_path, user), iids: [merge_request.iid, merge_request_closed.iid]
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).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
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] }
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" }
before do
@mr_later = mr_with_later_created_and_updated_at_time
@mr_earlier = mr_with_earlier_created_and_updated_at_time
group.add_reporter(user)
end
it "returns an array of merge_requests in ascending order" do
get api("/projects/#{project.id}/merge_requests?sort=asc", user)
it_behaves_like 'merge requests list'
expect_response_ordered_exactly(*merge_requests.sort_by { |mr| mr['created_at'] })
end
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) }
it "returns an array of merge_requests in descending order" do
get api("/projects/#{project.id}/merge_requests?sort=desc", user)
expect_response_ordered_exactly(*merge_requests.sort_by { |mr| mr['created_at'] }.reverse)
end
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_ordered_exactly(*merge_requests.sort_by { |mr| mr['updated_at'] }.reverse)
end
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_ordered_exactly(*merge_requests.sort_by { |mr| mr['created_at'] })
end
end
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'
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('/merge_requests', user), target_branch: merge_request_closed.target_branch, state: 'all'
expect_response_contain_exactly(merge_request_closed, merge_request_merged)
end
end
it_behaves_like 'merge requests list'
end
end
......
......@@ -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)
......
......@@ -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
......
......@@ -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
......
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
......@@ -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)
......
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
......@@ -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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment