Commit 33cf6171 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'master' into feature/gb/pipeline-only-except-with-modified-paths

* master:
  Require spec helpers loaded by other spec helpers first
  Resolve "2FA mobile options should be rephrased"
  Add css class to Admin > Project > show page
  Trim whitespace when inviting a new user by email
  Bump Gitaly to v0.124.0
  Banzai project ref- share context more aggresively
  Add reliable fetcher for Sidekiq
  Allows to filter issues by `Any milestone` in the API
parents a545703e 1e9003f4
...@@ -295,6 +295,7 @@ gem 'peek-mysql2', '~> 1.1.0', group: :mysql ...@@ -295,6 +295,7 @@ gem 'peek-mysql2', '~> 1.1.0', group: :mysql
gem 'peek-pg', '~> 1.3.0', group: :postgres gem 'peek-pg', '~> 1.3.0', group: :postgres
gem 'peek-rblineprof', '~> 0.2.0' gem 'peek-rblineprof', '~> 0.2.0'
gem 'peek-redis', '~> 1.2.0' gem 'peek-redis', '~> 1.2.0'
gem 'gitlab-sidekiq-fetcher', require: 'sidekiq-reliable-fetch'
# Metrics # Metrics
group :metrics do group :metrics do
......
...@@ -301,6 +301,8 @@ GEM ...@@ -301,6 +301,8 @@ GEM
mime-types (>= 1.16) mime-types (>= 1.16)
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab-markup (1.6.4) gitlab-markup (1.6.4)
gitlab-sidekiq-fetcher (0.3.0)
sidekiq (~> 5)
gitlab-styles (2.4.1) gitlab-styles (2.4.1)
rubocop (~> 0.54.0) rubocop (~> 0.54.0)
rubocop-gitlab-security (~> 0.1.0) rubocop-gitlab-security (~> 0.1.0)
...@@ -1031,6 +1033,7 @@ DEPENDENCIES ...@@ -1031,6 +1033,7 @@ DEPENDENCIES
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2) gitlab-gollum-lib (~> 4.2)
gitlab-markup (~> 1.6.4) gitlab-markup (~> 1.6.4)
gitlab-sidekiq-fetcher
gitlab-styles (~> 2.4) gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4) gitlab_omniauth-ldap (~> 2.0.4)
gon (~> 6.2) gon (~> 6.2)
......
...@@ -304,6 +304,8 @@ GEM ...@@ -304,6 +304,8 @@ GEM
mime-types (>= 1.16) mime-types (>= 1.16)
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab-markup (1.6.4) gitlab-markup (1.6.4)
gitlab-sidekiq-fetcher (0.3.0)
sidekiq (~> 5)
gitlab-styles (2.4.1) gitlab-styles (2.4.1)
rubocop (~> 0.54.0) rubocop (~> 0.54.0)
rubocop-gitlab-security (~> 0.1.0) rubocop-gitlab-security (~> 0.1.0)
...@@ -1040,6 +1042,7 @@ DEPENDENCIES ...@@ -1040,6 +1042,7 @@ DEPENDENCIES
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2) gitlab-gollum-lib (~> 4.2)
gitlab-markup (~> 1.6.4) gitlab-markup (~> 1.6.4)
gitlab-sidekiq-fetcher
gitlab-styles (~> 2.4) gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4) gitlab_omniauth-ldap (~> 2.0.4)
gon (~> 6.2) gon (~> 6.2)
......
...@@ -592,7 +592,7 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -592,7 +592,7 @@ function UsersSelect(currentUser, els, options = {}) {
if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) { if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) {
var trimmed = query.term.trim(); var trimmed = query.term.trim();
emailUser = { emailUser = {
name: "Invite \"" + query.term + "\" by email", name: "Invite \"" + trimmed + "\" by email",
username: trimmed, username: trimmed,
id: trimmed, id: trimmed,
invite: true invite: true
......
...@@ -428,6 +428,10 @@ class IssuableFinder ...@@ -428,6 +428,10 @@ class IssuableFinder
params[:milestone_title] == Milestone::Upcoming.name params[:milestone_title] == Milestone::Upcoming.name
end end
def filter_by_any_milestone?
params[:milestone_title] == Milestone::Any.title
end
def filter_by_started_milestone? def filter_by_started_milestone?
params[:milestone_title] == Milestone::Started.name params[:milestone_title] == Milestone::Started.name
end end
...@@ -437,6 +441,8 @@ class IssuableFinder ...@@ -437,6 +441,8 @@ class IssuableFinder
if milestones? if milestones?
if filter_by_no_milestone? if filter_by_no_milestone?
items = items.left_joins_milestones.where(milestone_id: [-1, nil]) items = items.left_joins_milestones.where(milestone_id: [-1, nil])
elsif filter_by_any_milestone?
items = items.any_milestone
elsif filter_by_upcoming_milestone? elsif filter_by_upcoming_milestone?
upcoming_ids = Milestone.upcoming_ids_by_projects(projects(items)) upcoming_ids = Milestone.upcoming_ids_by_projects(projects(items))
items = items.left_joins_milestones.where(milestone_id: upcoming_ids) items = items.left_joins_milestones.where(milestone_id: upcoming_ids)
......
...@@ -129,7 +129,7 @@ class LabelsFinder < UnionFinder ...@@ -129,7 +129,7 @@ class LabelsFinder < UnionFinder
end end
def project? def project?
params[:project_id].present? params[:project].present? || params[:project_id].present?
end end
def projects? def projects?
...@@ -152,7 +152,7 @@ class LabelsFinder < UnionFinder ...@@ -152,7 +152,7 @@ class LabelsFinder < UnionFinder
return @project if defined?(@project) return @project if defined?(@project)
if project? if project?
@project = Project.find(params[:project_id]) @project = params[:project] || Project.find(params[:project_id])
@project = nil unless authorized_to_read_labels?(@project) @project = nil unless authorized_to_read_labels?(@project)
else else
@project = nil @project = nil
......
...@@ -76,6 +76,7 @@ module Issuable ...@@ -76,6 +76,7 @@ module Issuable
scope :recent, -> { reorder(id: :desc) } scope :recent, -> { reorder(id: :desc) }
scope :of_projects, ->(ids) { where(project_id: ids) } scope :of_projects, ->(ids) { where(project_id: ids) }
scope :of_milestones, ->(ids) { where(milestone_id: ids) } scope :of_milestones, ->(ids) { where(milestone_id: ids) }
scope :any_milestone, -> { where('milestone_id IS NOT NULL') }
scope :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) } scope :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) }
scope :opened, -> { with_state(:opened) } scope :opened, -> { with_state(:opened) }
scope :only_opened, -> { with_state(:opened) } scope :only_opened, -> { with_state(:opened) }
......
- add_to_breadcrumbs "Projects", admin_projects_path - add_to_breadcrumbs "Projects", admin_projects_path
- breadcrumb_title @project.full_name - breadcrumb_title @project.full_name
- page_title @project.full_name, "Projects" - page_title @project.full_name, "Projects"
- @content_class = "admin-projects"
%h3.page-title %h3.page-title
Project: #{@project.full_name} Project: #{@project.full_name}
= link_to edit_project_path(@project), class: "btn btn-nr float-right" do = link_to edit_project_path(@project), class: "btn btn-nr float-right" do
......
%p.slead %p.slead
Should you ever lose your phone, each of these recovery codes can be used one Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one
time each to regain access to your account. Please save them in a safe place, or you time each to regain access to your account. Please save them in a safe place, or you
%b will %b will
lose access to your account. lose access to your account.
......
...@@ -6,13 +6,13 @@ ...@@ -6,13 +6,13 @@
.row.prepend-top-default .row.prepend-top-default
.col-lg-4 .col-lg-4
%h4.prepend-top-0 %h4.prepend-top-0
Register Two-Factor Authentication App Register Two-Factor Authenticator
%p %p
Use an app on your mobile device to enable two-factor authentication (2FA). Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA).
.col-lg-8 .col-lg-8
- if current_user.two_factor_otp_enabled? - if current_user.two_factor_otp_enabled?
%p %p
You've already enabled two-factor authentication using mobile authenticator applications. In order to register a different device, you must first disable two-factor authentication. You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication.
%p %p
If you lose your recovery codes you can generate new ones, invalidating all previous codes. If you lose your recovery codes you can generate new ones, invalidating all previous codes.
%div %div
......
---
title: Rephrase 2FA and TOTP documentation and view
merge_request: 21998
author: Marc Schwede
type: other
---
title: Allows to filter issues by Any milestone in the API
merge_request: 22080
author: Jacopo Beschi @jacopo-beschi
type: added
---
title: Trim whitespace when inviting a new user by email
merge_request: 22119
author: Jacopo Beschi @jacopo-beschi
type: fixed
---
title: Use Reliable Sidekiq fetch
merge_request: 21715
author:
type: fixed
---
title: Banzai label ref finder - minimize SQL calls by sharing context more aggresively
merge_request: 22070
author:
type: performance
...@@ -40,6 +40,10 @@ Sidekiq.configure_server do |config| ...@@ -40,6 +40,10 @@ Sidekiq.configure_server do |config|
ActiveRecord::Base.clear_all_connections! ActiveRecord::Base.clear_all_connections!
end end
if Feature.enabled?(:gitlab_sidekiq_reliable_fetcher)
Sidekiq::ReliableFetcher.setup_reliable_fetch!(config)
end
# Sidekiq-cron: load recurring jobs from gitlab.yml # Sidekiq-cron: load recurring jobs from gitlab.yml
# UGLY Hack to get nested hash from settingslogic # UGLY Hack to get nested hash from settingslogic
cron_jobs = JSON.parse(Gitlab.config.cron_jobs.to_json) cron_jobs = JSON.parse(Gitlab.config.cron_jobs.to_json)
...@@ -57,10 +61,10 @@ Sidekiq.configure_server do |config| ...@@ -57,10 +61,10 @@ Sidekiq.configure_server do |config|
Gitlab::SidekiqVersioning.install! Gitlab::SidekiqVersioning.install!
config = Gitlab::Database.config || db_config = Gitlab::Database.config ||
Rails.application.config.database_configuration[Rails.env] Rails.application.config.database_configuration[Rails.env]
config['pool'] = Sidekiq.options[:concurrency] db_config['pool'] = Sidekiq.options[:concurrency]
ActiveRecord::Base.establish_connection(config) ActiveRecord::Base.establish_connection(db_config)
Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}") Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}")
# Avoid autoload issue such as 'Mail::Parsers::AddressStruct' # Avoid autoload issue such as 'Mail::Parsers::AddressStruct'
......
...@@ -37,7 +37,7 @@ GET /issues?my_reaction_emoji=star ...@@ -37,7 +37,7 @@ GET /issues?my_reaction_emoji=star
| ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `state` | string | no | Return all issues or just those that are `opened` or `closed` | | `state` | string | no | Return all issues or just those that are `opened` or `closed` |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
| `milestone` | string | no | The milestone title. `No+Milestone` lists all issues with no milestone | | `milestone` | string | no | The milestone title. `No+Milestone` lists all issues with no milestone. `Any+Milestone` lists all issues that have an assigned milestone |
| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | | `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
| `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ | | `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ |
| `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | | `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
......
...@@ -2,18 +2,18 @@ ...@@ -2,18 +2,18 @@
Two-factor Authentication (2FA) provides an additional level of security to your Two-factor Authentication (2FA) provides an additional level of security to your
GitLab account. Once enabled, in addition to supplying your username and GitLab account. Once enabled, in addition to supplying your username and
password to login, you'll be prompted for a code generated by an application on password to login, you'll be prompted for a code generated by your one time password
your phone. authenticator. For example, a password manager on one of your devices.
By enabling 2FA, the only way someone other than you can log into your account By enabling 2FA, the only way someone other than you can log into your account
is to know your username and password *and* have access to your phone. is to know your username and password *and* have access to your one time password secret.
## Overview ## Overview
> **Note:** > **Note:**
When you enable 2FA, don't forget to back up your recovery codes. When you enable 2FA, don't forget to back up your recovery codes.
In addition to a phone application, GitLab supports U2F (universal 2nd factor) devices as In addition to one time authenticators (TOTP), GitLab supports U2F (universal 2nd factor) devices as
the second factor of authentication. Once enabled, in addition to supplying your username and the second factor of authentication. Once enabled, in addition to supplying your username and
password to login, you'll be prompted to activate your U2F device (usually by pressing password to login, you'll be prompted to activate your U2F device (usually by pressing
a button on it), and it will perform secure authentication on your behalf. a button on it), and it will perform secure authentication on your behalf.
...@@ -24,10 +24,10 @@ from other browsers. ...@@ -24,10 +24,10 @@ from other browsers.
## Enabling 2FA ## Enabling 2FA
There are two ways to enable two-factor authentication: via a mobile application There are two ways to enable two-factor authentication: via a one time password authenticator
or a U2F device. or a U2F device.
### Enable 2FA via mobile application ### Enable 2FA via one time password authenticator
**In GitLab:** **In GitLab:**
...@@ -82,7 +82,7 @@ Click on **Register U2F Device** to complete the process. ...@@ -82,7 +82,7 @@ Click on **Register U2F Device** to complete the process.
> **Note:** > **Note:**
Recovery codes are not generated for U2F devices. Recovery codes are not generated for U2F devices.
Should you ever lose access to your phone, you can use one of the ten provided Should you ever lose access to your one time password authenticator, you can use one of the ten provided
backup codes to login to your account. We suggest copying or printing them for backup codes to login to your account. We suggest copying or printing them for
storage in a safe place. **Each code can be used only once** to log in to your storage in a safe place. **Each code can be used only once** to log in to your
account. account.
...@@ -98,7 +98,7 @@ be presented with a second prompt, depending on which type of 2FA you've enabled ...@@ -98,7 +98,7 @@ be presented with a second prompt, depending on which type of 2FA you've enabled
### Log in via mobile application ### Log in via mobile application
Enter the pin from your phone's application or a recovery code to log in. Enter the pin from your one time password authenticator's application or a recovery code to log in.
![Two-Factor Authentication on sign in via OTP](img/2fa_auth.png) ![Two-Factor Authentication on sign in via OTP](img/2fa_auth.png)
......
...@@ -13,6 +13,7 @@ module Banzai ...@@ -13,6 +13,7 @@ module Banzai
# Returns a Project, or nil if the reference can't be found # Returns a Project, or nil if the reference can't be found
def parent_from_ref(ref) def parent_from_ref(ref)
return context[:project] || context[:group] unless ref return context[:project] || context[:group] unless ref
return context[:project] if context[:project]&.full_path == ref
Project.find_by_full_path(ref) Project.find_by_full_path(ref)
end end
......
...@@ -48,7 +48,7 @@ module Banzai ...@@ -48,7 +48,7 @@ module Banzai
include_ancestor_groups: true, include_ancestor_groups: true,
only_group_labels: true } only_group_labels: true }
else else
{ project_id: parent.id, { project: parent,
include_ancestor_groups: true } include_ancestor_groups: true }
end end
......
...@@ -637,6 +637,18 @@ describe Projects::IssuesController do ...@@ -637,6 +637,18 @@ describe Projects::IssuesController do
project_id: project, project_id: project,
id: id id: id
end end
it 'avoids (most) N+1s loading labels' do
label = create(:label, project: project).to_reference
labels = create_list(:label, 10, project: project).map(&:to_reference)
issue = create(:issue, project: project, description: 'Test issue')
control_count = ActiveRecord::QueryRecorder.new { issue.update(description: [issue.description, label].join(' ')) }.count
# Follow-up to get rid of this `2 * label.count` requirement: https://gitlab.com/gitlab-org/gitlab-ce/issues/52230
expect { issue.update(description: [issue.description, labels].join(' ')) }
.not_to exceed_query_limit(control_count + 2 * labels.count)
end
end end
describe 'GET #realtime_changes' do describe 'GET #realtime_changes' do
......
...@@ -42,7 +42,7 @@ describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do ...@@ -42,7 +42,7 @@ describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
it 'allows registering a new device with a name' do it 'allows registering a new device with a name' do
visit profile_account_path visit profile_account_path
manage_two_factor_authentication manage_two_factor_authentication
expect(page).to have_content("You've already enabled two-factor authentication using mobile") expect(page).to have_content("You've already enabled two-factor authentication using one time password authenticators")
u2f_device = register_u2f_device u2f_device = register_u2f_device
...@@ -70,7 +70,7 @@ describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do ...@@ -70,7 +70,7 @@ describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
it 'allows deleting a device' do it 'allows deleting a device' do
visit profile_account_path visit profile_account_path
manage_two_factor_authentication manage_two_factor_authentication
expect(page).to have_content("You've already enabled two-factor authentication using mobile") expect(page).to have_content("You've already enabled two-factor authentication using one time password authenticators")
first_u2f_device = register_u2f_device first_u2f_device = register_u2f_device
second_u2f_device = register_u2f_device(name: 'My other device') second_u2f_device = register_u2f_device(name: 'My other device')
......
...@@ -125,6 +125,14 @@ describe IssuesFinder do ...@@ -125,6 +125,14 @@ describe IssuesFinder do
end end
end end
context 'filtering by any milestone' do
let(:params) { { milestone_title: Milestone::Any.title } }
it 'returns issues with any assigned milestone' do
expect(issues).to contain_exactly(issue1)
end
end
context 'filtering by upcoming milestone' do context 'filtering by upcoming milestone' do
let(:params) { { milestone_title: Milestone::Upcoming.name } } let(:params) { { milestone_title: Milestone::Upcoming.name } }
......
require 'spec_helper' require 'spec_helper'
describe Banzai::CrossProjectReference do describe Banzai::CrossProjectReference do
include described_class let(:including_class) { Class.new.include(described_class).new }
before do
allow(including_class).to receive(:context).and_return({})
allow(including_class).to receive(:parent_from_ref).and_call_original
end
describe '#parent_from_ref' do describe '#parent_from_ref' do
context 'when no project was referenced' do context 'when no project was referenced' do
it 'returns the project from context' do it 'returns the project from context' do
project = double project = double
allow(self).to receive(:context).and_return({ project: project }) allow(including_class).to receive(:context).and_return({ project: project })
expect(parent_from_ref(nil)).to eq project expect(including_class.parent_from_ref(nil)).to eq project
end end
end end
...@@ -18,15 +23,15 @@ describe Banzai::CrossProjectReference do ...@@ -18,15 +23,15 @@ describe Banzai::CrossProjectReference do
it 'returns the group from context' do it 'returns the group from context' do
group = double group = double
allow(self).to receive(:context).and_return({ group: group }) allow(including_class).to receive(:context).and_return({ group: group })
expect(parent_from_ref(nil)).to eq group expect(including_class.parent_from_ref(nil)).to eq group
end end
end end
context 'when referenced project does not exist' do context 'when referenced project does not exist' do
it 'returns nil' do it 'returns nil' do
expect(parent_from_ref('invalid/reference')).to be_nil expect(including_class.parent_from_ref('invalid/reference')).to be_nil
end end
end end
...@@ -37,7 +42,7 @@ describe Banzai::CrossProjectReference do ...@@ -37,7 +42,7 @@ describe Banzai::CrossProjectReference do
expect(Project).to receive(:find_by_full_path) expect(Project).to receive(:find_by_full_path)
.with('cross/reference').and_return(project2) .with('cross/reference').and_return(project2)
expect(parent_from_ref('cross/reference')).to eq project2 expect(including_class.parent_from_ref('cross/reference')).to eq project2
end end
end end
end end
......
...@@ -60,6 +60,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter do ...@@ -60,6 +60,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter do
exp = act = "See #{commit1.id.reverse}...#{commit2.id}" exp = act = "See #{commit1.id.reverse}...#{commit2.id}"
allow(project.repository).to receive(:commit).with(commit1.id.reverse) allow(project.repository).to receive(:commit).with(commit1.id.reverse)
allow(project.repository).to receive(:commit).with(commit2.id)
expect(reference_filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
......
...@@ -56,6 +56,7 @@ describe API::Issues do ...@@ -56,6 +56,7 @@ describe API::Issues do
let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) } let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) }
let(:no_milestone_title) { URI.escape(Milestone::None.title) } let(:no_milestone_title) { URI.escape(Milestone::None.title) }
let(:any_milestone_title) { URI.escape(Milestone::Any.title) }
before(:all) do before(:all) do
project.add_reporter(user) project.add_reporter(user)
...@@ -811,6 +812,15 @@ describe API::Issues do ...@@ -811,6 +812,15 @@ describe API::Issues do
expect(json_response.first['id']).to eq(confidential_issue.id) expect(json_response.first['id']).to eq(confidential_issue.id)
end end
it 'returns an array of issues with any milestone' do
get api("#{base_url}/issues?milestone=#{any_milestone_title}", user)
response_ids = json_response.map { |issue| issue['id'] }
expect_paginated_array_response(size: 2)
expect(response_ids).to contain_exactly(closed_issue.id, issue.id)
end
it 'sorts by created_at descending by default' do it 'sorts by created_at descending by default' do
get api("#{base_url}/issues", user) get api("#{base_url}/issues", user)
......
...@@ -34,6 +34,11 @@ Rainbow.enabled = false ...@@ -34,6 +34,11 @@ Rainbow.enabled = false
# Requires supporting ruby files with custom matchers and macros, etc, # Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories. # in spec/support/ and its subdirectories.
# Requires helpers, and shared contexts/examples first since they're used in other support files # Requires helpers, and shared contexts/examples first since they're used in other support files
# Load these first since they may be required by other helpers
require Rails.root.join("spec/support/helpers/git_helpers.rb")
# Then the rest
Dir[Rails.root.join("spec/support/helpers/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/support/helpers/*.rb")].each { |f| require f }
Dir[Rails.root.join("spec/support/shared_contexts/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/support/shared_contexts/*.rb")].each { |f| require f }
Dir[Rails.root.join("spec/support/shared_examples/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/support/shared_examples/*.rb")].each { |f| require f }
......
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