Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b5e513dbef
commit
7e964f54ed
84 changed files with 400 additions and 220 deletions
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -221,7 +221,6 @@ export default {
|
|||
</div>
|
||||
<note-form
|
||||
ref="noteForm"
|
||||
:is-editing="false"
|
||||
:line-code="line.line_code"
|
||||
:line="line"
|
||||
:lines="commentLines"
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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*]*))$/;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
7
app/views/clusters/clusters/_deprecation_alert.html.haml
Normal file
7
app/views/clusters/clusters/_deprecation_alert.html.haml
Normal 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 }
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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`
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -9,7 +9,7 @@ module API
|
|||
|
||||
before { authenticate! }
|
||||
|
||||
feature_category :not_owned
|
||||
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
|
||||
|
||||
METADATA_QUERY = <<~EOF
|
||||
{
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
43
rubocop/cop/gitlab/avoid_feature_category_not_owned.rb
Normal file
43
rubocop/cop/gitlab/avoid_feature_category_not_owned.rb
Normal 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
|
|
@ -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]) }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -14,6 +14,7 @@ describe('Description field component', () => {
|
|||
propsData: {
|
||||
markdownPreviewPath: '/',
|
||||
markdownDocsPath: '/',
|
||||
quickActionsDocsPath: '/',
|
||||
formState: {
|
||||
description,
|
||||
},
|
||||
|
|
|
@ -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 - '}
|
||||
${'- - -'} | ${'- - -'}
|
||||
${'- --'} | ${'- --'}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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`);
|
||||
|
|
|
@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -28,7 +28,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Client do
|
|||
'TestNotOwnedWithContextWorker'
|
||||
end
|
||||
|
||||
feature_category_not_owned!
|
||||
feature_category :not_owned
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Server do
|
|||
"NotOwnedWorker"
|
||||
end
|
||||
|
||||
feature_category_not_owned!
|
||||
feature_category :not_owned
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue