Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-04-05 12:10:23 +00:00
parent b5e513dbef
commit 7e964f54ed
84 changed files with 400 additions and 220 deletions

View file

@ -213,13 +213,6 @@ RSpec/ReturnFromStub:
RSpec/ScatteredLet:
Enabled: false
# Offense count: 10
# Configuration parameters: EnforcedStyle.
# SupportedStyles: symbols, strings
RSpec/VariableDefinition:
Exclude:
- 'spec/initializers/mail_encoding_patch_spec.rb'
# Offense count: 24
# Configuration parameters: EnforcedStyle, IgnoredPatterns.
# SupportedStyles: snake_case, camelCase

View file

@ -170,7 +170,6 @@ export default {
<note-form
v-if="diffFileCommentForm"
ref="noteForm"
:is-editing="false"
:save-button-title="__('Comment')"
class="diff-comment-form new-note discussion-form discussion-form-container"
@handleFormUpdateAddToReview="addToReview"

View file

@ -221,7 +221,6 @@ export default {
</div>
<note-form
ref="noteForm"
:is-editing="false"
:line-code="line.line_code"
:line="line"
:lines="commentLines"

View file

@ -72,7 +72,7 @@ export default {
</script>
<template>
<div ref="milestoneDetails" class="issue-milestone-details">
<gl-icon :size="16" class="gl-mr-2" name="clock" />
<gl-icon :size="16" class="gl-mr-2 flex-shrink-0" name="clock" />
<span class="milestone-title d-inline-block">{{ milestone.title }}</span>
<gl-tooltip :target="() => $refs.milestoneDetails" placement="bottom" class="js-item-milestone">
<span class="bold">{{ __('Milestone') }}</span> <br />

View file

@ -32,7 +32,7 @@ export default {
</script>
<template>
<small class="edited-text">
<small class="edited-text js-issue-widgets">
Edited
<time-ago-tooltip v-if="updatedAt" :time="updatedAt" tooltip-placement="bottom" />
<span v-if="hasUpdatedBy">

View file

@ -1,5 +1,6 @@
<script>
import markdownField from '~/vue_shared/components/markdown/field.vue';
import { helpPagePath } from '~/helpers/help_page_helper';
import updateMixin from '../../mixins/update';
export default {
@ -31,6 +32,11 @@ export default {
default: true,
},
},
computed: {
quickActionsDocsPath() {
return helpPagePath('user/project/quick_actions');
},
},
mounted() {
this.$refs.textarea.focus();
},
@ -43,6 +49,7 @@ export default {
<markdown-field
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:quick-actions-docs-path="quickActionsDocsPath"
:can-attach-file="canAttachFile"
:enable-autocomplete="enableAutocomplete"
:textarea-value="formState.description"

View file

@ -63,13 +63,32 @@ export default {
const { category, action } = trackIncidentDetailsViewsOptions;
Tracking.event(category, action);
},
handleTabChange(tabIndex) {
const parent = document.querySelector('.js-issue-details');
if (parent !== null) {
const itemsToHide = parent.querySelectorAll('.js-issue-widgets');
const lineSeparator = parent.querySelector('.js-detail-page-description');
lineSeparator.classList.toggle('gl-border-b-0', tabIndex > 0);
itemsToHide.forEach(function hide(item) {
item.classList.toggle('gl-display-none', tabIndex > 0);
});
}
},
},
};
</script>
<template>
<div>
<gl-tabs content-class="gl-reset-line-height" class="gl-mt-n3" data-testid="incident-tabs">
<gl-tabs
content-class="gl-reset-line-height"
class="gl-mt-n3"
data-testid="incident-tabs"
@input="handleTabChange"
>
<gl-tab :title="s__('Incident|Summary')">
<highlight-bar :alert="alert" />
<description-component v-bind="$attrs" />

View file

@ -9,7 +9,7 @@ const LINK_TAG_PATTERN = '[{text}](url)';
// a bullet point character (*+-) and an optional checkbox ([ ] [x])
// OR a number with a . after it and an optional checkbox ([ ] [x])
// followed by one or more whitespace characters
const LIST_LINE_HEAD_PATTERN = /^(?<indent>\s*)(?<leader>((?<isUl>[*+-])|(?<isOl>\d+\.))( \[([xX ])\])?\s)(?<content>.)?/;
const LIST_LINE_HEAD_PATTERN = /^(?<indent>\s*)(?<leader>((?<isUl>[*+-])|(?<isOl>\d+\.))( \[([xX\s])\])?\s)(?<content>.)?/;
// detect a horizontal rule that might be mistaken for a list item (not full pattern for an <hr>)
const HR_PATTERN = /^((\s{0,3}-+\s*-+\s*-+\s*[\s-]*)|(\s{0,3}\*+\s*\*+\s*\*+\s*[\s*]*))$/;

View file

@ -174,7 +174,6 @@ export default {
<note-form
v-if="isEditing"
ref="noteForm"
:is-editing="isEditing"
:note-body="noteBody"
:note-id="note.id"
:line="line"

View file

@ -41,10 +41,6 @@ export default {
required: false,
default: () => ({}),
},
isEditing: {
type: Boolean,
required: true,
},
lineCode: {
type: String,
required: false,
@ -184,7 +180,7 @@ export default {
return this.getNotesDataByProp('markdownDocsPath');
},
quickActionsDocsPath() {
return !this.isEditing ? this.getNotesDataByProp('quickActionsDocsPath') : undefined;
return this.getNotesDataByProp('quickActionsDocsPath');
},
currentUserId() {
return this.getUserDataByProp('id');
@ -348,7 +344,7 @@ export default {
ref="textarea"
v-model="updatedNoteBody"
:disabled="isSubmitting"
:data-supports-quick-actions="!isEditing"
data-supports-quick-actions="true"
name="note[note]"
class="note-textarea js-gfm-input js-note-text js-autosize markdown-area js-vue-issue-note-form"
data-qa-selector="reply_field"

View file

@ -307,7 +307,6 @@ export default {
v-if="isReplying"
ref="noteForm"
:discussion="discussion"
:is-editing="false"
:line="diffLine"
save-button-title="Comment"
:autosave-key="autosaveKey"

View file

@ -13,7 +13,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
before_action :disable_query_limiting, only: [:usage_data]
feature_category :not_owned, [
feature_category :not_owned, [ # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
:general, :reporting, :metrics_and_profiling, :network,
:preferences, :update, :reset_health_check_token
]

View file

@ -1,5 +1,5 @@
# frozen_string_literal: true
class Admin::BackgroundJobsController < Admin::ApplicationController
feature_category :not_owned
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
end

View file

@ -5,7 +5,7 @@ class Admin::DashboardController < Admin::ApplicationController
COUNTED_ITEMS = [Project, User, Group].freeze
feature_category :not_owned
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
# rubocop: disable CodeReuse/ActiveRecord
def index

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
class Admin::HealthCheckController < Admin::ApplicationController
feature_category :not_owned
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
def show
@errors = HealthCheck::Utils.process_checks(checks)

View file

@ -5,7 +5,7 @@ class Admin::PlanLimitsController < Admin::ApplicationController
before_action :set_plan_limits
feature_category :not_owned
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
def create
redirect_path = referer_path(request) || general_admin_application_settings_path

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
class Admin::RequestsProfilesController < Admin::ApplicationController
feature_category :not_owned
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
def index
@profile_token = Gitlab::RequestProfiler.profile_token

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
class Admin::SpamLogsController < Admin::ApplicationController
feature_category :not_owned
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
# rubocop: disable CodeReuse/ActiveRecord
def index

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
class Admin::SystemInfoController < Admin::ApplicationController
feature_category :not_owned
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
EXCLUDED_MOUNT_OPTIONS = %w[
nobrowse

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
class Admin::VersionCheckController < Admin::ApplicationController
feature_category :not_owned
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
def version_check
response = VersionCheck.new.response

View file

@ -24,7 +24,12 @@ module EnforcesTwoFactorAuthentication
return unless respond_to?(:current_user)
if two_factor_authentication_required? && current_user_requires_two_factor?
redirect_to profile_two_factor_auth_path
case self
when GraphqlController
render_error("2FA required", status: :unauthorized)
else
redirect_to profile_two_factor_auth_path
end
end
end

View file

@ -44,7 +44,7 @@ class GraphqlController < ApplicationController
around_action :sessionless_bypass_admin_mode!, if: :sessionless_user?
# The default feature category is overridden to read from request
feature_category :not_owned
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
# We don't know what the query is going to be, so we can't set a high urgency
# See https://gitlab.com/groups/gitlab-org/-/epics/5841 for the work that will

View file

@ -3,7 +3,7 @@
class HelpController < ApplicationController
skip_before_action :authenticate_user!, unless: :public_visibility_restricted?
skip_before_action :check_two_factor_requirement
feature_category :not_owned
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
layout 'help'

View file

@ -68,6 +68,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
else
@error = { message: _('Invalid pin code.') }
@qr_code = build_qr_code
@account_string = account_string
if Feature.enabled?(:webauthn, default_enabled: :yaml)
setup_webauthn_registration

View file

@ -9,7 +9,6 @@ class Projects::IncidentsController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:incident_escalations, @project)
push_frontend_feature_flag(:incident_timeline, @project, default_enabled: :yaml)
push_licensed_feature(:incident_timeline_events) if @project.licensed_feature_available?(:incident_timeline_events)
end
feature_category :incident_management

View file

@ -43,6 +43,7 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:iteration_cadences, project&.group, default_enabled: :yaml)
push_frontend_feature_flag(:contacts_autocomplete, project&.group, default_enabled: :yaml)
push_frontend_feature_flag(:markdown_continue_lists, project, default_enabled: :yaml)
push_frontend_feature_flag(:incident_timeline, project, default_enabled: :yaml)
end
before_action only: :show do

View file

@ -6,7 +6,7 @@ module Projects
before_action :ensure_feature_enabled!
before_action :authorize_read_cluster!
feature_category :not_owned
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
def index
respond_to do |format|

View file

@ -11,7 +11,7 @@ class Projects::UploadsController < Projects::ApplicationController
before_action :authorize_upload_file!, only: [:create, :authorize]
before_action :verify_workhorse_api!, only: [:authorize]
feature_category :not_owned
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
private

View file

@ -5,7 +5,7 @@ class Projects::WorkItemsController < Projects::ApplicationController
push_force_frontend_feature_flag(:work_items, project&.work_items_feature_flag_enabled?)
end
feature_category :not_owned
feature_category :team_planning
def index
render_404 unless project&.work_items_feature_flag_enabled?

View file

@ -3,7 +3,7 @@
class SandboxController < ApplicationController # rubocop:disable Gitlab/NamespacedClass
skip_before_action :authenticate_user!
feature_category :not_owned
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
def mermaid
render layout: false

View file

@ -26,7 +26,7 @@ class UploadsController < ApplicationController
before_action :authorize_create_access!, only: [:create, :authorize]
before_action :verify_workhorse_api!, only: [:authorize]
feature_category :not_owned
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
def self.model_classes
MODEL_CLASSES

View file

@ -11,14 +11,16 @@ require 'task_list/filter'
module Taskable
COMPLETED = 'completed'
INCOMPLETE = 'incomplete'
COMPLETE_PATTERN = /(\[[xX]\])/.freeze
INCOMPLETE_PATTERN = /(\[\s\])/.freeze
COMPLETE_PATTERN = /\[[xX]\]/.freeze
INCOMPLETE_PATTERN = /\[[[:space:]]\]/.freeze
ITEM_PATTERN = %r{
^
(?:(?:>\s{0,4})*) # optional blockquote characters
((?:\s*(?:[-+*]|(?:\d+\.)))+) # list prefix (one or more) required - task item has to be always in a list
\s+ # whitespace prefix has to be always presented for a list item
(\[\s\]|\[[xX]\]) # checkbox
( # checkbox
#{COMPLETE_PATTERN}|#{INCOMPLETE_PATTERN}
)
(\s.+) # followed by whitespace and some text.
}x.freeze

View file

@ -97,6 +97,8 @@ class Note < ApplicationRecord
validates :author, presence: true
validates :discussion_id, presence: true, format: { with: /\A\h{40}\z/ }
validate :ensure_confidentiality_not_changed, on: :update
validate unless: [:for_commit?, :importing?, :skip_project_check?] do |note|
unless note.noteable.try(:project) == note.project
errors.add(:project, 'does not match noteable project')
@ -718,6 +720,13 @@ class Note < ApplicationRecord
def noteable_label_url_method
for_merge_request? ? :project_merge_requests_url : :project_issues_url
end
def ensure_confidentiality_not_changed
return unless will_save_change_to_attribute?(:confidential)
return unless attribute_change_to_be_saved(:confidential).include?(true)
errors.add(:confidential, _('can not be changed for existing notes'))
end
end
Note.prepend_mod_with('Note')

View file

@ -27,10 +27,7 @@ module Notes
note.assign_attributes(last_edited_at: Time.current, updated_by: current_user)
end
note.with_transaction_returning_status do
update_confidentiality(note)
note.save
end
note.save
unless only_commands || note.for_personal_snippet?
note.create_new_cross_references!(current_user)
@ -88,15 +85,6 @@ module Notes
TodoService.new.update_note(note, current_user, old_mentioned_users)
end
# This method updates confidentiality of all discussion notes at once
def update_confidentiality(note)
return unless params.key?(:confidential)
return unless note.is_a?(DiscussionNote) # we don't need to do bulk update for single notes
return unless note.start_of_discussion? # don't update all notes if a response is being updated
Note.id_in(note.discussion.notes.map(&:id)).update_all(confidential: params[:confidential])
end
def track_note_edit_usage_for_issues(note)
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_comment_edited_action(author: note.author)
end

View file

@ -0,0 +1,7 @@
= render 'shared/global_alert', variant: :warning, dismissible: false, alert_class: 'gl-mt-6 gl-mb-3' do
.gl-alert-body
- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe
- issue_link_start = link_start % { url: 'https://gitlab.com/gitlab-org/configure/general/-/issues/199' }
- docs_link_start = link_start % { url: help_page_path('user/clusters/agent/index.md') }
- link_end = '</a>'.html_safe
= s_('ClusterIntegration|This process is %{issue_link_start}deprecated%{issue_link_end}. Use the %{docs_link_start}the GitLab agent for Kubernetes%{docs_link_end} instead.').html_safe % { docs_link_start: docs_link_start, docs_link_end: link_end, issue_link_start: issue_link_start, issue_link_end: link_end }

View file

@ -3,6 +3,8 @@
- breadcrumb_title _('Connect a cluster')
- page_title _('Connect a Kubernetes Cluster')
= render 'deprecation_alert'
.gl-md-display-flex.gl-mt-3
.gl-w-quarter.gl-xs-w-full.gl-flex-shrink-0.gl-md-mr-5
= render 'sidebar', is_connect_page: true

View file

@ -4,6 +4,8 @@
- page_title _('Create a Kubernetes cluster')
- provider = params[:provider]
= render 'deprecation_alert'
= render_gcp_signup_offer
.gl-md-display-flex.gl-mt-3

View file

@ -3,16 +3,12 @@
%div
- if @user.errors.any?
.gl-alert.gl-alert-danger.gl-my-5
.gl-alert-container
%button.js-close.btn.gl-dismiss-btn.btn-default.btn-sm.gl-button.btn-default-tertiary.btn-icon{ type: 'button', 'aria-label' => _('Dismiss') }
= sprite_icon('close', css_class: 'gl-icon')
= sprite_icon('error', css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
.gl-alert-content
.gl-alert-body
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
= render 'shared/global_alert',
variant: :danger do
.gl-alert-body
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
= hidden_field_tag :notification_type, 'global'
.row.gl-mt-3

View file

@ -7,6 +7,7 @@
= sprite_icon('branch', size: 12, css_class: 'gl-flex-shrink-0')
= link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name gl-ml-3 qa-branch-name' do
= branch.name
= clipboard_button(text: branch.name, title: _("Copy branch name"))
- if branch.name == @repository.root_ref
= gl_badge_tag s_('DefaultBranchLabel|default'), { variant: :info, size: :sm }, { class: 'gl-ml-2' }
- elsif merged

View file

@ -1,8 +1,8 @@
- related_branches_path = related_branches_project_issue_path(@project, issuable)
- api_awards_path = local_assigns.fetch(:api_awards_path, nil)
.issue-details.issuable-details
.detail-page-description.content-block
.issue-details.issuable-details.js-issue-details
.detail-page-description.content-block.js-detail-page-description
#js-issuable-app{ data: { initial: issuable_initial_data(issuable).to_json, full_path: @project.full_path } }
.title-container
%h1.title= markdown_field(issuable, :title)
@ -12,6 +12,7 @@
= edited_time_ago_with_tooltip(issuable, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago')
.js-issue-widgets
= render 'shared/issue_type/sentry_stack_trace', issuable: issuable
= render 'projects/issues/design_management'
@ -28,8 +29,9 @@
#related-branches{ data: { url: related_branches_path } }
-# This element is filled in using JavaScript.
= render 'shared/issue_type/emoji_block', issuable: issuable, api_awards_path: api_awards_path
.js-issue-widgets
= render 'shared/issue_type/emoji_block', issuable: issuable, api_awards_path: api_awards_path
= render 'projects/issues/discussion'
= render 'projects/issues/discussion'
= render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @issue.assignees

View file

@ -5,6 +5,6 @@ module ChaosQueue
included do
queue_namespace :chaos
feature_category_not_owned!
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
end
end

View file

@ -8,7 +8,10 @@ module ReactiveCacheableWorker
sidekiq_options retry: 3
feature_category_not_owned!
# Feature category is different depending on the model that is using the
# reactive cache. Identified by the `related_class` attribute.
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
loggable_arguments 0
def self.context_for_arguments(arguments)

View file

@ -35,17 +35,9 @@ module WorkerAttributes
class_methods do
def feature_category(value, *extras)
raise "Invalid category. Use `feature_category_not_owned!` to mark a worker as not owned" if value == :not_owned
set_class_attribute(:feature_category, value)
end
# Special case: mark this work as not associated with a feature category
# this should be used for cross-cutting concerns, such as mailer workers.
def feature_category_not_owned!
set_class_attribute(:feature_category, :not_owned)
end
# Special case: if a worker is not owned, get the feature category
# (if present) from the calling context.
def get_feature_category

View file

@ -7,7 +7,7 @@ class DeleteStoredFilesWorker # rubocop:disable Scalability/IdempotentWorker
sidekiq_options retry: 3
feature_category_not_owned!
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
loggable_arguments 0
def perform(class_name, keys)

View file

@ -12,7 +12,10 @@ class FlushCounterIncrementsWorker
sidekiq_options retry: 3
feature_category_not_owned!
# The increments in `ProjectStatistics` are owned by several teams depending
# on the counter
feature_category :not_owned # rubocop:disable Gitlab/AvoidFeatureCategoryNotOwned
urgency :low
deduplicate :until_executing, including_scheduled: true

View file

@ -8,7 +8,7 @@ module ObjectStorage
include ObjectStorageQueue
sidekiq_options retry: 5
feature_category_not_owned!
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
loggable_arguments 0, 1, 2, 3
def perform(uploader_class_name, subject_class_name, file_field, subject_id)

View file

@ -10,7 +10,7 @@ module ObjectStorage
sidekiq_options retry: 3
include ObjectStorageQueue
feature_category_not_owned!
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
loggable_arguments 0, 1, 2, 3
SanityCheckError = Class.new(StandardError)

View file

@ -1808,8 +1808,9 @@ Input type: `DastScannerProfileCreateInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationdastscannerprofilecreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationdastscannerprofilecreatedastscannerprofile"></a>`dastScannerProfile` | [`DastScannerProfile`](#dastscannerprofile) | Created scanner profile. |
| <a id="mutationdastscannerprofilecreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationdastscannerprofilecreateid"></a>`id` | [`DastScannerProfileID`](#dastscannerprofileid) | ID of the scanner profile. |
| <a id="mutationdastscannerprofilecreateid"></a>`id` **{warning-solid}** | [`DastScannerProfileID`](#dastscannerprofileid) | **Deprecated:** use `dastScannerProfile` field. Deprecated in 14.10. |
### `Mutation.dastScannerProfileDelete`
@ -1853,8 +1854,9 @@ Input type: `DastScannerProfileUpdateInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationdastscannerprofileupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationdastscannerprofileupdatedastscannerprofile"></a>`dastScannerProfile` | [`DastScannerProfile`](#dastscannerprofile) | Updated scanner profile. |
| <a id="mutationdastscannerprofileupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationdastscannerprofileupdateid"></a>`id` | [`DastScannerProfileID`](#dastscannerprofileid) | ID of the scanner profile. |
| <a id="mutationdastscannerprofileupdateid"></a>`id` **{warning-solid}** | [`DastScannerProfileID`](#dastscannerprofileid) | **Deprecated:** use `dastScannerProfile` field. Deprecated in 14.10. |
### `Mutation.dastSiteProfileCreate`

View file

@ -58,7 +58,8 @@ not, the specs will fail.
### Excluding Sidekiq workers from feature categorization
A few Sidekiq workers, that are used across all features, cannot be mapped to a
single category. These should be declared as such using the `feature_category_not_owned!`
single category. These should be declared as such using the
`feature_category :not_owned`
declaration, as shown below:
```ruby
@ -66,7 +67,7 @@ class SomeCrossCuttingConcernWorker
include ApplicationWorker
# Declares that this worker does not map to a feature category
feature_category_not_owned!
feature_category :not_owned # rubocop:disable Gitlab/AvoidFeatureCategoryNotOwned
# ...
end

View file

@ -5,7 +5,7 @@ module API
class PlanLimits < ::API::Base
before { authenticated_as_admin! }
feature_category :not_owned
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
helpers do
def current_plan(name)

View file

@ -5,7 +5,7 @@ module API
class Sidekiq < ::API::Base
before { authenticated_as_admin! }
feature_category :not_owned
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
namespace 'admin' do
namespace 'sidekiq' do

View file

@ -321,7 +321,7 @@ module API
end
end
route :any, '*path', feature_category: :not_owned do
route :any, '*path', feature_category: :not_owned do # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
error!('404 Not Found', 404)
end
end

View file

@ -189,7 +189,7 @@ module API
present actor.user, with: Entities::UserSafe
end
get '/check', feature_category: :not_owned do
get '/check', feature_category: :not_owned do # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
{
api_version: API.version,
gitlab_version: Gitlab::VERSION,

View file

@ -2,7 +2,7 @@
module API
class Markdown < ::API::Base
feature_category :not_owned
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
params do
requires :text, type: String, desc: "The markdown text to render"

View file

@ -631,7 +631,7 @@ module API
desc 'Workhorse authorize the file upload' do
detail 'This feature was introduced in GitLab 13.11'
end
post ':id/uploads/authorize', feature_category: :not_owned do
post ':id/uploads/authorize', feature_category: :not_owned do # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
require_gitlab_workhorse!
status 200
@ -643,7 +643,7 @@ module API
params do
requires :file, types: [Rack::Multipart::UploadedFile, ::API::Validations::Types::WorkhorseFile], desc: 'The attachment file to be uploaded'
end
post ":id/uploads", feature_category: :not_owned do
post ":id/uploads", feature_category: :not_owned do # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
log_if_upload_exceed_max_size(user_project, params[:file])
service = UploadService.new(user_project, params[:file])

View file

@ -4,7 +4,7 @@ module API
class Settings < ::API::Base
before { authenticated_as_admin! }
feature_category :not_owned
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
helpers Helpers::SettingsHelpers

View file

@ -6,7 +6,7 @@ module API
class SidekiqMetrics < ::API::Base
before { authenticated_as_admin! }
feature_category :not_owned
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
helpers do
def queue_metrics

View file

@ -9,7 +9,7 @@ module API
before { authenticate! }
feature_category :not_owned
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
METADATA_QUERY = <<~EOF
{

View file

@ -8790,6 +8790,9 @@ msgstr ""
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr ""
msgid "ClusterIntegration|This process is %{issue_link_start}deprecated%{issue_link_end}. Use the %{docs_link_start}the GitLab agent for Kubernetes%{docs_link_end} instead."
msgstr ""
msgid "ClusterIntegration|This project does not have billing enabled. To create a cluster, %{linkToBillingStart}enable billing%{linkToBillingEnd} and try again."
msgstr ""
@ -43868,6 +43871,9 @@ msgstr ""
msgid "can contain only lowercase letters, digits, and '_'."
msgstr ""
msgid "can not be changed for existing notes"
msgstr ""
msgid "can only be changed by a group admin."
msgstr ""

View file

@ -0,0 +1,43 @@
# frozen_string_literal: true
require_relative '../../code_reuse_helpers'
module RuboCop
module Cop
module Gitlab
class AvoidFeatureCategoryNotOwned < RuboCop::Cop::Cop
include ::RuboCop::CodeReuseHelpers
MSG = 'Avoid adding new endpoints with `feature_category :not_owned`. See https://docs.gitlab.com/ee/development/feature_categorization'
RESTRICT_ON_SEND = %i[feature_category get post put patch delete].freeze
def_node_matcher :feature_category_not_owned?, <<~PATTERN
(send _ :feature_category (sym :not_owned) ...)
PATTERN
def_node_matcher :feature_category_not_owned_api?, <<~PATTERN
(send nil? {:get :post :put :patch :delete} _
(hash <(pair (sym :feature_category) (sym :not_owned)) ...>)
)
PATTERN
def on_send(node)
return unless file_needs_feature_category?(node)
return unless setting_not_owned?(node)
add_offense(node, location: :expression)
end
private
def file_needs_feature_category?(node)
in_controller?(node) || in_worker?(node) || in_api?(node)
end
def setting_not_owned?(node)
feature_category_not_owned?(node) || feature_category_not_owned_api?(node)
end
end
end
end
end

View file

@ -152,6 +152,26 @@ RSpec.describe GraphqlController do
end
end
context 'when 2FA is required for the user' do
let(:user) { create(:user, last_activity_on: Date.yesterday) }
before do
group = create(:group, require_two_factor_authentication: true)
group.add_developer(user)
sign_in(user)
end
it 'does not redirect if 2FA is enabled' do
expect(controller).not_to receive(:redirect_to)
post :execute
expect(response).to have_gitlab_http_status(:unauthorized)
expect(json_response).to eq({ 'errors' => [{ 'message' => '2FA required' }] })
end
end
context 'when user uses an API token' do
let(:user) { create(:user, last_activity_on: Date.yesterday) }
let(:token) { create(:personal_access_token, user: user, scopes: [:api]) }

View file

@ -104,7 +104,7 @@ RSpec.describe Profiles::TwoFactorAuthsController do
expect(subject).to receive(:build_qr_code).and_return(code)
get :show
expect(assigns[:qr_code]).to eq code
expect(assigns[:qr_code]).to eq(code)
end
it 'generates a unique otp_secret every time the page is loaded' do
@ -183,7 +183,12 @@ RSpec.describe Profiles::TwoFactorAuthsController do
expect(subject).to receive(:build_qr_code).and_return(code)
go
expect(assigns[:qr_code]).to eq code
expect(assigns[:qr_code]).to eq(code)
end
it 'assigns account_string' do
go
expect(assigns[:account_string]).to eq("#{Gitlab.config.gitlab.host}:#{user.email}")
end
it 'renders show' do

View file

@ -56,5 +56,37 @@ RSpec.describe 'Incident Detail', :js do
end
end
end
context 'when on summary tab' do
before do
click_link 'Summary'
end
it 'shows the summary tab with all components' do
page.within('.issuable-details') do
hidden_items = find_all('.js-issue-widgets')
# Linked Issues/MRs and comment box
expect(hidden_items.count).to eq(2)
expect(hidden_items).to all(be_visible)
end
end
end
context 'when on alert details tab' do
before do
click_link 'Alert details'
end
it 'does not show the linked issues and notes/comment components' do
page.within('.issuable-details') do
hidden_items = find_all('.js-issue-widgets')
# Linked Issues/MRs and comment box are hidden on page
expect(hidden_items.count).to eq(0)
end
end
end
end
end

View file

@ -36,6 +36,8 @@ RSpec.describe 'Branches' do
expect(page).to have_content(sorted_branches(repository, count: 5, sort_by: :updated_desc, state: 'active'))
expect(page).to have_content(sorted_branches(repository, count: 4, sort_by: :updated_asc, state: 'stale'))
expect(page).to have_button('Copy branch name')
expect(page).to have_link('Show more active branches', href: project_branches_filtered_path(project, state: 'active'))
expect(page).not_to have_content('Show more stale branches')
end

View file

@ -818,7 +818,6 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
context 'when 2FA is required for the user' do
before do
stub_feature_flags(mr_attention_requests: false)
group = create(:group, require_two_factor_authentication: true)
group.add_developer(user)
end

View file

@ -14,6 +14,7 @@ describe('Description field component', () => {
propsData: {
markdownPreviewPath: '/',
markdownDocsPath: '/',
quickActionsDocsPath: '/',
formState: {
description,
},

View file

@ -183,6 +183,7 @@ describe('init markdown', () => {
${'- [ ] item'} | ${'- [ ] item\n- [ ] '}
${'- [x] item'} | ${'- [x] item\n- [ ] '}
${'- [X] item'} | ${'- [X] item\n- [ ] '}
${'- [ ] nbsp (U+00A0)'} | ${'- [ ] nbsp (U+00A0)\n- [ ] '}
${'- item\n - second'} | ${'- item\n - second\n - '}
${'- - -'} | ${'- - -'}
${'- --'} | ${'- --'}

View file

@ -81,7 +81,6 @@ describe('issue_note_form component', () => {
it('should show conflict message if note changes outside the component', async () => {
wrapper.setProps({
...props,
isEditing: true,
noteBody: 'Foo',
});
@ -111,6 +110,12 @@ describe('issue_note_form component', () => {
);
});
it('should set data-supports-quick-actions to enable autocomplete', () => {
const textarea = wrapper.find('textarea');
expect(textarea.attributes('data-supports-quick-actions')).toBe('true');
});
it('should link to markdown docs', () => {
const { markdownDocsPath } = notesDataMock;
const markdownField = wrapper.find(MarkdownField);
@ -171,7 +176,6 @@ describe('issue_note_form component', () => {
it('should be possible to cancel', async () => {
wrapper.setProps({
...props,
isEditing: true,
});
await nextTick();
@ -185,7 +189,6 @@ describe('issue_note_form component', () => {
it('should be possible to update the note', async () => {
wrapper.setProps({
...props,
isEditing: true,
});
await nextTick();

View file

@ -86,7 +86,6 @@ describe('noteable_discussion component', () => {
const noteFormProps = noteForm.props();
expect(noteFormProps.discussion).toBe(discussionMock);
expect(noteFormProps.isEditing).toBe(false);
expect(noteFormProps.line).toBe(null);
expect(noteFormProps.saveButtonTitle).toBe('Comment');
expect(noteFormProps.autosaveKey).toBe(`Note/Issue/${discussionMock.id}/Reply`);

View file

@ -298,16 +298,18 @@ describe('note_app', () => {
await nextTick();
expect(wrapper.find(`.edit-note a[href="${markdownDocsPath}"]`).text().trim()).toEqual(
'Markdown is supported',
'Markdown',
);
});
it('should not render quick actions docs url', async () => {
it('should render quick actions docs url', async () => {
wrapper.find('.js-note-edit').trigger('click');
const { quickActionsDocsPath } = mockData.notesDataMock;
await nextTick();
expect(wrapper.find(`.edit-note a[href="${quickActionsDocsPath}"]`).exists()).toBe(false);
expect(wrapper.find(`.edit-note a[href="${quickActionsDocsPath}"]`).text().trim()).toEqual(
'quick actions',
);
});
});

View file

@ -1,4 +1,5 @@
# frozen_string_literal: true
# rubocop:disable RSpec/VariableDefinition
require 'fast_spec_helper'
@ -205,3 +206,4 @@ RSpec.describe 'Mail quoted-printable transfer encoding patch and Unicode charac
end
end
end
# rubocop:enable RSpec/VariableDefinition

View file

@ -292,7 +292,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
if category
feature_category category
else
feature_category_not_owned!
feature_category :not_owned
end
def perform

View file

@ -28,7 +28,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Client do
'TestNotOwnedWithContextWorker'
end
feature_category_not_owned!
feature_category :not_owned
end
end

View file

@ -29,7 +29,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Server do
"NotOwnedWorker"
end
feature_category_not_owned!
feature_category :not_owned
end
end

View file

@ -13,6 +13,10 @@ RSpec.describe Taskable do
- [x] Second item
* [x] First item
* [ ] Second item
+ [ ] No-break space (U+00A0)
+ [] Figure space (U+2007)
+ [] Narrow no-break space (U+202F)
+ [] Thin space (U+2009)
MARKDOWN
end
@ -21,7 +25,11 @@ RSpec.describe Taskable do
TaskList::Item.new('- [ ]', 'First item'),
TaskList::Item.new('- [x]', 'Second item'),
TaskList::Item.new('* [x]', 'First item'),
TaskList::Item.new('* [ ]', 'Second item')
TaskList::Item.new('* [ ]', 'Second item'),
TaskList::Item.new('+ [ ]', 'No-break space (U+00A0)'),
TaskList::Item.new('+ []', 'Figure space (U+2007)'),
TaskList::Item.new('+ []', 'Narrow no-break space (U+202F)'),
TaskList::Item.new('+ []', 'Thin space (U+2009)')
]
end

View file

@ -105,6 +105,36 @@ RSpec.describe Note do
it { is_expected.to be_valid }
end
end
describe 'confidentiality' do
context 'for existing public note' do
let_it_be(:existing_note) { create(:note) }
it 'is not possible to change the note to confidential' do
existing_note.confidential = true
expect(existing_note).not_to be_valid
expect(existing_note.errors[:confidential]).to include('can not be changed for existing notes')
end
it 'is possible to change confidentiality from nil to false' do
existing_note.confidential = false
expect(existing_note).to be_valid
end
end
context 'for existing confidential note' do
let_it_be(:existing_note) { create(:note, confidential: true) }
it 'is not possible to change the note to public' do
existing_note.confidential = false
expect(existing_note).not_to be_valid
expect(existing_note.errors[:confidential]).to include('can not be changed for existing notes')
end
end
end
end
describe 'callbacks' do

View file

@ -8,7 +8,7 @@ RSpec.describe 'Updating a Note' do
let!(:note) { create(:note, note: original_body) }
let(:original_body) { 'Initial body text' }
let(:updated_body) { 'Updated body text' }
let(:params) { { body: updated_body, confidential: true } }
let(:params) { { body: updated_body } }
let(:mutation) do
variables = params.merge(id: GitlabSchema.id_from_object(note).to_s)
@ -28,7 +28,6 @@ RSpec.describe 'Updating a Note' do
post_graphql_mutation(mutation, current_user: current_user)
expect(note.reload.note).to eq(original_body)
expect(note.confidential).to be_falsey
end
end
@ -41,46 +40,19 @@ RSpec.describe 'Updating a Note' do
post_graphql_mutation(mutation, current_user: current_user)
expect(note.reload.note).to eq(updated_body)
expect(note.confidential).to be_truthy
end
it 'returns the updated Note' do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['note']['body']).to eq(updated_body)
expect(mutation_response['note']['confidential']).to be_truthy
end
context 'when only confidential param is present' do
let(:params) { { confidential: true } }
it 'updates only the note confidentiality' do
post_graphql_mutation(mutation, current_user: current_user)
expect(note.reload.note).to eq(original_body)
expect(note.confidential).to be_truthy
end
end
context 'when only body param is present' do
let(:params) { { body: updated_body } }
before do
note.update_column(:confidential, true)
end
it 'updates only the note body' do
post_graphql_mutation(mutation, current_user: current_user)
expect(note.reload.note).to eq(updated_body)
expect(note.confidential).to be_truthy
end
end
context 'when there are ActiveRecord validation errors' do
let(:updated_body) { '' }
let(:params) { { body: '', confidential: true } }
it_behaves_like 'a mutation that returns errors in the response', errors: ["Note can't be blank"]
it_behaves_like 'a mutation that returns errors in the response',
errors: ["Note can't be blank", 'Confidential can not be changed for existing notes']
it 'does not update the Note' do
post_graphql_mutation(mutation, current_user: current_user)

View file

@ -0,0 +1,69 @@
# frozen_string_literal: true
require 'fast_spec_helper'
require_relative '../../../../rubocop/cop/gitlab/avoid_feature_category_not_owned'
RSpec.describe RuboCop::Cop::Gitlab::AvoidFeatureCategoryNotOwned do
subject(:cop) { described_class.new }
shared_examples 'defining feature category on a class' do
it 'flags a method call on a class' do
expect_offense(<<~SOURCE)
feature_category :not_owned
^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid adding new endpoints with `feature_category :not_owned`. See https://docs.gitlab.com/ee/development/feature_categorization
SOURCE
end
it 'flags a method call on a class with an array passed' do
expect_offense(<<~SOURCE)
feature_category :not_owned, [:index, :edit]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid adding new endpoints with `feature_category :not_owned`. See https://docs.gitlab.com/ee/development/feature_categorization
SOURCE
end
it 'flags a method call on a class with an array passed' do
expect_offense(<<~SOURCE)
worker.feature_category :not_owned, [:index, :edit]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid adding new endpoints with `feature_category :not_owned`. See https://docs.gitlab.com/ee/development/feature_categorization
SOURCE
end
end
context 'in controllers' do
before do
allow(subject).to receive(:in_controller?).and_return(true)
end
it_behaves_like 'defining feature category on a class'
end
context 'in workers' do
before do
allow(subject).to receive(:in_worker?).and_return(true)
end
it_behaves_like 'defining feature category on a class'
end
context 'for grape endpoints' do
before do
allow(subject).to receive(:in_api?).and_return(true)
end
it_behaves_like 'defining feature category on a class'
it 'flags when passed as a hash for a Grape endpoint as keyword args' do
expect_offense(<<~SOURCE)
get :hello, feature_category: :not_owned
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid adding new endpoints with `feature_category :not_owned`. See https://docs.gitlab.com/ee/development/feature_categorization
SOURCE
end
it 'flags when passed as a hash for a Grape endpoint in a hash' do
expect_offense(<<~SOURCE)
get :hello, { feature_category: :not_owned, urgency: :low}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid adding new endpoints with `feature_category :not_owned`. See https://docs.gitlab.com/ee/development/feature_categorization
SOURCE
end
end
end

View file

@ -138,45 +138,6 @@ RSpec.describe Notes::UpdateService do
end
end
context 'setting confidentiality' do
let(:opts) { { confidential: true } }
context 'simple note' do
it 'updates the confidentiality' do
expect { update_note(opts) }.to change { note.reload.confidential }.from(nil).to(true)
end
end
context 'discussion notes' do
let(:note) { create(:discussion_note, project: project, noteable: issue, author: user, note: "Old note #{user2.to_reference}") }
let!(:response_note_1) { create(:discussion_note, project: project, noteable: issue, in_reply_to: note) }
let!(:response_note_2) { create(:discussion_note, project: project, noteable: issue, in_reply_to: note, confidential: false) }
let!(:other_note) { create(:note, project: project, noteable: issue) }
context 'when updating the root note' do
it 'updates the confidentiality of the root note and all the responses' do
update_note(opts)
expect(note.reload.confidential).to be_truthy
expect(response_note_1.reload.confidential).to be_truthy
expect(response_note_2.reload.confidential).to be_truthy
expect(other_note.reload.confidential).to be_falsey
end
end
context 'when updating one of the response notes' do
it 'updates only the confidentiality of the note that is being updated' do
Notes::UpdateService.new(project, user, opts).execute(response_note_1)
expect(note.reload.confidential).to be_falsey
expect(response_note_1.reload.confidential).to be_truthy
expect(response_note_2.reload.confidential).to be_falsey
expect(other_note.reload.confidential).to be_falsey
end
end
end
end
context 'todos' do
shared_examples 'does not update todos' do
it 'keep todos' do

View file

@ -16,6 +16,8 @@ RSpec.describe TaskListToggleService do
- [ ] loose list
with an embedded paragraph
+ [ ] No-break space (U+00A0)
EOT
end
@ -40,12 +42,17 @@ RSpec.describe TaskListToggleService do
</ul>
</li>
</ol>
<ul data-sourcepos="9:1-11:28" class="task-list" dir="auto">
<li data-sourcepos="9:1-11:28" class="task-list-item">
<ul data-sourcepos="9:1-12:0" class="task-list" dir="auto">
<li data-sourcepos="9:1-12:0" class="task-list-item">
<p data-sourcepos="9:3-9:16"><input type="checkbox" class="task-list-item-checkbox" disabled=""> loose list</p>
<p data-sourcepos="11:3-11:28">with an embedded paragraph</p>
</li>
</ul>
<ul data-sourcepos="13:1-13:21" class="task-list" dir="auto">
<li data-sourcepos="13:1-13:21" class="task-list-item">
<input type="checkbox" class="task-list-item-checkbox" disabled=""> No-break space (U+00A0)
</li>
</ul>
EOT
end
@ -79,6 +86,16 @@ RSpec.describe TaskListToggleService do
expect(toggler.updated_markdown_html).to include('disabled checked> loose list')
end
it 'checks task with no-break space' do
toggler = described_class.new(markdown, markdown_html,
toggle_as_checked: true,
line_source: '+ [ ] No-break space (U+00A0)', line_number: 13)
expect(toggler.execute).to be_truthy
expect(toggler.updated_markdown.lines[12]).to eq "+ [x] No-break space (U+00A0)"
expect(toggler.updated_markdown_html).to include('disabled checked> No-break space (U+00A0)')
end
it 'returns false if line_source does not match the text' do
toggler = described_class.new(markdown, markdown_html,
toggle_as_checked: false,

View file

@ -227,9 +227,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
end
context 'for confidential notes' do
before_all do
issue_note.update!(confidential: true)
end
let_it_be(:issue_note) { create(:note_on_issue, project: project, note: "issue note", confidential: true) }
it 'falls back to note channel' do
expect(::Slack::Messenger).to execute_with_options(channel: ['random'])

View file

@ -306,7 +306,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
end
describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id" do
let(:params) { { body: 'Hello!', confidential: false } }
let(:params) { { body: 'Hello!' } }
subject do
put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{note.id}", user), params: params
@ -314,44 +314,27 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
context 'when eveything is ok' do
before do
note.update!(confidential: true)
subject
end
context 'with multiple params present' do
before do
subject
end
it 'returns modified note' do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['body']).to eq('Hello!')
expect(json_response['confidential']).to be_falsey
end
it 'updates the note' do
expect(note.reload.note).to eq('Hello!')
expect(note.confidential).to be_falsey
end
it 'returns modified note' do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['body']).to eq('Hello!')
end
context 'when only body param is present' do
let(:params) { { body: 'Hello!' } }
it 'updates only the note text' do
expect { subject }.not_to change { note.reload.confidential }
expect(note.note).to eq('Hello!')
end
it 'updates the note' do
expect(note.reload.note).to eq('Hello!')
expect(note.confidential).to be_falsey
end
end
context 'when only confidential param is present' do
let(:params) { { confidential: false } }
context 'when also confidential param is set' do
let(:params) { { body: 'Hello!', confidential: true } }
it 'updates only the note text' do
expect { subject }.not_to change { note.reload.note }
it 'fails to update the note' do
expect { subject }.not_to change { note.reload.note }
expect(note.confidential).to be_falsey
end
expect(response).to have_gitlab_http_status(:bad_request)
end
end

View file

@ -49,7 +49,7 @@ RSpec.describe ApplicationWorker do
worker.feature_category :pages
expect(worker.sidekiq_options['queue']).to eq('queue_2')
worker.feature_category_not_owned!
worker.feature_category :not_owned
expect(worker.sidekiq_options['queue']).to eq('queue_3')
worker.urgency :high

View file

@ -54,7 +54,7 @@ RSpec.describe 'Every Sidekiq worker' do
# All Sidekiq worker classes should declare a valid `feature_category`
# or explicitly be excluded with the `feature_category_not_owned!` annotation.
# Please see doc/development/sidekiq_style_guide.md#feature-categorization for more details.
it 'has a feature_category or feature_category_not_owned! attribute', :aggregate_failures do
it 'has a feature_category attribute', :aggregate_failures do
workers_without_defaults.each do |worker|
expect(worker.get_feature_category).to be_a(Symbol), "expected #{worker.inspect} to declare a feature_category or feature_category_not_owned!"
end