summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop.yml1
-rw-r--r--app/assets/javascripts/static_site_editor/components/static_site_editor.vue6
-rw-r--r--app/controllers/projects/alert_management_controller.rb11
-rw-r--r--app/controllers/projects/issues_controller.rb3
-rw-r--r--app/graphql/resolvers/issues_resolver.rb16
-rw-r--r--app/graphql/types/issuable_sort_enum.rb3
-rw-r--r--app/models/commit_status.rb8
-rw-r--r--app/presenters/commit_status_presenter.rb8
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml12
-rw-r--r--app/views/notify/issues_csv_email.html.haml9
-rw-r--r--app/views/notify/issues_csv_email.text.erb4
-rw-r--r--app/views/projects/issues/export_csv/_modal.html.haml1
-rw-r--r--app/views/shared/members/_badge.html.haml4
-rw-r--r--app/views/shared/members/_blocked_badge.html.haml3
-rw-r--r--app/views/shared/members/_its_you_badge.html.haml3
-rw-r--r--app/views/shared/members/_member.html.haml25
-rw-r--r--app/views/shared/members/_two_factor_auth_badge.html.haml3
-rw-r--r--changelogs/unreleased/29713-graphql-add-issue-priority-sort.yml5
-rw-r--r--changelogs/unreleased/38096-resource-state-events-pd.yml5
-rw-r--r--changelogs/unreleased/tr-alert-management-feature-flag.yml5
-rw-r--r--config/routes/project.rb34
-rw-r--r--db/migrate/20200406132529_add_resource_state_events_table.rb20
-rw-r--r--db/migrate/20200423075720_add_user_id_foreign_key_to_resource_state_events.rb19
-rw-r--r--db/migrate/20200423080334_add_issue_id_foreign_key_to_resource_state_events.rb19
-rw-r--r--db/migrate/20200423080607_add_merge_request_id_foreign_key_to_resource_state_events.rb19
-rw-r--r--db/migrate/20200423081409_add_constraint_to_resource_state_events_must_belong_to_issue_or_merge_request.rb19
-rw-r--r--db/structure.sql44
-rw-r--r--doc/administration/geo/replication/index.md2
-rw-r--r--doc/administration/high_availability/consul.md5
-rw-r--r--doc/api/epics.md3
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql10
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json12
-rw-r--r--doc/development/go_guide/index.md10
-rw-r--r--doc/development/permissions.md6
-rw-r--r--doc/user/group/epics/index.md2
-rw-r--r--doc/user/project/code_owners.md29
-rw-r--r--doc/user/project/merge_requests/code_quality.md14
-rw-r--r--doc/user/project/merge_requests/img/code_quality.pngbin94062 -> 511302 bytes
-rw-r--r--doc/user/project/repository/file_finder.md6
-rw-r--r--doc/user/project/repository/img/file_finder_find_button.pngbin14565 -> 0 bytes
-rw-r--r--doc/user/project/repository/img/file_finder_find_button_v12_10.pngbin0 -> 70732 bytes
-rw-r--r--doc/user/project/repository/img/file_finder_find_file.pngbin19478 -> 0 bytes
-rw-r--r--doc/user/project/repository/img/file_finder_find_file_v12_10.pngbin0 -> 59474 bytes
-rw-r--r--doc/user/project/requirements/index.md3
-rw-r--r--locale/gitlab.pot15
-rw-r--r--qa/spec/factory/resource/user_spec.rb8
-rw-r--r--qa/spec/git/repository_spec.rb8
-rw-r--r--spec/controllers/projects/alert_management_controller_spec.rb40
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb41
-rw-r--r--spec/graphql/types/issuable_sort_enum_spec.rb11
-rw-r--r--spec/models/commit_status_spec.rb44
-rw-r--r--spec/presenters/ci/build_presenter_spec.rb26
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb123
-rw-r--r--spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb54
54 files changed, 623 insertions, 158 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index 0df519e1d75..78aefcd0c7a 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -327,7 +327,6 @@ RSpec/LeakyConstantDeclaration:
Enabled: true
Exclude:
- 'spec/**/*.rb'
- - 'qa/spec/**/*.rb'
RSpec/EmptyLineAfterHook:
Enabled: false
diff --git a/app/assets/javascripts/static_site_editor/components/static_site_editor.vue b/app/assets/javascripts/static_site_editor/components/static_site_editor.vue
index 82917319fc3..d45c3d08ef4 100644
--- a/app/assets/javascripts/static_site_editor/components/static_site_editor.vue
+++ b/app/assets/javascripts/static_site_editor/components/static_site_editor.vue
@@ -5,7 +5,7 @@ import { GlSkeletonLoader } from '@gitlab/ui';
import EditArea from './edit_area.vue';
import EditHeader from './edit_header.vue';
import SavedChangesMessage from './saved_changes_message.vue';
-import Toolbar from './publish_toolbar.vue';
+import PublishToolbar from './publish_toolbar.vue';
import InvalidContentMessage from './invalid_content_message.vue';
import SubmitChangesError from './submit_changes_error.vue';
@@ -16,7 +16,7 @@ export default {
InvalidContentMessage,
GlSkeletonLoader,
SavedChangesMessage,
- Toolbar,
+ PublishToolbar,
SubmitChangesError,
},
computed: {
@@ -80,7 +80,7 @@ export default {
:value="content"
@input="setContent"
/>
- <toolbar
+ <publish-toolbar
:return-url="returnUrl"
:saveable="contentChanged"
:saving-changes="isSavingChanges"
diff --git a/app/controllers/projects/alert_management_controller.rb b/app/controllers/projects/alert_management_controller.rb
index 46db87dba94..5480e19072d 100644
--- a/app/controllers/projects/alert_management_controller.rb
+++ b/app/controllers/projects/alert_management_controller.rb
@@ -1,9 +1,14 @@
# frozen_string_literal: true
class Projects::AlertManagementController < Projects::ApplicationController
+ before_action :ensure_feature_enabled
+
def index
- respond_to do |format|
- format.html
- end
+ end
+
+ private
+
+ def ensure_feature_enabled
+ render_404 unless Feature.enabled?(:alert_management_minimal, project)
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 3aae8990f07..3d53ad1a29f 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -193,7 +193,8 @@ class Projects::IssuesController < Projects::ApplicationController
ExportCsvWorker.perform_async(current_user.id, project.id, finder_options.to_h) # rubocop:disable CodeReuse/Worker
index_path = project_issues_path(project)
- redirect_to(index_path, notice: "Your CSV export has started. It will be emailed to #{current_user.notification_email} when complete.")
+ message = _('Your CSV export has started. It will be emailed to %{email} when complete.') % { email: current_user.notification_email }
+ redirect_to(index_path, notice: message)
end
def import_csv
diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb
index 04da54a6bb6..4d33a874a81 100644
--- a/app/graphql/resolvers/issues_resolver.rb
+++ b/app/graphql/resolvers/issues_resolver.rb
@@ -52,6 +52,8 @@ module Resolvers
type Types::IssueType, null: true
+ NON_STABLE_CURSOR_SORTS = %i[priority_asc priority_desc].freeze
+
def resolve(**args)
# The project could have been loaded in batch by `BatchLoader`.
# At this point we need the `id` of the project to query for issues, so
@@ -70,7 +72,15 @@ module Resolvers
args[:iids] ||= [args[:iid]].compact
args[:attempt_project_search_optimizations] = args[:search].present?
- IssuesFinder.new(context[:current_user], args).execute
+ issues = IssuesFinder.new(context[:current_user], args).execute
+
+ if non_stable_cursor_sort?(args[:sort])
+ # Certain complex sorts are not supported by the stable cursor pagination yet.
+ # In these cases, we use offset pagination, so we return the correct connection.
+ Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection.new(issues)
+ else
+ issues
+ end
end
def self.resolver_complexity(args, child_complexity:)
@@ -79,5 +89,9 @@ module Resolvers
complexity
end
+
+ def non_stable_cursor_sort?(sort)
+ NON_STABLE_CURSOR_SORTS.include?(sort)
+ end
end
end
diff --git a/app/graphql/types/issuable_sort_enum.rb b/app/graphql/types/issuable_sort_enum.rb
index 9fb1249d582..bade05b2744 100644
--- a/app/graphql/types/issuable_sort_enum.rb
+++ b/app/graphql/types/issuable_sort_enum.rb
@@ -4,5 +4,8 @@ module Types
class IssuableSortEnum < SortEnum
graphql_name 'IssuableSort'
description 'Values for sorting issuables'
+
+ value 'PRIORITY_ASC', 'Priority by ascending order', value: :priority_asc
+ value 'PRIORITY_DESC', 'Priority by descending order', value: :priority_desc
end
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 046f131b041..a2a30c61bfb 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -267,8 +267,16 @@ class CommitStatus < ApplicationRecord
end
end
+ def recoverable?
+ failed? && !unrecoverable_failure?
+ end
+
private
+ def unrecoverable_failure?
+ script_failure? || missing_dependency_failure? || archived_failure? || scheduler_failure? || data_integrity_failure?
+ end
+
def schedule_stage_and_pipeline_update
if Feature.enabled?(:ci_atomic_processing, project)
# Atomic Processing requires only single Worker
diff --git a/app/presenters/commit_status_presenter.rb b/app/presenters/commit_status_presenter.rb
index 23e688e562e..52811e152a6 100644
--- a/app/presenters/commit_status_presenter.rb
+++ b/app/presenters/commit_status_presenter.rb
@@ -33,14 +33,6 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
def callout_failure_message
self.class.callout_failure_messages.fetch(failure_reason.to_sym)
end
-
- def recoverable?
- failed? && !unrecoverable?
- end
-
- def unrecoverable?
- script_failure? || missing_dependency_failure? || archived_failure? || scheduler_failure? || data_integrity_failure?
- end
end
CommitStatusPresenter.prepend_if_ee('::EE::CommitStatusPresenter')
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 3aa056fad7b..9df6a7dfc07 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -222,12 +222,14 @@
%span
= _('Metrics')
- - if project_nav_tab?(:alert_management)
- = nav_link(controller: :alert_management) do
- = link_to project_alert_management_index_path(@project), title: _('Alerts'), class: 'shortcuts-tracking qa-operations-tracking-link' do
- %span
- = _('Alerts')
+ - if Feature.enabled?(:alert_management_minimal, @project)
+ - if project_nav_tab?(:alert_management)
+ = nav_link(controller: :alert_management) do
+ = link_to project_alert_management_index_path(@project), title: _('Alerts'), class: 'shortcuts-tracking qa-operations-tracking-link' do
+ %span
+ = _('Alerts')
+ - if project_nav_tab? :environments
= render_if_exists "layouts/nav/sidebar/tracing_link"
= nav_link(controller: :environments, action: [:index, :folder, :show, :new, :edit, :create, :update, :stop, :terminal]) do
diff --git a/app/views/notify/issues_csv_email.html.haml b/app/views/notify/issues_csv_email.html.haml
index b777ca1e57d..77502a45f02 100644
--- a/app/views/notify/issues_csv_email.html.haml
+++ b/app/views/notify/issues_csv_email.html.haml
@@ -1,9 +1,6 @@
--# haml-lint:disable NoPlainNodes
%p{ style: 'font-size:18px; text-align:center; line-height:30px;' }
- Your CSV export of #{ pluralize(@written_count, 'issue') } from project
- %a{ href: project_url(@project), style: "color:#3777b0; text-decoration:none; display:block;" }
- = @project.full_name
- has been added to this email as an attachment.
+ - project_link = link_to(@project.full_name, project_url(@project), style: "color:#3777b0; text-decoration:none; display:block;")
+ = _('Your CSV export of %{issues_count} from project %{project_link} has been added to this email as an attachment.').html_safe % { issues_count: pluralize(@written_count, 'issue'), project_link: project_link }
- if @truncated
%p
- This attachment has been truncated to avoid exceeding a maximum allowed attachment size of 15MB. #{ @written_count } of #{ @issues_count } issues have been included. Consider re-exporting with a narrower selection of issues.
+ = _('This attachment has been truncated to avoid exceeding the maximum allowed attachment size of 15MB. %{written_count} of %{issues_count} issues have been included. Consider re-exporting with a narrower selection of issues.') % { written_count: @written_count, issues_count: @issues_count }
diff --git a/app/views/notify/issues_csv_email.text.erb b/app/views/notify/issues_csv_email.text.erb
index 5d4128e3ae9..a1d2a4691bc 100644
--- a/app/views/notify/issues_csv_email.text.erb
+++ b/app/views/notify/issues_csv_email.text.erb
@@ -1,5 +1,5 @@
-Your CSV export of <%= pluralize(@written_count, 'issue') %> from project <%= @project.full_name %> (<%= project_url(@project) %>) has been added to this email as an attachment.
+<%= _('Your CSV export of %{written_count} from project %{project_name} (%{project_url}) has been added to this email as an attachment.') % { written_count: pluralize(@written_count, 'issue'), project_name: @project.full_name, project_url: project_url(@project) } %>
<% if @truncated %>
-This attachment has been truncated to avoid exceeding a maximum allowed attachment size of 15MB. <%= @written_count %> of <%= @issues_count %> issues have been included. Consider re-exporting with a narrower selection of issues.
+ <%= _('This attachment has been truncated to avoid exceeding the maximum allowed attachment size of 15MB. %{written_count} of %{issues_count} issues have been included. Consider re-exporting with a narrower selection of issues.') % { written_count: @written_count, issues_count: @issues_count} %>
<% end %>
diff --git a/app/views/projects/issues/export_csv/_modal.html.haml b/app/views/projects/issues/export_csv/_modal.html.haml
index af3a087ca59..9fdeb901b56 100644
--- a/app/views/projects/issues/export_csv/_modal.html.haml
+++ b/app/views/projects/issues/export_csv/_modal.html.haml
@@ -1,4 +1,3 @@
--# haml-lint:disable NoPlainNodes
- if current_user
.issues-export-modal.modal
.modal-dialog
diff --git a/app/views/shared/members/_badge.html.haml b/app/views/shared/members/_badge.html.haml
new file mode 100644
index 00000000000..e304207f3e9
--- /dev/null
+++ b/app/views/shared/members/_badge.html.haml
@@ -0,0 +1,4 @@
+- type ||= 'info'
+
+%span.px-1.py-1
+ %span{ class: "badge badge-#{type}" }= yield
diff --git a/app/views/shared/members/_blocked_badge.html.haml b/app/views/shared/members/_blocked_badge.html.haml
new file mode 100644
index 00000000000..95335ebe74d
--- /dev/null
+++ b/app/views/shared/members/_blocked_badge.html.haml
@@ -0,0 +1,3 @@
+- if user.blocked?
+ = render 'shared/members/badge', type: 'danger' do
+ = _("Blocked")
diff --git a/app/views/shared/members/_its_you_badge.html.haml b/app/views/shared/members/_its_you_badge.html.haml
new file mode 100644
index 00000000000..b53ffd8032d
--- /dev/null
+++ b/app/views/shared/members/_its_you_badge.html.haml
@@ -0,0 +1,3 @@
+- if user == current_user
+ = render 'shared/members/badge', type: 'success' do
+ = _("It's you")
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index d74030c566f..31eba9d71ee 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -13,24 +13,23 @@
- if user
= image_tag avatar_icon_for_user(user, 40), class: "avatar s40 flex-shrink-0 flex-grow-0", alt: ''
.user-info
- = link_to user.name, user_path(user), class: 'member js-user-link', data: { user_id: user.id }
- = user_status(user)
- %span.cgray= user.to_reference
+ %span.mr-1
+ = link_to user.name, user_path(user), class: 'member js-user-link', data: { user_id: user.id }
+ = user_status(user)
+ %span.cgray= user.to_reference
- = render_if_exists 'shared/members/ee/sso_badge', member: member
+ .mx-n1.d-inline-flex.flex-wrap
+ = render_if_exists 'shared/members/ee/sso_badge', member: member
- - if user == current_user
- %span.badge.badge-success.prepend-left-5= _("It's you")
+ = render_if_exists 'shared/members/ee/gma_badge', member: member
- = render_if_exists 'shared/members/ee/license_badge', user: user, group: @group
+ = render 'shared/members/its_you_badge', user: user, current_user: current_user
- - if user.blocked?
- %label.badge.badge-danger
- %strong= _("Blocked")
+ = render_if_exists 'shared/members/ee/license_badge', user: user, group: @group
- - if user.two_factor_enabled?
- %label.badge.badge-info
- = _("2FA")
+ = render 'shared/members/blocked_badge', user: user
+
+ = render 'shared/members/two_factor_auth_badge', user: user
- if source.instance_of?(Group) && source != @group
&middot;
diff --git a/app/views/shared/members/_two_factor_auth_badge.html.haml b/app/views/shared/members/_two_factor_auth_badge.html.haml
new file mode 100644
index 00000000000..34850c135d6
--- /dev/null
+++ b/app/views/shared/members/_two_factor_auth_badge.html.haml
@@ -0,0 +1,3 @@
+- if user.two_factor_enabled?
+ = render 'shared/members/badge', type: 'info' do
+ = _("2FA")
diff --git a/changelogs/unreleased/29713-graphql-add-issue-priority-sort.yml b/changelogs/unreleased/29713-graphql-add-issue-priority-sort.yml
new file mode 100644
index 00000000000..12661ae69ad
--- /dev/null
+++ b/changelogs/unreleased/29713-graphql-add-issue-priority-sort.yml
@@ -0,0 +1,5 @@
+---
+title: Graphql query for issues can now be sorted by priority
+merge_request: 18901
+author:
+type: added
diff --git a/changelogs/unreleased/38096-resource-state-events-pd.yml b/changelogs/unreleased/38096-resource-state-events-pd.yml
new file mode 100644
index 00000000000..a761945d994
--- /dev/null
+++ b/changelogs/unreleased/38096-resource-state-events-pd.yml
@@ -0,0 +1,5 @@
+---
+title: Add resource_state_events table
+merge_request: 28926
+author:
+type: added
diff --git a/changelogs/unreleased/tr-alert-management-feature-flag.yml b/changelogs/unreleased/tr-alert-management-feature-flag.yml
new file mode 100644
index 00000000000..6ae9b766d13
--- /dev/null
+++ b/changelogs/unreleased/tr-alert-management-feature-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Move alert management behind a feature flag
+merge_request: 30133
+author:
+type: fixed
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 67afbe5e7f5..bad1c57a6d4 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -316,7 +316,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
# All new routes should go under /-/ scope.
# Look for scope '-' at the top of the file.
- # rubocop: disable Cop/PutProjectRoutesUnderScope
#
# Templates
@@ -332,8 +331,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
defaults: { format: 'json' },
constraints: { template_type: %r{issue|merge_request}, format: 'json' }
- resource :pages, only: [:show, :update, :destroy] do
- resources :domains, except: :index, controller: 'pages_domains', constraints: { id: %r{[^/]+} } do
+ resource :pages, only: [:show, :update, :destroy] do # rubocop: disable Cop/PutProjectRoutesUnderScope
+ resources :domains, except: :index, controller: 'pages_domains', constraints: { id: %r{[^/]+} } do # rubocop: disable Cop/PutProjectRoutesUnderScope
member do
post :verify
post :retry_auto_ssl
@@ -342,7 +341,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do
+ resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do # rubocop: disable Cop/PutProjectRoutesUnderScope
member do
get :raw
post :mark_as_spam
@@ -350,14 +349,14 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
namespace :prometheus do
- resources :alerts, constraints: { id: /\d+/ }, only: [:index, :create, :show, :update, :destroy] do
+ resources :alerts, constraints: { id: /\d+/ }, only: [:index, :create, :show, :update, :destroy] do # rubocop: disable Cop/PutProjectRoutesUnderScope
post :notify, on: :collection
member do
get :metrics_dashboard
end
end
- resources :metrics, constraints: { id: %r{[^\/]+} }, only: [:index, :new, :create, :edit, :update, :destroy] do
+ resources :metrics, constraints: { id: %r{[^\/]+} }, only: [:index, :new, :create, :edit, :update, :destroy] do # rubocop: disable Cop/PutProjectRoutesUnderScope
get :active_common, on: :collection
post :validate_query, on: :collection
end
@@ -378,28 +377,28 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
draw :legacy_builds
- resources :hooks, only: [:index, :create, :edit, :update, :destroy], constraints: { id: /\d+/ } do
+ resources :hooks, only: [:index, :create, :edit, :update, :destroy], constraints: { id: /\d+/ } do # rubocop: disable Cop/PutProjectRoutesUnderScope
member do
post :test
end
- resources :hook_logs, only: [:show] do
+ resources :hook_logs, only: [:show] do # rubocop: disable Cop/PutProjectRoutesUnderScope
member do
post :retry
end
end
end
- resources :container_registry, only: [:index, :destroy, :show],
+ resources :container_registry, only: [:index, :destroy, :show], # rubocop: disable Cop/PutProjectRoutesUnderScope
controller: 'registry/repositories'
namespace :registry do
- resources :repository, only: [] do
+ resources :repository, only: [] do # rubocop: disable Cop/PutProjectRoutesUnderScope
# We default to JSON format in the controller to avoid ambiguity.
# `latest.json` could either be a request for a tag named `latest`
# in JSON format, or a request for tag named `latest.json`.
scope format: false do
- resources :tags, only: [:index, :destroy],
+ resources :tags, only: [:index, :destroy], # rubocop: disable Cop/PutProjectRoutesUnderScope
constraints: { id: Gitlab::Regex.container_registry_tag_regex } do
collection do
delete :bulk_destroy
@@ -415,7 +414,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
draw :issues
end
- resources :notes, only: [:create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do
+ resources :notes, only: [:create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do # rubocop: disable Cop/PutProjectRoutesUnderScope
member do
delete :delete_attachment
post :resolve
@@ -425,16 +424,16 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get 'noteable/:target_type/:target_id/notes' => 'notes#index', as: 'noteable_notes'
- resources :todos, only: [:create]
+ resources :todos, only: [:create] # rubocop: disable Cop/PutProjectRoutesUnderScope
- resources :uploads, only: [:create] do
+ resources :uploads, only: [:create] do # rubocop: disable Cop/PutProjectRoutesUnderScope
collection do
get ":secret/:filename", action: :show, as: :show, constraints: { filename: %r{[^/]+} }, format: false, defaults: { format: nil }
post :authorize
end
end
- resources :runners, only: [:index, :edit, :update, :destroy, :show] do
+ resources :runners, only: [:index, :edit, :update, :destroy, :show] do # rubocop: disable Cop/PutProjectRoutesUnderScope
member do
post :resume
post :pause
@@ -446,8 +445,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resources :runner_projects, only: [:create, :destroy]
- resources :badges, only: [:index] do
+ resources :runner_projects, only: [:create, :destroy] # rubocop: disable Cop/PutProjectRoutesUnderScope
+ resources :badges, only: [:index] do # rubocop: disable Cop/PutProjectRoutesUnderScope
collection do
scope '*ref', constraints: { ref: Gitlab::PathRegex.git_reference_regex } do
constraints format: /svg/ do
@@ -470,7 +469,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
# All new routes should go under /-/ scope.
# Look for scope '-' at the top of the file.
- # rubocop: enable Cop/PutProjectRoutesUnderScope
# Legacy routes.
# Introduced in 12.0.
diff --git a/db/migrate/20200406132529_add_resource_state_events_table.rb b/db/migrate/20200406132529_add_resource_state_events_table.rb
new file mode 100644
index 00000000000..ce241dff4dd
--- /dev/null
+++ b/db/migrate/20200406132529_add_resource_state_events_table.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class AddResourceStateEventsTable < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ create_table :resource_state_events, id: :bigserial do |t|
+ t.bigint :user_id, null: false
+ t.bigint :issue_id, null: true
+ t.bigint :merge_request_id, null: true
+
+ t.datetime_with_timezone :created_at, null: false
+ t.integer :state, limit: 2, null: false
+
+ t.index [:issue_id, :created_at], name: 'index_resource_state_events_on_issue_id_and_created_at'
+ t.index [:user_id], name: 'index_resource_state_events_on_user_id'
+ t.index [:merge_request_id], name: 'index_resource_state_events_on_merge_request_id'
+ end
+ end
+end
diff --git a/db/migrate/20200423075720_add_user_id_foreign_key_to_resource_state_events.rb b/db/migrate/20200423075720_add_user_id_foreign_key_to_resource_state_events.rb
new file mode 100644
index 00000000000..702347e5d43
--- /dev/null
+++ b/db/migrate/20200423075720_add_user_id_foreign_key_to_resource_state_events.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddUserIdForeignKeyToResourceStateEvents < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ add_foreign_key :resource_state_events, :users, column: :user_id, on_delete: :nullify # rubocop:disable Migration/AddConcurrentForeignKey
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :resource_state_events, column: :user_id
+ end
+ end
+end
diff --git a/db/migrate/20200423080334_add_issue_id_foreign_key_to_resource_state_events.rb b/db/migrate/20200423080334_add_issue_id_foreign_key_to_resource_state_events.rb
new file mode 100644
index 00000000000..660c51eb3a6
--- /dev/null
+++ b/db/migrate/20200423080334_add_issue_id_foreign_key_to_resource_state_events.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddIssueIdForeignKeyToResourceStateEvents < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ add_foreign_key :resource_state_events, :issues, column: :issue_id, on_delete: :cascade # rubocop:disable Migration/AddConcurrentForeignKey
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :resource_state_events, column: :issue_id
+ end
+ end
+end
diff --git a/db/migrate/20200423080607_add_merge_request_id_foreign_key_to_resource_state_events.rb b/db/migrate/20200423080607_add_merge_request_id_foreign_key_to_resource_state_events.rb
new file mode 100644
index 00000000000..4f0a689a992
--- /dev/null
+++ b/db/migrate/20200423080607_add_merge_request_id_foreign_key_to_resource_state_events.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddMergeRequestIdForeignKeyToResourceStateEvents < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ add_foreign_key :resource_state_events, :merge_requests, column: :merge_request_id, on_delete: :cascade # rubocop:disable Migration/AddConcurrentForeignKey
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :resource_state_events, column: :merge_request_id
+ end
+ end
+end
diff --git a/db/migrate/20200423081409_add_constraint_to_resource_state_events_must_belong_to_issue_or_merge_request.rb b/db/migrate/20200423081409_add_constraint_to_resource_state_events_must_belong_to_issue_or_merge_request.rb
new file mode 100644
index 00000000000..57df1045e2c
--- /dev/null
+++ b/db/migrate/20200423081409_add_constraint_to_resource_state_events_must_belong_to_issue_or_merge_request.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddConstraintToResourceStateEventsMustBelongToIssueOrMergeRequest < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ CONSTRAINT_NAME = 'resource_state_events_must_belong_to_issue_or_merge_request'
+
+ def up
+ add_check_constraint :resource_state_events, '(issue_id != NULL AND merge_request_id IS NULL) OR (merge_request_id != NULL AND issue_id IS NULL)', CONSTRAINT_NAME
+ end
+
+ def down
+ remove_check_constraint :resource_state_events, CONSTRAINT_NAME
+ end
+end
diff --git a/db/structure.sql b/db/structure.sql
index 759465c6845..b3e605b97cf 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -5643,6 +5643,25 @@ CREATE SEQUENCE public.resource_milestone_events_id_seq
ALTER SEQUENCE public.resource_milestone_events_id_seq OWNED BY public.resource_milestone_events.id;
+CREATE TABLE public.resource_state_events (
+ id bigint NOT NULL,
+ user_id bigint NOT NULL,
+ issue_id bigint,
+ merge_request_id bigint,
+ created_at timestamp with time zone NOT NULL,
+ state smallint NOT NULL,
+ CONSTRAINT resource_state_events_must_belong_to_issue_or_merge_request CHECK ((((issue_id <> NULL::bigint) AND (merge_request_id IS NULL)) OR ((merge_request_id <> NULL::bigint) AND (issue_id IS NULL))))
+);
+
+CREATE SEQUENCE public.resource_state_events_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE public.resource_state_events_id_seq OWNED BY public.resource_state_events.id;
+
CREATE TABLE public.resource_weight_events (
id bigint NOT NULL,
user_id bigint NOT NULL,
@@ -7530,6 +7549,8 @@ ALTER TABLE ONLY public.resource_label_events ALTER COLUMN id SET DEFAULT nextva
ALTER TABLE ONLY public.resource_milestone_events ALTER COLUMN id SET DEFAULT nextval('public.resource_milestone_events_id_seq'::regclass);
+ALTER TABLE ONLY public.resource_state_events ALTER COLUMN id SET DEFAULT nextval('public.resource_state_events_id_seq'::regclass);
+
ALTER TABLE ONLY public.resource_weight_events ALTER COLUMN id SET DEFAULT nextval('public.resource_weight_events_id_seq'::regclass);
ALTER TABLE ONLY public.reviews ALTER COLUMN id SET DEFAULT nextval('public.reviews_id_seq'::regclass);
@@ -8422,6 +8443,9 @@ ALTER TABLE ONLY public.resource_label_events
ALTER TABLE ONLY public.resource_milestone_events
ADD CONSTRAINT resource_milestone_events_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY public.resource_state_events
+ ADD CONSTRAINT resource_state_events_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY public.resource_weight_events
ADD CONSTRAINT resource_weight_events_pkey PRIMARY KEY (id);
@@ -10197,6 +10221,12 @@ CREATE INDEX index_resource_milestone_events_on_milestone_id ON public.resource_
CREATE INDEX index_resource_milestone_events_on_user_id ON public.resource_milestone_events USING btree (user_id);
+CREATE INDEX index_resource_state_events_on_issue_id_and_created_at ON public.resource_state_events USING btree (issue_id, created_at);
+
+CREATE INDEX index_resource_state_events_on_merge_request_id ON public.resource_state_events USING btree (merge_request_id);
+
+CREATE INDEX index_resource_state_events_on_user_id ON public.resource_state_events USING btree (user_id);
+
CREATE INDEX index_resource_weight_events_on_issue_id_and_created_at ON public.resource_weight_events USING btree (issue_id, created_at);
CREATE INDEX index_resource_weight_events_on_issue_id_and_weight ON public.resource_weight_events USING btree (issue_id, weight);
@@ -11329,6 +11359,9 @@ ALTER TABLE ONLY public.lfs_file_locks
ALTER TABLE ONLY public.project_alerting_settings
ADD CONSTRAINT fk_rails_27a84b407d FOREIGN KEY (project_id) REFERENCES public.projects(id) ON DELETE CASCADE;
+ALTER TABLE ONLY public.resource_state_events
+ ADD CONSTRAINT fk_rails_29af06892a FOREIGN KEY (issue_id) REFERENCES public.issues(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.reviews
ADD CONSTRAINT fk_rails_29e6f859c4 FOREIGN KEY (author_id) REFERENCES public.users(id) ON DELETE SET NULL;
@@ -11347,6 +11380,9 @@ ALTER TABLE ONLY public.protected_branch_unprotect_access_levels
ALTER TABLE ONLY public.saml_providers
ADD CONSTRAINT fk_rails_306d459be7 FOREIGN KEY (group_id) REFERENCES public.namespaces(id) ON DELETE CASCADE;
+ALTER TABLE ONLY public.resource_state_events
+ ADD CONSTRAINT fk_rails_3112bba7dc FOREIGN KEY (merge_request_id) REFERENCES public.merge_requests(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.merge_request_diff_commits
ADD CONSTRAINT fk_rails_316aaceda3 FOREIGN KEY (merge_request_diff_id) REFERENCES public.merge_request_diffs(id) ON DELETE CASCADE;
@@ -12148,6 +12184,9 @@ ALTER TABLE ONLY public.insights
ALTER TABLE ONLY public.board_group_recent_visits
ADD CONSTRAINT fk_rails_f410736518 FOREIGN KEY (group_id) REFERENCES public.namespaces(id) ON DELETE CASCADE;
+ALTER TABLE ONLY public.resource_state_events
+ ADD CONSTRAINT fk_rails_f5827a7ccd FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE SET NULL;
+
ALTER TABLE ONLY public.design_user_mentions
ADD CONSTRAINT fk_rails_f7075a53c1 FOREIGN KEY (design_id) REFERENCES public.design_management_designs(id) ON DELETE CASCADE;
@@ -13281,6 +13320,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200406100909
20200406102111
20200406102120
+20200406132529
20200406135648
20200406141452
20200406192059
@@ -13326,5 +13366,9 @@ COPY "schema_migrations" (version) FROM STDIN;
20200416120354
20200417044453
20200421233150
+20200423075720
+20200423080334
+20200423080607
+20200423081409
\.
diff --git a/doc/administration/geo/replication/index.md b/doc/administration/geo/replication/index.md
index 7c661abef9a..5a9befc2232 100644
--- a/doc/administration/geo/replication/index.md
+++ b/doc/administration/geo/replication/index.md
@@ -245,7 +245,7 @@ This list of limitations only reflects the latest version of GitLab. If you are
- Pushing directly to a **secondary** node redirects (for HTTP) or proxies (for SSH) the request to the **primary** node instead of [handling it directly](https://gitlab.com/gitlab-org/gitlab/issues/1381), except when using Git over HTTP with credentials embedded within the URI. For example, `https://user:password@secondary.tld`.
- Cloning, pulling, or pushing repositories that exist on the **primary** node but not on the **secondary** nodes where [selective synchronization](configuration.md#selective-synchronization) does not include the project is not supported over SSH [but support is planned](https://gitlab.com/groups/gitlab-org/-/epics/2562). HTTP(S) is supported.
-- The **primary** node has to be online for OAuth login to happen. Existing sessions and Git are not affected.
+- The **primary** node has to be online for OAuth login to happen. Existing sessions and Git are not affected. Support for the **secondary** node to use an OAuth provider independent from the primary is [being planned](https://gitlab.com/gitlab-org/gitlab/issues/208465).
- The installation takes multiple manual steps that together can take about an hour depending on circumstances. We are working on improving this experience. See [Omnibus GitLab issue #2978](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2978) for details.
- Real-time updates of issues/merge requests (for example, via long polling) doesn't work on the **secondary** node.
- [Selective synchronization](configuration.md#selective-synchronization) applies only to files and repositories. Other datasets are replicated to the **secondary** node in full, making it inappropriate for use as an access control mechanism.
diff --git a/doc/administration/high_availability/consul.md b/doc/administration/high_availability/consul.md
index 6762a81f671..acd1906eb7b 100644
--- a/doc/administration/high_availability/consul.md
+++ b/doc/administration/high_availability/consul.md
@@ -4,7 +4,10 @@ type: reference
# Working with the bundled Consul service **(PREMIUM ONLY)**
-As part of its High Availability stack, GitLab Premium includes a bundled version of [Consul](https://www.consul.io/) that can be managed through `/etc/gitlab/gitlab.rb`.
+As part of its High Availability stack, GitLab Premium includes a bundled version of [Consul](https://www.consul.io/) that can be managed through `/etc/gitlab/gitlab.rb`. Consul is a service networking solution. When it comes to [GitLab Architecture](../../development/architecture.md), Consul utilization is supported for configuring:
+
+1. [Monitoring in Scaled and Highly Available environments](monitoring_node.md)
+1. [PostgreSQL High Availability with Omnibus](database.md#high-availability-with-gitlab-omnibus-premium-only)
A Consul cluster consists of multiple server agents, as well as client agents that run on other nodes which need to talk to the Consul cluster.
diff --git a/doc/api/epics.md b/doc/api/epics.md
index bf6a18fcedc..7cdf6c2729b 100644
--- a/doc/api/epics.md
+++ b/doc/api/epics.md
@@ -1,5 +1,8 @@
# Epics API **(PREMIUM)**
+> - Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.2.
+> - Single-level Epics [were moved](https://gitlab.com/gitlab-org/gitlab/issues/37081) to [GitLab Premium](https://about.gitlab.com/pricing/) in 12.8.
+
Every API call to epic must be authenticated.
If a user is not a member of a group and the group is private, a `GET` request on that group will result to a `404` status code.
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 749461b5e9e..3522a5a50eb 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -4399,6 +4399,16 @@ enum IssueSort {
DUE_DATE_DESC
"""
+ Priority by ascending order
+ """
+ PRIORITY_ASC
+
+ """
+ Priority by descending order
+ """
+ PRIORITY_DESC
+
+ """
Relative position by ascending order
"""
RELATIVE_POSITION_ASC
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 70f9a53244e..4c8fb099faa 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -12547,6 +12547,18 @@
"deprecationReason": null
},
{
+ "name": "PRIORITY_ASC",
+ "description": "Priority by ascending order",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "PRIORITY_DESC",
+ "description": "Priority by descending order",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "DUE_DATE_ASC",
"description": "Due date by ascending order",
"isDeprecated": false,
diff --git a/doc/development/go_guide/index.md b/doc/development/go_guide/index.md
index c1dfb220df8..48135c14b9b 100644
--- a/doc/development/go_guide/index.md
+++ b/doc/development/go_guide/index.md
@@ -70,8 +70,14 @@ projects:
- Avoid global variables, even in packages. By doing so you will introduce side
effects if the package is included multiple times.
-- Use `go fmt` before committing ([Gofmt](https://golang.org/cmd/gofmt/) is a
- tool that automatically formats Go source code).
+- Use `goimports` before committing.
+ [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports)
+ is a tool that automatically formats Go source code using
+ [Gofmt](https://golang.org/cmd/gofmt/), in addition to formatting import lines,
+ adding missing ones and removing unreferenced ones.
+
+ Most editors/IDEs will allow you to run commands before/after saving a file, you can set it
+ up to run `goimports` so that it's applied to every file when saving.
- Place private methods below the first caller method in the source file.
### Automatic linting
diff --git a/doc/development/permissions.md b/doc/development/permissions.md
index bca137337fc..0772389bf9e 100644
--- a/doc/development/permissions.md
+++ b/doc/development/permissions.md
@@ -9,9 +9,9 @@ anything that deals with permissions, all of them should be considered.
Groups and projects can have the following visibility levels:
-- public (20) - an entity is visible to everyone
-- internal (10) - an entity is visible to logged in users
-- private (0) - an entity is visible only to the approved members of the entity
+- public (`20`) - an entity is visible to everyone
+- internal (`10`) - an entity is visible to logged in users
+- private (`0`) - an entity is visible only to the approved members of the entity
The visibility level of a group can be changed only if all subgroups and
subprojects have the same or lower visibility level. (e.g., a group can be set
diff --git a/doc/user/group/epics/index.md b/doc/user/group/epics/index.md
index 024f346ad47..d0962dbc65f 100644
--- a/doc/user/group/epics/index.md
+++ b/doc/user/group/epics/index.md
@@ -5,7 +5,7 @@ type: reference, howto
# Epics **(PREMIUM)**
> - Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.2.
-> - In [GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/issues/37081), single-level Epics were moved to the Premium tier.
+> - Single-level Epics [were moved](https://gitlab.com/gitlab-org/gitlab/issues/37081) to [GitLab Premium](https://about.gitlab.com/pricing/) in 12.8.
Epics let you manage your portfolio of projects more efficiently and with less
effort by tracking groups of issues that share a theme, across projects and
diff --git a/doc/user/project/code_owners.md b/doc/user/project/code_owners.md
index 49083be77dc..c9129ef3269 100644
--- a/doc/user/project/code_owners.md
+++ b/doc/user/project/code_owners.md
@@ -9,6 +9,33 @@ in [GitLab Starter](https://about.gitlab.com/pricing/) 11.3.
> - [Support for group namespaces](https://gitlab.com/gitlab-org/gitlab-foss/issues/53182) added in GitLab Starter 12.1.
> - Code Owners for Merge Request approvals was [introduced](https://gitlab.com/gitlab-org/gitlab/issues/4418) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.9.
+## Introduction
+
+When contributing to a project, it can often be difficult
+to find out who should review or approve merge requests.
+Additionally, if you have a question over a specific file or
+code block, it may be difficult to know who to find the answer from.
+
+GitLab Code Owners is a feature to define who owns specific
+files or paths in a repository, allowing other users to understand
+who is responsible for each file or path.
+
+## Why is this useful?
+
+Code Owners allows for a version controlled single source of
+truth file outlining the exact GitLab users or groups that
+own certain files or paths in a repository. Code Owners can be
+utilized in the merge request approval process which can streamline
+the process of finding the right reviewers and approvers for a given
+merge request.
+
+In larger organizations or popular open source projects, Code Owners
+can also be useful to understand who to contact if you have
+a question that may not be related to code review or a merge request
+approval.
+
+## How to set up Code Owners
+
You can use a `CODEOWNERS` file to specify users or
[shared groups](members/share_project_with_groups.md)
that are responsible for certain files in a repository.
@@ -41,7 +68,7 @@ The user that would show for `README.md` would be `@user2`.
## Approvals by Code Owners
Once you've set Code Owners to a project, you can configure it to
-receive approvals:
+be used for merge request approvals:
- As [merge request eligible approvers](merge_requests/merge_request_approvals.md#code-owners-as-eligible-approvers).
- As required approvers for [protected branches](protected_branches.md#protected-branches-approval-by-code-owners-premium). **(PREMIUM)**
diff --git a/doc/user/project/merge_requests/code_quality.md b/doc/user/project/merge_requests/code_quality.md
index a7e712a4c0a..887ed3e0cb9 100644
--- a/doc/user/project/merge_requests/code_quality.md
+++ b/doc/user/project/merge_requests/code_quality.md
@@ -27,7 +27,19 @@ in the merge request widget area:
![Code Quality Widget](img/code_quality.png)
-For more information, see the Code Climate list of [Supported Languages for Maintainability](https://docs.codeclimate.com/docs/supported-languages-for-maintainability).
+Watch a quick walkthrough of Code Quality in action:
+
+<div class="video-fallback">
+ See the video: <a href="https://www.youtube.com/watch?v=B32LxtJKo9M">Video title</a>.
+</div>
+<figure class="video-container">
+ <iframe src="https://www.youtube.com/embed/B32LxtJKo9M" frameborder="0" allowfullscreen="true"> </iframe>
+</figure>
+
+NOTE: **Note:**
+For one customer, the auditor found that having Code Quality, SAST, and Container Scanning all automated in GitLab CI/CD was almost better than a manual review! [Read more](https://about.gitlab.com/customers/bi_worldwide/).
+
+See also the Code Climate list of [Supported Languages for Maintainability](https://docs.codeclimate.com/docs/supported-languages-for-maintainability).
## Use cases
diff --git a/doc/user/project/merge_requests/img/code_quality.png b/doc/user/project/merge_requests/img/code_quality.png
index a20f6476fb8..3c6c92baad2 100644
--- a/doc/user/project/merge_requests/img/code_quality.png
+++ b/doc/user/project/merge_requests/img/code_quality.png
Binary files differ
diff --git a/doc/user/project/repository/file_finder.md b/doc/user/project/repository/file_finder.md
index 1361053dac3..3a923ff32a8 100644
--- a/doc/user/project/repository/file_finder.md
+++ b/doc/user/project/repository/file_finder.md
@@ -12,7 +12,7 @@ GitLab UI.
You can find the **Find File** button when in the **Files** section of a
project.
-![Find file button](img/file_finder_find_button.png)
+![Find file button](img/file_finder_find_button_v12_10.png)
For those who prefer to keep their fingers on the keyboard, there is a
[shortcut button](../../shortcuts.md) as well, which you can invoke from _anywhere_
@@ -32,11 +32,11 @@ The File finder feature is powered by the [Fuzzy filter](https://github.com/jean
It implements a fuzzy search with highlight, and tries to provide intuitive
results by recognizing patterns that people use while searching.
-For example, consider the [GitLab CE repository](https://gitlab.com/gitlab-org/gitlab-foss/tree/master) and that we want to open
+For example, consider the [GitLab FOSS repository](https://gitlab.com/gitlab-org/gitlab-foss/tree/master) and that we want to open
the `app/controllers/admin/deploy_keys_controller.rb` file.
Using fuzzy search, we start by typing letters that get us closer to the file.
**Tip:** To narrow down your search, include `/` in your search terms.
-![Find file button](img/file_finder_find_file.png)
+![Find file button](img/file_finder_find_file_v12_10.png)
diff --git a/doc/user/project/repository/img/file_finder_find_button.png b/doc/user/project/repository/img/file_finder_find_button.png
deleted file mode 100644
index 0c2d7d7bc73..00000000000
--- a/doc/user/project/repository/img/file_finder_find_button.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/repository/img/file_finder_find_button_v12_10.png b/doc/user/project/repository/img/file_finder_find_button_v12_10.png
new file mode 100644
index 00000000000..e93db946005
--- /dev/null
+++ b/doc/user/project/repository/img/file_finder_find_button_v12_10.png
Binary files differ
diff --git a/doc/user/project/repository/img/file_finder_find_file.png b/doc/user/project/repository/img/file_finder_find_file.png
deleted file mode 100644
index c2212c7cd9e..00000000000
--- a/doc/user/project/repository/img/file_finder_find_file.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/repository/img/file_finder_find_file_v12_10.png b/doc/user/project/repository/img/file_finder_find_file_v12_10.png
new file mode 100644
index 00000000000..1404ccc6d0b
--- /dev/null
+++ b/doc/user/project/repository/img/file_finder_find_file_v12_10.png
Binary files differ
diff --git a/doc/user/project/requirements/index.md b/doc/user/project/requirements/index.md
index 8f4ec7bbbed..50343e52a68 100644
--- a/doc/user/project/requirements/index.md
+++ b/doc/user/project/requirements/index.md
@@ -10,6 +10,9 @@ Requirements allow you to create criteria to check your products against. They
can be based on users, stakeholders, system, software, or anything else you
find important to capture.
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
+For an overview, see [GitLab 12.10 Introduces Requirements Management](https://www.youtube.com/watch?v=uSS7oUNSEoU).
+
![requirements list view](img/requirements_list_view_v12_10.png)
## Create a requirement
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index b235caaa6a2..610c504bfee 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -12521,6 +12521,9 @@ msgstr ""
msgid "Manage your license"
msgstr ""
+msgid "Managed Account"
+msgstr ""
+
msgid "Manifest"
msgstr ""
@@ -20981,6 +20984,9 @@ msgstr ""
msgid "This application will be able to:"
msgstr ""
+msgid "This attachment has been truncated to avoid exceeding the maximum allowed attachment size of 15MB. %{written_count} of %{issues_count} issues have been included. Consider re-exporting with a narrower selection of issues."
+msgstr ""
+
msgid "This block is self-referential"
msgstr ""
@@ -24138,6 +24144,15 @@ msgstr ""
msgid "Your %{strong}%{plan_name}%{strong_close} subscription will expire on %{strong}%{expires_on}%{strong_close}. After that, you will not to be able to create issues or merge requests as well as many other features."
msgstr ""
+msgid "Your CSV export has started. It will be emailed to %{email} when complete."
+msgstr ""
+
+msgid "Your CSV export of %{issues_count} from project %{project_link} has been added to this email as an attachment."
+msgstr ""
+
+msgid "Your CSV export of %{written_count} from project %{project_name} (%{project_url}) has been added to this email as an attachment."
+msgstr ""
+
msgid "Your Commit Email will be used for web based operations, such as edits and merges."
msgstr ""
diff --git a/qa/spec/factory/resource/user_spec.rb b/qa/spec/factory/resource/user_spec.rb
index 820c506b715..d59ee24c758 100644
--- a/qa/spec/factory/resource/user_spec.rb
+++ b/qa/spec/factory/resource/user_spec.rb
@@ -2,7 +2,7 @@
describe QA::Resource::User do
describe "#fabricate_via_api!" do
- Response = Struct.new(:code, :body)
+ response = Struct.new(:code, :body)
it 'fetches an existing user' do
existing_users = [
@@ -13,8 +13,8 @@ describe QA::Resource::User do
web_url: ''
}
]
- users_response = Response.new('200', JSON.dump(existing_users))
- single_user_response = Response.new('200', JSON.dump(existing_users.first))
+ users_response = response.new('200', JSON.dump(existing_users))
+ single_user_response = response.new('200', JSON.dump(existing_users.first))
expect(subject).to receive(:api_get_from).with("/users?username=name").and_return(users_response)
expect(subject).to receive(:api_get_from).with("/users/0").and_return(single_user_response)
@@ -26,7 +26,7 @@ describe QA::Resource::User do
end
it 'tries to create a user if it does not exist' do
- expect(subject).to receive(:api_get_from).with("/users?username=foo").and_return(Response.new('200', '[]'))
+ expect(subject).to receive(:api_get_from).with("/users?username=foo").and_return(response.new('200', '[]'))
expect(subject).to receive(:api_post).and_return({ web_url: '' })
subject.username = 'foo'
diff --git a/qa/spec/git/repository_spec.rb b/qa/spec/git/repository_spec.rb
index 6cca9f55e11..0559fabbfd5 100644
--- a/qa/spec/git/repository_spec.rb
+++ b/qa/spec/git/repository_spec.rb
@@ -69,20 +69,20 @@ describe QA::Git::Repository do
end
describe '#fetch_supported_git_protocol' do
- Result = Struct.new(:response)
+ result = Struct.new(:response)
it "reports the detected version" do
- expect(repository).to receive(:run).and_return(Result.new("packet: git< version 2"))
+ expect(repository).to receive(:run).and_return(result.new("packet: git< version 2"))
expect(repository.fetch_supported_git_protocol).to eq('2')
end
it 'reports unknown if version is unknown' do
- expect(repository).to receive(:run).and_return(Result.new("packet: git< version -1"))
+ expect(repository).to receive(:run).and_return(result.new("packet: git< version -1"))
expect(repository.fetch_supported_git_protocol).to eq('unknown')
end
it 'reports unknown if content does not identify a version' do
- expect(repository).to receive(:run).and_return(Result.new("foo"))
+ expect(repository).to receive(:run).and_return(result.new("foo"))
expect(repository.fetch_supported_git_protocol).to eq('unknown')
end
end
diff --git a/spec/controllers/projects/alert_management_controller_spec.rb b/spec/controllers/projects/alert_management_controller_spec.rb
new file mode 100644
index 00000000000..5321632875e
--- /dev/null
+++ b/spec/controllers/projects/alert_management_controller_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::AlertManagementController do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:role) { :reporter }
+ let_it_be(:user) { create(:user) }
+
+ before do
+ project.add_role(user, role)
+ sign_in(user)
+ end
+
+ describe 'GET #index' do
+ context 'when alert_management_minimal is enabled' do
+ before do
+ stub_feature_flags(alert_management_minimal: true)
+ end
+
+ it 'shows the page' do
+ get :index, params: { namespace_id: project.namespace, project_id: project }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when alert_management_minimal is disabled' do
+ before do
+ stub_feature_flags(alert_management_minimal: false)
+ end
+
+ it 'shows 404' do
+ get :index, params: { namespace_id: project.namespace, project_id: project }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+end
diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb
index 53e0a9e3724..3dd4bb31e94 100644
--- a/spec/graphql/resolvers/issues_resolver_spec.rb
+++ b/spec/graphql/resolvers/issues_resolver_spec.rb
@@ -125,12 +125,12 @@ describe Resolvers::IssuesResolver do
end
context 'when sorting by due date' do
- let(:project) { create(:project) }
+ let_it_be(:project) { create(:project) }
- let!(:due_issue1) { create(:issue, project: project, due_date: 3.days.from_now) }
- let!(:due_issue2) { create(:issue, project: project, due_date: nil) }
- let!(:due_issue3) { create(:issue, project: project, due_date: 2.days.ago) }
- let!(:due_issue4) { create(:issue, project: project, due_date: nil) }
+ let_it_be(:due_issue1) { create(:issue, project: project, due_date: 3.days.from_now) }
+ let_it_be(:due_issue2) { create(:issue, project: project, due_date: nil) }
+ let_it_be(:due_issue3) { create(:issue, project: project, due_date: 2.days.ago) }
+ let_it_be(:due_issue4) { create(:issue, project: project, due_date: nil) }
it 'sorts issues ascending' do
expect(resolve_issues(sort: :due_date_asc)).to eq [due_issue3, due_issue1, due_issue4, due_issue2]
@@ -142,17 +142,38 @@ describe Resolvers::IssuesResolver do
end
context 'when sorting by relative position' do
- let(:project) { create(:project) }
+ let_it_be(:project) { create(:project) }
- let!(:relative_issue1) { create(:issue, project: project, relative_position: 2000) }
- let!(:relative_issue2) { create(:issue, project: project, relative_position: nil) }
- let!(:relative_issue3) { create(:issue, project: project, relative_position: 1000) }
- let!(:relative_issue4) { create(:issue, project: project, relative_position: nil) }
+ let_it_be(:relative_issue1) { create(:issue, project: project, relative_position: 2000) }
+ let_it_be(:relative_issue2) { create(:issue, project: project, relative_position: nil) }
+ let_it_be(:relative_issue3) { create(:issue, project: project, relative_position: 1000) }
+ let_it_be(:relative_issue4) { create(:issue, project: project, relative_position: nil) }
it 'sorts issues ascending' do
expect(resolve_issues(sort: :relative_position_asc)).to eq [relative_issue3, relative_issue1, relative_issue4, relative_issue2]
end
end
+
+ context 'when sorting by priority' do
+ let_it_be(:project) { create(:project) }
+
+ let_it_be(:early_milestone) { create(:milestone, project: project, due_date: 10.days.from_now) }
+ let_it_be(:late_milestone) { create(:milestone, project: project, due_date: 30.days.from_now) }
+ let_it_be(:label_1) { create(:label, project: project, priority: 1) }
+ let_it_be(:label_2) { create(:label, project: project, priority: 5) }
+ let_it_be(:issue1) { create(:issue, project: project, labels: [label_1], milestone: late_milestone) }
+ let_it_be(:issue2) { create(:issue, project: project, labels: [label_2]) }
+ let_it_be(:issue3) { create(:issue, project: project, milestone: early_milestone) }
+ let_it_be(:issue4) { create(:issue, project: project) }
+
+ it 'sorts issues ascending' do
+ expect(resolve_issues(sort: :priority_asc).items).to eq([issue3, issue1, issue2, issue4])
+ end
+
+ it 'sorts issues descending' do
+ expect(resolve_issues(sort: :priority_desc).items).to eq([issue1, issue3, issue2, issue4])
+ end
+ end
end
it 'returns issues user can see' do
diff --git a/spec/graphql/types/issuable_sort_enum_spec.rb b/spec/graphql/types/issuable_sort_enum_spec.rb
new file mode 100644
index 00000000000..feb318df66d
--- /dev/null
+++ b/spec/graphql/types/issuable_sort_enum_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Types::IssuableSortEnum do
+ it { expect(described_class.graphql_name).to eq('IssuableSort') }
+
+ it 'exposes all the existing issuable sort values' do
+ expect(described_class.values.keys).to include(*%w[PRIORITY_ASC PRIORITY_DESC])
+ end
+end
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 73b81b2225a..05d3329215a 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -751,4 +751,48 @@ describe CommitStatus do
it { is_expected.to be_a(CommitStatusPresenter) }
end
+
+ describe '#recoverable?' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:commit_status) { create(:commit_status, :pending) }
+
+ subject(:recoverable?) { commit_status.recoverable? }
+
+ context 'when commit status is failed' do
+ before do
+ commit_status.drop!
+ end
+
+ where(:failure_reason, :recoverable) do
+ :script_failure | false
+ :missing_dependency_failure | false
+ :archived_failure | false
+ :scheduler_failure | false
+ :data_integrity_failure | false
+ :unknown_failure | true
+ :api_failure | true
+ :stuck_or_timeout_failure | true
+ :runner_system_failure | true
+ end
+
+ with_them do
+ context "when failure reason is #{params[:failure_reason]}" do
+ before do
+ commit_status.update_attribute(:failure_reason, failure_reason)
+ end
+
+ it { is_expected.to eq(recoverable) }
+ end
+ end
+ end
+
+ context 'when commit status is not failed' do
+ before do
+ commit_status.success!
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
end
diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb
index b6c47f40ceb..9cf6eb45c63 100644
--- a/spec/presenters/ci/build_presenter_spec.rb
+++ b/spec/presenters/ci/build_presenter_spec.rb
@@ -264,30 +264,4 @@ describe Ci::BuildPresenter do
expect(description).to eq('There has been an API failure, please try again')
end
end
-
- describe '#recoverable?' do
- let(:build) { create(:ci_build, :failed, :script_failure) }
-
- context 'when is a script or missing dependency failure' do
- let(:failure_reasons) { %w(script_failure missing_dependency_failure archived_failure scheduler_failure data_integrity_failure) }
-
- it 'returns false' do
- failure_reasons.each do |failure_reason|
- build.update_attribute(:failure_reason, failure_reason)
- expect(presenter.recoverable?).to be_falsy
- end
- end
- end
-
- context 'when is any other failure type' do
- let(:failure_reasons) { %w(unknown_failure api_failure stuck_or_timeout_failure runner_system_failure) }
-
- it 'returns true' do
- failure_reasons.each do |failure_reason|
- build.update_attribute(:failure_reason, failure_reason)
- expect(presenter.recoverable?).to be_truthy
- end
- end
- end
- end
end
diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb
index 4ce7a3912a3..ae21951008c 100644
--- a/spec/requests/api/graphql/project/issues_spec.rb
+++ b/spec/requests/api/graphql/project/issues_spec.rb
@@ -45,8 +45,8 @@ describe 'getting an issue list for a project' do
it 'includes discussion locked' do
post_graphql(query, current_user: current_user)
- expect(issues_data[0]['node']['discussionLocked']).to eq false
- expect(issues_data[1]['node']['discussionLocked']).to eq true
+ expect(issues_data[0]['node']['discussionLocked']).to eq(false)
+ expect(issues_data[1]['node']['discussionLocked']).to eq(true)
end
context 'when limiting the number of results' do
@@ -79,7 +79,7 @@ describe 'getting an issue list for a project' do
post_graphql(query)
- expect(issues_data).to eq []
+ expect(issues_data).to eq([])
end
end
@@ -122,15 +122,15 @@ describe 'getting an issue list for a project' do
let(:end_cursor) { graphql_data['project']['issues']['pageInfo']['endCursor'] }
context 'when sorting by due date' do
- let(:sort_project) { create(:project, :public) }
+ let_it_be(:sort_project) { create(:project, :public) }
- let!(:due_issue1) { create(:issue, project: sort_project, due_date: 3.days.from_now) }
- let!(:due_issue2) { create(:issue, project: sort_project, due_date: nil) }
- let!(:due_issue3) { create(:issue, project: sort_project, due_date: 2.days.ago) }
- let!(:due_issue4) { create(:issue, project: sort_project, due_date: nil) }
- let!(:due_issue5) { create(:issue, project: sort_project, due_date: 1.day.ago) }
+ let_it_be(:due_issue1) { create(:issue, project: sort_project, due_date: 3.days.from_now) }
+ let_it_be(:due_issue2) { create(:issue, project: sort_project, due_date: nil) }
+ let_it_be(:due_issue3) { create(:issue, project: sort_project, due_date: 2.days.ago) }
+ let_it_be(:due_issue4) { create(:issue, project: sort_project, due_date: nil) }
+ let_it_be(:due_issue5) { create(:issue, project: sort_project, due_date: 1.day.ago) }
- let(:params) { 'sort: DUE_DATE_ASC' }
+ let_it_be(:params) { 'sort: DUE_DATE_ASC' }
def query(issue_params = params)
graphql_query_for(
@@ -160,20 +160,20 @@ describe 'getting an issue list for a project' do
context 'when ascending' do
it 'sorts issues' do
- expect(grab_iids).to eq [due_issue3.iid, due_issue5.iid, due_issue1.iid, due_issue4.iid, due_issue2.iid]
+ expect(grab_iids).to eq([due_issue3.iid, due_issue5.iid, due_issue1.iid, due_issue4.iid, due_issue2.iid])
end
context 'when paginating' do
let(:params) { 'sort: DUE_DATE_ASC, first: 2' }
it 'sorts issues' do
- expect(grab_iids).to eq [due_issue3.iid, due_issue5.iid]
+ expect(grab_iids).to eq([due_issue3.iid, due_issue5.iid])
cursored_query = query("sort: DUE_DATE_ASC, after: \"#{end_cursor}\"")
post_graphql(cursored_query, current_user: current_user)
response_data = JSON.parse(response.body)['data']['project']['issues']['edges']
- expect(grab_iids(response_data)).to eq [due_issue1.iid, due_issue4.iid, due_issue2.iid]
+ expect(grab_iids(response_data)).to eq([due_issue1.iid, due_issue4.iid, due_issue2.iid])
end
end
end
@@ -182,35 +182,35 @@ describe 'getting an issue list for a project' do
let(:params) { 'sort: DUE_DATE_DESC' }
it 'sorts issues' do
- expect(grab_iids).to eq [due_issue1.iid, due_issue5.iid, due_issue3.iid, due_issue4.iid, due_issue2.iid]
+ expect(grab_iids).to eq([due_issue1.iid, due_issue5.iid, due_issue3.iid, due_issue4.iid, due_issue2.iid])
end
context 'when paginating' do
let(:params) { 'sort: DUE_DATE_DESC, first: 2' }
it 'sorts issues' do
- expect(grab_iids).to eq [due_issue1.iid, due_issue5.iid]
+ expect(grab_iids).to eq([due_issue1.iid, due_issue5.iid])
cursored_query = query("sort: DUE_DATE_DESC, after: \"#{end_cursor}\"")
post_graphql(cursored_query, current_user: current_user)
response_data = JSON.parse(response.body)['data']['project']['issues']['edges']
- expect(grab_iids(response_data)).to eq [due_issue3.iid, due_issue4.iid, due_issue2.iid]
+ expect(grab_iids(response_data)).to eq([due_issue3.iid, due_issue4.iid, due_issue2.iid])
end
end
end
end
context 'when sorting by relative position' do
- let(:sort_project) { create(:project, :public) }
+ let_it_be(:sort_project) { create(:project, :public) }
- let!(:relative_issue1) { create(:issue, project: sort_project, relative_position: 2000) }
- let!(:relative_issue2) { create(:issue, project: sort_project, relative_position: nil) }
- let!(:relative_issue3) { create(:issue, project: sort_project, relative_position: 1000) }
- let!(:relative_issue4) { create(:issue, project: sort_project, relative_position: nil) }
- let!(:relative_issue5) { create(:issue, project: sort_project, relative_position: 500) }
+ let_it_be(:relative_issue1) { create(:issue, project: sort_project, relative_position: 2000) }
+ let_it_be(:relative_issue2) { create(:issue, project: sort_project, relative_position: nil) }
+ let_it_be(:relative_issue3) { create(:issue, project: sort_project, relative_position: 1000) }
+ let_it_be(:relative_issue4) { create(:issue, project: sort_project, relative_position: nil) }
+ let_it_be(:relative_issue5) { create(:issue, project: sort_project, relative_position: 500) }
- let(:params) { 'sort: RELATIVE_POSITION_ASC' }
+ let_it_be(:params) { 'sort: RELATIVE_POSITION_ASC' }
def query(issue_params = params)
graphql_query_for(
@@ -228,20 +228,91 @@ describe 'getting an issue list for a project' do
context 'when ascending' do
it 'sorts issues' do
- expect(grab_iids).to eq [relative_issue5.iid, relative_issue3.iid, relative_issue1.iid, relative_issue4.iid, relative_issue2.iid]
+ expect(grab_iids).to eq([relative_issue5.iid, relative_issue3.iid, relative_issue1.iid, relative_issue4.iid, relative_issue2.iid])
end
context 'when paginating' do
let(:params) { 'sort: RELATIVE_POSITION_ASC, first: 2' }
it 'sorts issues' do
- expect(grab_iids).to eq [relative_issue5.iid, relative_issue3.iid]
+ expect(grab_iids).to eq([relative_issue5.iid, relative_issue3.iid])
cursored_query = query("sort: RELATIVE_POSITION_ASC, after: \"#{end_cursor}\"")
post_graphql(cursored_query, current_user: current_user)
response_data = JSON.parse(response.body)['data']['project']['issues']['edges']
- expect(grab_iids(response_data)).to eq [relative_issue1.iid, relative_issue4.iid, relative_issue2.iid]
+ expect(grab_iids(response_data)).to eq([relative_issue1.iid, relative_issue4.iid, relative_issue2.iid])
+ end
+ end
+ end
+ end
+
+ context 'when sorting by priority' do
+ let_it_be(:sort_project) { create(:project, :public) }
+
+ let_it_be(:early_milestone) { create(:milestone, project: sort_project, due_date: 10.days.from_now) }
+ let_it_be(:late_milestone) { create(:milestone, project: sort_project, due_date: 30.days.from_now) }
+ let_it_be(:label_1) { create(:label, project: sort_project, priority: 1) }
+ let_it_be(:label_2) { create(:label, project: sort_project, priority: 5) }
+ let_it_be(:issue1) { create(:issue, project: sort_project, labels: [label_1], milestone: late_milestone) }
+ let_it_be(:issue2) { create(:issue, project: sort_project, labels: [label_2]) }
+ let_it_be(:issue3) { create(:issue, project: sort_project, milestone: early_milestone) }
+ let_it_be(:issue4) { create(:issue, project: sort_project) }
+
+ let_it_be(:params) { 'sort: PRIORITY_ASC' }
+
+ def query(issue_params = params)
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => sort_project.full_path },
+ "issues(#{issue_params}) { pageInfo { endCursor} edges { node { iid dueDate } } }"
+ )
+ end
+
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ context 'when ascending' do
+ it 'sorts issues' do
+ expect(grab_iids).to eq([issue3.iid, issue1.iid, issue2.iid, issue4.iid])
+ end
+
+ context 'when paginating' do
+ let(:params) { 'sort: PRIORITY_ASC, first: 2' }
+
+ it 'sorts issues' do
+ expect(grab_iids).to eq([issue3.iid, issue1.iid])
+
+ cursored_query = query("sort: PRIORITY_ASC, after: \"#{end_cursor}\"")
+ post_graphql(cursored_query, current_user: current_user)
+ response_data = JSON.parse(response.body)['data']['project']['issues']['edges']
+
+ expect(grab_iids(response_data)).to eq([issue2.iid, issue4.iid])
+ end
+ end
+ end
+
+ context 'when descending' do
+ let(:params) { 'sort: PRIORITY_DESC' }
+
+ it 'sorts issues' do
+ expect(grab_iids).to eq([issue1.iid, issue3.iid, issue2.iid, issue4.iid])
+ end
+
+ context 'when paginating' do
+ let(:params) { 'sort: PRIORITY_DESC, first: 2' }
+
+ it 'sorts issues' do
+ expect(grab_iids).to eq([issue1.iid, issue3.iid])
+
+ cursored_query = query("sort: PRIORITY_DESC, after: \"#{end_cursor}\"")
+ post_graphql(cursored_query, current_user: current_user)
+ response_data = JSON.parse(response.body)['data']['project']['issues']['edges']
+
+ expect(grab_iids(response_data)).to eq([issue2.iid, issue4.iid])
end
end
end
diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
index cd622807c09..41bb5f55453 100644
--- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -136,27 +136,55 @@ describe 'layouts/nav/sidebar/_project' do
end
describe 'operations settings tab' do
- before do
- project.update!(archived: project_archived)
- end
+ describe 'archive projects' do
+ before do
+ project.update!(archived: project_archived)
+ end
- context 'when project is archived' do
- let(:project_archived) { true }
+ context 'when project is archived' do
+ let(:project_archived) { true }
- it 'does not show the operations settings tab' do
- render
+ it 'does not show the operations settings tab' do
+ render
+
+ expect(rendered).not_to have_link('Operations', href: project_settings_operations_path(project))
+ end
+ end
- expect(rendered).not_to have_link('Operations', href: project_settings_operations_path(project))
+ context 'when project is active' do
+ let(:project_archived) { false }
+
+ it 'shows the operations settings tab' do
+ render
+
+ expect(rendered).to have_link('Operations', href: project_settings_operations_path(project))
+ end
end
end
- context 'when project is active' do
- let(:project_archived) { false }
+ describe 'Alert Management' do
+ context 'when alert_management_minimal is enabled' do
+ before do
+ stub_feature_flags(alert_management_minimal: true)
+ end
- it 'shows the operations settings tab' do
- render
+ it 'shows the Alerts sidebar entry' do
+ render
+
+ expect(rendered).to have_css('a[title="Alerts"]')
+ end
+ end
+
+ context 'when alert_management_minimal is disabled' do
+ before do
+ stub_feature_flags(alert_management_minimal: false)
+ end
+
+ it 'does not show the Alerts sidebar entry' do
+ render
- expect(rendered).to have_link('Operations', href: project_settings_operations_path(project))
+ expect(rendered).to have_no_css('a[title="Alerts"]')
+ end
end
end
end