Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
92e4789eb0
commit
326b4d3216
|
@ -1 +1 @@
|
|||
bb2e3f4a916f031f38c9fb1c4fc955f50f0e4275
|
||||
bdbed7a8a30246ee83d325615bb43213bf7a1d69
|
||||
|
|
|
@ -24,7 +24,7 @@ export default () => {
|
|||
} = domEl.dataset;
|
||||
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: createDefaultClient(),
|
||||
defaultClient: createDefaultClient({}, { assumeImmutableResults: true }),
|
||||
});
|
||||
|
||||
return new Vue({
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlIcon, GlIntersectionObserver } from '@gitlab/ui';
|
||||
import { GlIcon, GlIntersectionObserver, GlTooltipDirective } from '@gitlab/ui';
|
||||
import Visibility from 'visibilityjs';
|
||||
import createFlash from '~/flash';
|
||||
import Poll from '~/lib/utils/poll';
|
||||
|
@ -32,6 +32,9 @@ export default {
|
|||
formComponent,
|
||||
PinnedLinks,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
endpoint: {
|
||||
required: true,
|
||||
|
@ -183,6 +186,11 @@ export default {
|
|||
required: false,
|
||||
default: true,
|
||||
},
|
||||
isHidden: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const store = new Store({
|
||||
|
@ -508,6 +516,15 @@ export default {
|
|||
<span v-if="isConfidential" data-testid="confidential" class="issuable-warning-icon">
|
||||
<gl-icon name="eye-slash" :aria-label="__('Confidential')" />
|
||||
</span>
|
||||
<span
|
||||
v-if="isHidden"
|
||||
v-gl-tooltip
|
||||
:title="__('This issue is hidden because its author has been banned')"
|
||||
data-testid="hidden"
|
||||
class="issuable-warning-icon"
|
||||
>
|
||||
<gl-icon name="spam" />
|
||||
</span>
|
||||
<p
|
||||
class="gl-font-weight-bold gl-overflow-hidden gl-white-space-nowrap gl-text-overflow-ellipsis gl-my-0"
|
||||
:title="state.titleText"
|
||||
|
|
|
@ -135,9 +135,9 @@ export default {
|
|||
</gl-form-checkbox>
|
||||
|
||||
<gl-form-checkbox
|
||||
v-if="canBeLockedToProject"
|
||||
v-model="model.locked"
|
||||
data-testid="runner-field-locked"
|
||||
:disabled="!canBeLockedToProject"
|
||||
>
|
||||
{{ __('Lock to current projects') }}
|
||||
<template #help>
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
CONFIRM,
|
||||
WARNING,
|
||||
MT_MERGE_STRATEGY,
|
||||
PIPELINE_FAILED_STATE,
|
||||
} from '../../constants';
|
||||
import eventHub from '../../event_hub';
|
||||
import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
|
||||
|
@ -39,7 +40,6 @@ import CommitsHeader from './commits_header.vue';
|
|||
import SquashBeforeMerge from './squash_before_merge.vue';
|
||||
|
||||
const PIPELINE_RUNNING_STATE = 'running';
|
||||
const PIPELINE_FAILED_STATE = 'failed';
|
||||
const PIPELINE_PENDING_STATE = 'pending';
|
||||
const PIPELINE_SUCCESS_STATE = 'success';
|
||||
|
||||
|
@ -105,6 +105,10 @@ export default {
|
|||
import(
|
||||
'ee_component/vue_merge_request_widget/components/merge_immediately_confirmation_dialog.vue'
|
||||
),
|
||||
MergeTrainFailedPipelineConfirmationDialog: () =>
|
||||
import(
|
||||
'ee_component/vue_merge_request_widget/components/merge_train_failed_pipeline_confirmation_dialog.vue'
|
||||
),
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
|
@ -125,6 +129,7 @@ export default {
|
|||
squashBeforeMerge: this.mr.squashIsSelected,
|
||||
isSquashReadOnly: this.mr.squashIsReadonly,
|
||||
squashCommitMessage: this.mr.squashCommitMessage,
|
||||
isPipelineFailedModalVisible: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -327,7 +332,12 @@ export default {
|
|||
: this.mr.commitMessageWithDescription;
|
||||
this.commitMessage = includeDescription ? commitMessageWithDescription : commitMessage;
|
||||
},
|
||||
handleMergeButtonClick(useAutoMerge, mergeImmediately = false) {
|
||||
handleMergeButtonClick(useAutoMerge, mergeImmediately = false, confirmationClicked = false) {
|
||||
if (this.showFailedPipelineModal && !confirmationClicked) {
|
||||
this.isPipelineFailedModalVisible = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mergeImmediately) {
|
||||
this.isMergingImmediately = true;
|
||||
}
|
||||
|
@ -522,6 +532,11 @@ export default {
|
|||
@mergeImmediately="onMergeImmediatelyConfirmation"
|
||||
/>
|
||||
</gl-dropdown>
|
||||
<merge-train-failed-pipeline-confirmation-dialog
|
||||
:visible="isPipelineFailedModalVisible"
|
||||
@startMergeTrain="onStartMergeTrainConfirmation"
|
||||
@cancel="isPipelineFailedModalVisible = false"
|
||||
/>
|
||||
</gl-button-group>
|
||||
<div
|
||||
v-if="shouldShowMergeControls"
|
||||
|
|
|
@ -10,6 +10,8 @@ export const MWPS_MERGE_STRATEGY = 'merge_when_pipeline_succeeds';
|
|||
export const MTWPS_MERGE_STRATEGY = 'add_to_merge_train_when_pipeline_succeeds';
|
||||
export const MT_MERGE_STRATEGY = 'merge_train';
|
||||
|
||||
export const PIPELINE_FAILED_STATE = 'failed';
|
||||
|
||||
export const AUTO_MERGE_STRATEGIES = [MWPS_MERGE_STRATEGY, MTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY];
|
||||
|
||||
// SP - "Suggest Pipelines"
|
||||
|
|
|
@ -38,5 +38,13 @@ export default {
|
|||
pipelineId() {
|
||||
return this.pipeline.id;
|
||||
},
|
||||
showFailedPipelineModal() {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onStartMergeTrainConfirmation() {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
import Vue from 'vue';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import IssuableHeaderWarnings from './issuable_header_warnings.vue';
|
||||
|
||||
export default function issuableHeaderWarnings(store) {
|
||||
const el = document.getElementById('js-issuable-header-warnings');
|
||||
|
||||
if (!el) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { hidden } = el.dataset;
|
||||
|
||||
return new Vue({
|
||||
el: document.getElementById('js-issuable-header-warnings'),
|
||||
el,
|
||||
store,
|
||||
provide: { hidden: parseBoolean(hidden) },
|
||||
render(createElement) {
|
||||
return createElement(IssuableHeaderWarnings);
|
||||
},
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
<script>
|
||||
import { GlIcon } from '@gitlab/ui';
|
||||
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlIcon,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
inject: ['hidden'],
|
||||
computed: {
|
||||
...mapGetters(['getNoteableData']),
|
||||
isLocked() {
|
||||
|
@ -26,6 +31,12 @@ export default {
|
|||
visible: this.isConfidential,
|
||||
dataTestId: 'confidential',
|
||||
},
|
||||
{
|
||||
iconName: 'spam',
|
||||
visible: this.hidden,
|
||||
dataTestId: 'hidden',
|
||||
tooltip: __('This issue is hidden because its author has been banned'),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
|
@ -35,8 +46,15 @@ export default {
|
|||
<template>
|
||||
<div class="gl-display-inline-block">
|
||||
<template v-for="meta in warningIconsMeta">
|
||||
<div v-if="meta.visible" :key="meta.iconName" class="issuable-warning-icon inline">
|
||||
<gl-icon :name="meta.iconName" :data-testid="meta.dataTestId" class="icon" />
|
||||
<div
|
||||
v-if="meta.visible"
|
||||
:key="meta.iconName"
|
||||
v-gl-tooltip
|
||||
:data-testid="meta.dataTestId"
|
||||
:title="meta.tooltip || null"
|
||||
class="issuable-warning-icon inline"
|
||||
>
|
||||
<gl-icon :name="meta.iconName" class="icon" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
text-align: center;
|
||||
margin-right: $issuable-warning-icon-margin;
|
||||
line-height: $gl-line-height-24;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.limit-container-width {
|
||||
|
|
|
@ -77,7 +77,12 @@ class InvitesController < ApplicationController
|
|||
def track_invite_join_click
|
||||
return unless member && initial_invite_email?
|
||||
|
||||
experiment(:invite_email_preview_text, actor: member).track(:join_clicked) if params[:experiment_name] == 'invite_email_preview_text'
|
||||
if params[:experiment_name] == 'invite_email_preview_text'
|
||||
experiment(:invite_email_preview_text, actor: member).track(:join_clicked)
|
||||
elsif params[:experiment_name] == 'invite_email_from'
|
||||
experiment(:invite_email_from, actor: member).track(:join_clicked)
|
||||
end
|
||||
|
||||
Gitlab::Tracking.event(self.class.name, 'join_clicked', label: 'invite_email', property: member.id.to_s)
|
||||
end
|
||||
|
||||
|
|
|
@ -201,6 +201,7 @@ class RegistrationsController < Devise::RegistrationsController
|
|||
|
||||
experiment_name = session.delete(:invite_email_experiment_name)
|
||||
experiment(:invite_email_preview_text, actor: member).track(:accepted) if experiment_name == 'invite_email_preview_text'
|
||||
experiment(:invite_email_from, actor: member).track(:accepted) if experiment_name == 'invite_email_from'
|
||||
Gitlab::Tracking.event(self.class.name, 'accepted', label: 'invite_email', property: member.id.to_s)
|
||||
end
|
||||
|
||||
|
|
|
@ -256,7 +256,8 @@ module IssuablesHelper
|
|||
issueType: issuable.issue_type,
|
||||
zoomMeetingUrl: ZoomMeeting.canonical_meeting_url(issuable),
|
||||
sentryIssueIdentifier: SentryIssue.find_by(issue: issuable)&.sentry_issue_identifier, # rubocop:disable CodeReuse/ActiveRecord
|
||||
iid: issuable.iid.to_s
|
||||
iid: issuable.iid.to_s,
|
||||
isHidden: issue_hidden?(issuable)
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -60,8 +60,16 @@ module IssuesHelper
|
|||
sprite_icon('eye-slash', css_class: 'gl-vertical-align-text-bottom') if issue.confidential?
|
||||
end
|
||||
|
||||
def issue_hidden?(issue)
|
||||
Feature.enabled?(:ban_user_feature_flag) && issue.hidden?
|
||||
end
|
||||
|
||||
def hidden_issue_icon(issue)
|
||||
sprite_icon('spam', css_class: 'gl-vertical-align-text-bottom') if issue.hidden?
|
||||
return unless issue_hidden?(issue)
|
||||
|
||||
content_tag(:span, class: 'has-tooltip', title: _('This issue is hidden because its author has been banned')) do
|
||||
sprite_icon('spam', css_class: 'gl-vertical-align-text-bottom')
|
||||
end
|
||||
end
|
||||
|
||||
def award_user_list(awards, current_user, limit: 10)
|
||||
|
|
|
@ -24,7 +24,14 @@ module NotifyHelper
|
|||
def invited_join_url(token, member)
|
||||
additional_params = { invite_type: Emails::Members::INITIAL_INVITE }
|
||||
|
||||
if experiment(:invite_email_preview_text, actor: member).enabled?
|
||||
# order important below to our scheduled testing of these
|
||||
# `from` experiment will be after the `text` on, but we may not cleanup
|
||||
# from the `text` one by the time we run the `from` experiment,
|
||||
# therefore we want to support `text` being fully enabled
|
||||
# but if `from` is also enabled, then we only care about `from`
|
||||
if experiment(:invite_email_from, actor: member).enabled?
|
||||
additional_params[:experiment_name] = 'invite_email_from'
|
||||
elsif experiment(:invite_email_preview_text, actor: member).enabled?
|
||||
additional_params[:experiment_name] = 'invite_email_preview_text'
|
||||
end
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ module Emails
|
|||
|
||||
Gitlab::Tracking.event(self.class.name, 'invite_email_sent', label: 'invite_email', property: member_id.to_s)
|
||||
|
||||
mail(to: member.invite_email, subject: invite_email_subject, **invite_email_headers) do |format|
|
||||
mail(to: member.invite_email, subject: invite_email_subject, **invite_email_headers.merge(additional_invite_settings)) do |format|
|
||||
format.html { render layout: 'unknown_user_mailer' }
|
||||
format.text { render layout: 'unknown_user_mailer' }
|
||||
end
|
||||
|
@ -147,7 +147,17 @@ module Emails
|
|||
|
||||
def invite_email_subject
|
||||
if member.created_by
|
||||
subject(s_("MemberInviteEmail|%{member_name} invited you to join GitLab") % { member_name: member.created_by.name })
|
||||
experiment(:invite_email_from, actor: member) do |experiment_instance|
|
||||
experiment_instance.use do
|
||||
subject(s_("MemberInviteEmail|%{member_name} invited you to join GitLab") % { member_name: member.created_by.name })
|
||||
end
|
||||
|
||||
experiment_instance.candidate do
|
||||
subject(s_("MemberInviteEmail|I've invited you to join me in GitLab"))
|
||||
end
|
||||
|
||||
experiment_instance.run
|
||||
end
|
||||
else
|
||||
subject(s_("MemberInviteEmail|Invitation to join the %{project_or_group} %{project_or_group_name}") % { project_or_group: member_source.human_name, project_or_group_name: member_source.model_name.singular })
|
||||
end
|
||||
|
@ -164,6 +174,21 @@ module Emails
|
|||
end
|
||||
end
|
||||
|
||||
def additional_invite_settings
|
||||
return {} unless member.created_by
|
||||
|
||||
experiment(:invite_email_from, actor: member) do |experiment_instance|
|
||||
experiment_instance.use { {} }
|
||||
experiment_instance.candidate do
|
||||
{
|
||||
from: "#{member.created_by.name} <#{member.created_by.email}>"
|
||||
}
|
||||
end
|
||||
|
||||
experiment_instance.run
|
||||
end
|
||||
end
|
||||
|
||||
def member_exists?
|
||||
Gitlab::AppLogger.info("Tried to send an email invitation for a deleted group. Member id: #{@member_id}") if member.blank?
|
||||
member.present?
|
||||
|
|
|
@ -69,6 +69,14 @@ class IssuePolicy < IssuablePolicy
|
|||
rule { persisted & can?(:admin_issue) }.policy do
|
||||
enable :set_issue_metadata
|
||||
end
|
||||
|
||||
rule { can?(:set_issue_metadata) }.policy do
|
||||
enable :set_confidentiality
|
||||
end
|
||||
|
||||
rule { ~persisted & can?(:create_issue) }.policy do
|
||||
enable :set_confidentiality
|
||||
end
|
||||
end
|
||||
|
||||
IssuePolicy.prepend_mod_with('IssuePolicy')
|
||||
|
|
|
@ -51,9 +51,12 @@ class IssuableBaseService < ::BaseProjectService
|
|||
params.delete(:canonical_issue_id)
|
||||
params.delete(:project)
|
||||
params.delete(:discussion_locked)
|
||||
params.delete(:confidential)
|
||||
end
|
||||
|
||||
# confidential attribute is a special type of metadata and needs to be allowed to be set
|
||||
# by non-members on issues in public projects so that security issues can be reported as confidential.
|
||||
params.delete(:confidential) unless can?(current_user, :set_confidentiality, issuable)
|
||||
|
||||
filter_assignees(issuable)
|
||||
filter_milestone
|
||||
filter_labels
|
||||
|
|
|
@ -4,15 +4,7 @@
|
|||
- page_title "##{@runner.id} (#{@runner.short_sha})"
|
||||
- add_to_breadcrumbs _('Runners'), admin_runners_path
|
||||
|
||||
- if Feature.enabled?(:runner_detailed_view_vue_ui, current_user, default_enabled: :yaml)
|
||||
#js-runner-details{ data: {runner_id: @runner.id} }
|
||||
- else
|
||||
%h2.page-title
|
||||
= s_('Runners|Runner #%{runner_id}' % { runner_id: @runner.id })
|
||||
= render 'shared/runners/runner_type_badge', runner: @runner
|
||||
= render 'shared/runners/runner_type_alert', runner: @runner
|
||||
.gl-mb-6
|
||||
= render 'shared/runners/form', runner: @runner, runner_form_url: admin_runner_path(@runner), in_gitlab_com_admin_context: Gitlab.com?
|
||||
#js-runner-details{ data: {runner_id: @runner.id} }
|
||||
|
||||
.row
|
||||
.col-md-6
|
||||
|
|
|
@ -12,9 +12,7 @@
|
|||
- if issue.confidential?
|
||||
%span.has-tooltip{ title: _('Confidential') }
|
||||
= confidential_icon(issue)
|
||||
- if Feature.enabled?(:ban_user_feature_flag) && issue.hidden?
|
||||
%span.has-tooltip{ title: _('This issue is hidden because its author has been banned') }
|
||||
= hidden_issue_icon(issue)
|
||||
= hidden_issue_icon(issue)
|
||||
= link_to issue.title, issue_path(issue)
|
||||
= render_if_exists 'projects/issues/subepic_flag', issue: issue
|
||||
- if issue.tasks?
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
- project = local_assigns.fetch(:project)
|
||||
- issuable = local_assigns.fetch(:issuable)
|
||||
- presenter = local_assigns.fetch(:presenter)
|
||||
|
||||
- return unless can?(current_user, :"set_#{issuable.to_ability_name}_metadata", issuable)
|
||||
|
||||
- has_due_date = issuable.has_attribute?(:due_date)
|
||||
- form = local_assigns.fetch(:form)
|
||||
|
||||
- if issuable.respond_to?(:confidential)
|
||||
- if issuable.respond_to?(:confidential) && can?(current_user, :set_confidentiality, issuable)
|
||||
.form-group.row
|
||||
.offset-sm-2.col-sm-10
|
||||
.form-check
|
||||
|
@ -15,39 +12,40 @@
|
|||
= form.label :confidential, class: 'form-check-label' do
|
||||
This issue is confidential and should only be visible to team members with at least Reporter access.
|
||||
|
||||
%hr
|
||||
.row
|
||||
%div{ class: (has_due_date ? "col-lg-6" : "col-12") }
|
||||
.form-group.row.merge-request-assignee
|
||||
= render "shared/issuable/form/metadata_issuable_assignee", issuable: issuable, form: form, has_due_date: has_due_date
|
||||
- if can?(current_user, :"set_#{issuable.to_ability_name}_metadata", issuable)
|
||||
%hr
|
||||
.row
|
||||
%div{ class: (has_due_date ? "col-lg-6" : "col-12") }
|
||||
.form-group.row.merge-request-assignee
|
||||
= render "shared/issuable/form/metadata_issuable_assignee", issuable: issuable, form: form, has_due_date: has_due_date
|
||||
|
||||
- if issuable.allows_reviewers?
|
||||
.form-group.row.merge-request-reviewer
|
||||
= render "shared/issuable/form/metadata_issuable_reviewer", issuable: issuable, form: form, has_due_date: has_due_date, presenter: presenter
|
||||
- if issuable.allows_reviewers?
|
||||
.form-group.row.merge-request-reviewer
|
||||
= render "shared/issuable/form/metadata_issuable_reviewer", issuable: issuable, form: form, has_due_date: has_due_date, presenter: presenter
|
||||
|
||||
= render_if_exists "shared/issuable/form/epic", issuable: issuable, form: form, project: project
|
||||
= render_if_exists "shared/issuable/form/epic", issuable: issuable, form: form, project: project
|
||||
|
||||
- if issuable.supports_milestone?
|
||||
.form-group.row.issue-milestone
|
||||
= form.label :milestone_id, "Milestone", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}"
|
||||
.col-sm-10{ class: ("col-md-8" if has_due_date) }
|
||||
.issuable-form-select-holder
|
||||
= render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, show_started: false, extra_class: "qa-issuable-milestone-dropdown js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
|
||||
- if issuable.supports_milestone?
|
||||
.form-group.row.issue-milestone
|
||||
= form.label :milestone_id, "Milestone", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}"
|
||||
.col-sm-10{ class: ("col-md-8" if has_due_date) }
|
||||
.issuable-form-select-holder
|
||||
= render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, show_started: false, extra_class: "qa-issuable-milestone-dropdown js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
|
||||
|
||||
.form-group.row
|
||||
= form.label :label_ids, "Labels", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}"
|
||||
= form.hidden_field :label_ids, multiple: true, value: ''
|
||||
.col-sm-10{ class: "#{"col-md-8" if has_due_date}" }
|
||||
.issuable-form-select-holder
|
||||
= render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false }, dropdown_title: "Select label"
|
||||
|
||||
= render_if_exists "shared/issuable/form/merge_request_blocks", issuable: issuable, form: form
|
||||
|
||||
- if has_due_date
|
||||
.col-lg-6
|
||||
= render_if_exists "shared/issuable/form/weight", issuable: issuable, form: form
|
||||
.form-group.row
|
||||
= form.label :due_date, "Due date", class: "col-form-label col-md-2 col-lg-4"
|
||||
.col-8
|
||||
= form.label :label_ids, "Labels", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}"
|
||||
= form.hidden_field :label_ids, multiple: true, value: ''
|
||||
.col-sm-10{ class: "#{"col-md-8" if has_due_date}" }
|
||||
.issuable-form-select-holder
|
||||
= form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date", autocomplete: 'off'
|
||||
= render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false }, dropdown_title: "Select label"
|
||||
|
||||
= render_if_exists "shared/issuable/form/merge_request_blocks", issuable: issuable, form: form
|
||||
|
||||
- if has_due_date
|
||||
.col-lg-6
|
||||
= render_if_exists "shared/issuable/form/weight", issuable: issuable, form: form
|
||||
.form-group.row
|
||||
= form.label :due_date, "Due date", class: "col-form-label col-md-2 col-lg-4"
|
||||
.col-8
|
||||
.issuable-form-select-holder
|
||||
= form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date", autocomplete: 'off'
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
= _('Open')
|
||||
|
||||
.issuable-meta
|
||||
#js-issuable-header-warnings
|
||||
#js-issuable-header-warnings{ data: { hidden: issue_hidden?(issuable).to_s } }
|
||||
= issuable_meta(issuable, @project)
|
||||
|
||||
%a.btn.gl-button.btn-default.btn-icon.float-right.gl-display-block.d-sm-none.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: dast_profile_disable_joins
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66709
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/336944
|
||||
milestone: '14.2'
|
||||
type: development
|
||||
group: group::dynamic analysis
|
||||
default_enabled: true
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: dast_scanner_profile_disable_joins
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66709
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/336946
|
||||
milestone: '14.2'
|
||||
type: development
|
||||
group: group::dynamic analysis
|
||||
default_enabled: true
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: dast_site_profile_disable_joins
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66709
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/336945
|
||||
milestone: '14.2'
|
||||
type: development
|
||||
group: group::dynamic analysis
|
||||
default_enabled: true
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: runner_detailed_view_vue_ui
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57256
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/325737
|
||||
milestone: '13.11'
|
||||
type: development
|
||||
group: group::runner
|
||||
default_enabled: true
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: set_full_path
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66929
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337002
|
||||
milestone: '14.2'
|
||||
type: development
|
||||
group: group::gitaly
|
||||
default_enabled: false
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: invite_email_from
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68376
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/growth/team-tasks/-/issues/429
|
||||
milestone: '14.3'
|
||||
type: experiment
|
||||
group: group::expansion
|
||||
default_enabled: false
|
|
@ -121,11 +121,11 @@ stages:
|
|||
- test
|
||||
- deploy
|
||||
|
||||
job 1:
|
||||
job1:
|
||||
stage: build
|
||||
script: make build dependencies
|
||||
|
||||
job 2:
|
||||
job2:
|
||||
stage: build
|
||||
script: make build artifacts
|
||||
|
||||
|
@ -216,12 +216,12 @@ jobs:
|
|||
Example of the same workflow using `rules` in GitLab CI/CD:
|
||||
|
||||
```yaml
|
||||
deploy_prod:
|
||||
deploy:
|
||||
stage: deploy
|
||||
script:
|
||||
- echo "Deploy to production server"
|
||||
- echo "Deploy job"
|
||||
rules:
|
||||
- if: '$CI_COMMIT_BRANCH == "main"'
|
||||
- if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH =~ /^rc-/'
|
||||
```
|
||||
|
||||
### Caching
|
||||
|
|
|
@ -336,7 +336,7 @@ module API
|
|||
lines = Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
|
||||
|
||||
lines.each do |line|
|
||||
next unless line.new_pos == params[:line] && line.type == params[:line_type]
|
||||
next unless line.line == params[:line] && line.type == params[:line_type]
|
||||
|
||||
break opts[:line_code] = Gitlab::Git.diff_line_code(diff.new_path, line.new_pos, line.old_pos)
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ module API
|
|||
class CommitNote < Grape::Entity
|
||||
expose :note
|
||||
expose(:path) { |note| note.diff_file.try(:file_path) if note.diff_note? }
|
||||
expose(:line) { |note| note.diff_line.try(:new_line) if note.diff_note? }
|
||||
expose(:line) { |note| note.diff_line.try(:line) if note.diff_note? }
|
||||
expose(:line_type) { |note| note.diff_line.try(:type) if note.diff_note? }
|
||||
expose :author, using: Entities::UserBasic
|
||||
expose :created_at
|
||||
|
|
|
@ -35,12 +35,6 @@ module Gitlab
|
|||
|
||||
def hosts=(hosts)
|
||||
@mutex.synchronize do
|
||||
::Gitlab::Database::LoadBalancing::Logger.info(
|
||||
event: :host_list_update,
|
||||
message: "Updating the host list for service discovery",
|
||||
host_list_length: hosts.length,
|
||||
old_host_list_length: @hosts.length
|
||||
)
|
||||
@hosts = hosts
|
||||
unsafe_shuffle
|
||||
end
|
||||
|
|
|
@ -13,11 +13,17 @@ module Gitlab
|
|||
# balancer with said hosts. Requests may continue to use the old hosts
|
||||
# until they complete.
|
||||
class ServiceDiscovery
|
||||
EmptyDnsResponse = Class.new(StandardError)
|
||||
|
||||
attr_reader :interval, :record, :record_type, :disconnect_timeout,
|
||||
:load_balancer
|
||||
|
||||
MAX_SLEEP_ADJUSTMENT = 10
|
||||
|
||||
MAX_DISCOVERY_RETRIES = 3
|
||||
|
||||
RETRY_DELAY_RANGE = (0.1..0.2).freeze
|
||||
|
||||
RECORD_TYPES = {
|
||||
'A' => Net::DNS::A,
|
||||
'SRV' => Net::DNS::SRV
|
||||
|
@ -76,15 +82,21 @@ module Gitlab
|
|||
end
|
||||
|
||||
def perform_service_discovery
|
||||
refresh_if_necessary
|
||||
rescue StandardError => error
|
||||
# Any exceptions that might occur should be reported to
|
||||
# Sentry, instead of silently terminating this thread.
|
||||
Gitlab::ErrorTracking.track_exception(error)
|
||||
MAX_DISCOVERY_RETRIES.times do
|
||||
return refresh_if_necessary
|
||||
rescue StandardError => error
|
||||
# Any exceptions that might occur should be reported to
|
||||
# Sentry, instead of silently terminating this thread.
|
||||
Gitlab::ErrorTracking.track_exception(error)
|
||||
|
||||
Gitlab::AppLogger.error(
|
||||
"Service discovery encountered an error: #{error.message}"
|
||||
)
|
||||
Gitlab::AppLogger.error(
|
||||
"Service discovery encountered an error: #{error.message}"
|
||||
)
|
||||
|
||||
# Slightly randomize the retry delay so that, in the case of a total
|
||||
# dns outage, all starting services do not pressure the dns server at the same time.
|
||||
sleep(rand(RETRY_DELAY_RANGE))
|
||||
end
|
||||
|
||||
interval
|
||||
end
|
||||
|
@ -99,7 +111,22 @@ module Gitlab
|
|||
|
||||
current = addresses_from_load_balancer
|
||||
|
||||
replace_hosts(from_dns) if from_dns != current
|
||||
if from_dns != current
|
||||
::Gitlab::Database::LoadBalancing::Logger.info(
|
||||
event: :host_list_update,
|
||||
message: "Updating the host list for service discovery",
|
||||
host_list_length: from_dns.length,
|
||||
old_host_list_length: current.length
|
||||
)
|
||||
replace_hosts(from_dns)
|
||||
else
|
||||
::Gitlab::Database::LoadBalancing::Logger.info(
|
||||
event: :host_list_unchanged,
|
||||
message: "Unchanged host list for service discovery",
|
||||
host_list_length: from_dns.length,
|
||||
old_host_list_length: current.length
|
||||
)
|
||||
end
|
||||
|
||||
interval
|
||||
end
|
||||
|
@ -141,6 +168,8 @@ module Gitlab
|
|||
addresses_from_srv_record(response)
|
||||
end
|
||||
|
||||
raise EmptyDnsResponse if addresses.empty?
|
||||
|
||||
# Addresses are sorted so we can directly compare the old and new
|
||||
# addresses, without having to use any additional data structures.
|
||||
[new_wait_time_for(resources), addresses.sort]
|
||||
|
|
|
@ -6,9 +6,8 @@ module Gitlab
|
|||
class Context
|
||||
attr_reader :context
|
||||
|
||||
LOG_DEPTH_THRESHOLD = 4 # 3 nested subtransactions + 1 real transaction
|
||||
LOG_SAVEPOINTS_THRESHOLD = 5 # 5 `SAVEPOINTS` created in sequence or nested
|
||||
LOG_DURATION_S_THRESHOLD = 120 # 2 minutes long transaction
|
||||
LOG_SAVEPOINTS_THRESHOLD = 1 # 1 `SAVEPOINT` created in a transaction
|
||||
LOG_DURATION_S_THRESHOLD = 120 # transaction that is running for 2 minutes or longer
|
||||
LOG_THROTTLE_DURATION = 1
|
||||
|
||||
def initialize
|
||||
|
@ -19,6 +18,10 @@ module Gitlab
|
|||
@context[:start_time] = current_timestamp
|
||||
end
|
||||
|
||||
def set_depth(depth)
|
||||
@context[:depth] = [@context[:depth].to_i, depth].max
|
||||
end
|
||||
|
||||
def increment_savepoints
|
||||
@context[:savepoints] = @context[:savepoints].to_i + 1
|
||||
end
|
||||
|
@ -31,10 +34,6 @@ module Gitlab
|
|||
@context[:releases] = @context[:releases].to_i + 1
|
||||
end
|
||||
|
||||
def set_depth(depth)
|
||||
@context[:depth] = [@context[:depth].to_i, depth].max
|
||||
end
|
||||
|
||||
def track_sql(sql)
|
||||
(@context[:queries] ||= []).push(sql)
|
||||
end
|
||||
|
@ -45,10 +44,6 @@ module Gitlab
|
|||
current_timestamp - @context[:start_time]
|
||||
end
|
||||
|
||||
def depth_threshold_exceeded?
|
||||
@context[:depth].to_i >= LOG_DEPTH_THRESHOLD
|
||||
end
|
||||
|
||||
def savepoints_threshold_exceeded?
|
||||
@context[:savepoints].to_i >= LOG_SAVEPOINTS_THRESHOLD
|
||||
end
|
||||
|
@ -57,16 +52,10 @@ module Gitlab
|
|||
duration.to_i >= LOG_DURATION_S_THRESHOLD
|
||||
end
|
||||
|
||||
def log_savepoints?
|
||||
depth_threshold_exceeded? || savepoints_threshold_exceeded?
|
||||
end
|
||||
|
||||
def log_duration?
|
||||
duration_threshold_exceeded?
|
||||
end
|
||||
|
||||
def should_log?
|
||||
!logged_already? && (log_savepoints? || log_duration?)
|
||||
return false if logged_already?
|
||||
|
||||
savepoints_threshold_exceeded? || duration_threshold_exceeded?
|
||||
end
|
||||
|
||||
def commit
|
||||
|
|
|
@ -21,7 +21,7 @@ module Gitlab
|
|||
context.set_start_time
|
||||
context.set_depth(0)
|
||||
context.track_sql(event.payload[:sql])
|
||||
elsif cmd.start_with?('SAVEPOINT ')
|
||||
elsif cmd.start_with?('SAVEPOINT', 'EXCEPTION')
|
||||
context.set_depth(manager.open_transactions)
|
||||
context.increment_savepoints
|
||||
elsif cmd.start_with?('ROLLBACK TO SAVEPOINT')
|
||||
|
|
|
@ -898,17 +898,7 @@ module Gitlab
|
|||
# This guard avoids Gitaly log/error spam
|
||||
raise NoRepository, 'repository does not exist' unless exists?
|
||||
|
||||
if Feature.enabled?(:set_full_path)
|
||||
gitaly_repository_client.set_full_path(full_path)
|
||||
else
|
||||
set_config('gitlab.fullpath' => full_path)
|
||||
end
|
||||
end
|
||||
|
||||
def set_config(entries)
|
||||
wrapped_gitaly_errors do
|
||||
gitaly_repository_client.set_config(entries)
|
||||
end
|
||||
gitaly_repository_client.set_full_path(full_path)
|
||||
end
|
||||
|
||||
def disconnect_alternates
|
||||
|
|
|
@ -264,25 +264,6 @@ module Gitlab
|
|||
nil
|
||||
end
|
||||
|
||||
def set_config(entries)
|
||||
return if entries.empty?
|
||||
|
||||
request = Gitaly::SetConfigRequest.new(repository: @gitaly_repo)
|
||||
entries.each do |key, value|
|
||||
request.entries << build_set_config_entry(key, value)
|
||||
end
|
||||
|
||||
GitalyClient.call(
|
||||
@storage,
|
||||
:repository_service,
|
||||
:set_config,
|
||||
request,
|
||||
timeout: GitalyClient.fast_timeout
|
||||
)
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def license_short_name
|
||||
request = Gitaly::FindLicenseRequest.new(repository: @gitaly_repo)
|
||||
|
||||
|
|
|
@ -4379,6 +4379,9 @@ msgstr ""
|
|||
msgid "Are you sure you want to %{action} %{name}?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Are you sure you want to attempt to merge?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Are you sure you want to cancel editing this comment?"
|
||||
msgstr ""
|
||||
|
||||
|
@ -20784,6 +20787,9 @@ msgstr ""
|
|||
msgid "MemberInviteEmail|%{member_name} invited you to join GitLab"
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberInviteEmail|I've invited you to join me in GitLab"
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberInviteEmail|Invitation to join the %{project_or_group} %{project_or_group_name}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -33334,6 +33340,9 @@ msgstr ""
|
|||
msgid "The latest pipeline for this merge request did not complete successfully."
|
||||
msgstr ""
|
||||
|
||||
msgid "The latest pipeline for this merge request has failed."
|
||||
msgstr ""
|
||||
|
||||
msgid "The license key is invalid. Make sure it is exactly as you received it from GitLab Inc."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -23,10 +23,6 @@ RSpec.describe Admin::RunnersController do
|
|||
describe '#show' do
|
||||
render_views
|
||||
|
||||
before do
|
||||
stub_feature_flags(runner_detailed_view_vue_ui: false)
|
||||
end
|
||||
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:project_two) { create(:project) }
|
||||
|
||||
|
@ -61,30 +57,6 @@ RSpec.describe Admin::RunnersController do
|
|||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
||||
describe 'Cost factors values' do
|
||||
context 'when it is Gitlab.com' do
|
||||
before do
|
||||
expect(Gitlab).to receive(:com?).at_least(:once) { true }
|
||||
end
|
||||
|
||||
it 'renders cost factors fields' do
|
||||
get :show, params: { id: runner.id }
|
||||
|
||||
expect(response.body).to match /Private projects Minutes cost factor/
|
||||
expect(response.body).to match /Public projects Minutes cost factor/
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is not Gitlab.com' do
|
||||
it 'does not show cost factor fields' do
|
||||
get :show, params: { id: runner.id }
|
||||
|
||||
expect(response.body).not_to match /Private projects Minutes cost factor/
|
||||
expect(response.body).not_to match /Public projects Minutes cost factor/
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update' do
|
||||
|
|
|
@ -120,6 +120,29 @@ RSpec.describe InvitesController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when it is part of the invite_email_from experiment' do
|
||||
let(:extra_params) { { invite_type: 'initial_email', experiment_name: 'invite_email_from' } }
|
||||
|
||||
it 'tracks the initial join click from email' do
|
||||
experiment = double(track: true)
|
||||
allow(controller).to receive(:experiment).with(:invite_email_from, actor: member).and_return(experiment)
|
||||
|
||||
request
|
||||
|
||||
expect(experiment).to have_received(:track).with(:join_clicked)
|
||||
end
|
||||
|
||||
context 'when member does not exist' do
|
||||
let(:raw_invite_token) { '_bogus_token_' }
|
||||
|
||||
it 'does not track the experiment' do
|
||||
expect(controller).not_to receive(:experiment).with(:invite_email_from, actor: member)
|
||||
|
||||
request
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when member does not exist' do
|
||||
let(:raw_invite_token) { '_bogus_token_' }
|
||||
|
||||
|
@ -147,8 +170,9 @@ RSpec.describe InvitesController do
|
|||
end
|
||||
|
||||
context 'when it is not part of our invite email experiment' do
|
||||
it 'does not track via experiment' do
|
||||
it 'does not track via experiment', :aggregate_failures do
|
||||
expect(controller).not_to receive(:experiment).with(:invite_email_preview_text, actor: member)
|
||||
expect(controller).not_to receive(:experiment).with(:invite_email_from, actor: member)
|
||||
|
||||
request
|
||||
end
|
||||
|
|
|
@ -227,6 +227,40 @@ RSpec.describe RegistrationsController do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the invite_email_preview_text experiment', :experiment do
|
||||
let(:extra_session_params) { { invite_email_experiment_name: 'invite_email_from' } }
|
||||
|
||||
context 'when member and invite_email_experiment_name exists from the session key value' do
|
||||
it 'tracks the invite acceptance' do
|
||||
expect(experiment(:invite_email_from)).to track(:accepted)
|
||||
.with_context(actor: member)
|
||||
.on_next_instance
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when member does not exist from the session key value' do
|
||||
let(:originating_member_id) { -1 }
|
||||
|
||||
it 'does not track invite acceptance' do
|
||||
expect(experiment(:invite_email_from)).not_to track(:accepted)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invite_email_experiment_name does not exist from the session key value' do
|
||||
let(:extra_session_params) { {} }
|
||||
|
||||
it 'does not track invite acceptance' do
|
||||
expect(experiment(:invite_email_from)).not_to track(:accepted)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invite email matches email used on registration' do
|
||||
|
|
|
@ -216,6 +216,20 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with invite email acceptance for the invite_email_from experiment', :experiment do
|
||||
let(:extra_params) do
|
||||
{ invite_type: Emails::Members::INITIAL_INVITE, experiment_name: 'invite_email_from' }
|
||||
end
|
||||
|
||||
it 'tracks the accepted invite' do
|
||||
expect(experiment(:invite_email_from)).to track(:accepted)
|
||||
.with_context(actor: group_invite)
|
||||
.on_next_instance
|
||||
|
||||
fill_in_sign_up_form(new_user)
|
||||
end
|
||||
end
|
||||
|
||||
it 'signs up and redirects to the group activity page with all the project/groups invitation automatically accepted' do
|
||||
fill_in_sign_up_form(new_user)
|
||||
fill_in_welcome_form
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { GlIntersectionObserver } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { nextTick } from 'vue';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import '~/behaviors/markdown/render_gfm';
|
||||
import IssuableApp from '~/issue_show/components/app.vue';
|
||||
import DescriptionComponent from '~/issue_show/components/description.vue';
|
||||
|
@ -33,13 +34,17 @@ describe('Issuable output', () => {
|
|||
let realtimeRequestCount = 0;
|
||||
let wrapper;
|
||||
|
||||
const findStickyHeader = () => wrapper.find('[data-testid="issue-sticky-header"]');
|
||||
const findLockedBadge = () => wrapper.find('[data-testid="locked"]');
|
||||
const findConfidentialBadge = () => wrapper.find('[data-testid="confidential"]');
|
||||
const findStickyHeader = () => wrapper.findByTestId('issue-sticky-header');
|
||||
const findLockedBadge = () => wrapper.findByTestId('locked');
|
||||
const findConfidentialBadge = () => wrapper.findByTestId('confidential');
|
||||
const findHiddenBadge = () => wrapper.findByTestId('hidden');
|
||||
const findAlert = () => wrapper.find('.alert');
|
||||
|
||||
const mountComponent = (props = {}, options = {}, data = {}) => {
|
||||
wrapper = mount(IssuableApp, {
|
||||
wrapper = mountExtended(IssuableApp, {
|
||||
directives: {
|
||||
GlTooltip: createMockDirective(),
|
||||
},
|
||||
propsData: { ...appProps, ...props },
|
||||
provide: {
|
||||
fullPath: 'gitlab-org/incidents',
|
||||
|
@ -539,8 +544,8 @@ describe('Issuable output', () => {
|
|||
|
||||
it.each`
|
||||
title | isConfidential
|
||||
${'does not show confidential badge when issue is not confidential'} | ${true}
|
||||
${'shows confidential badge when issue is confidential'} | ${false}
|
||||
${'does not show confidential badge when issue is not confidential'} | ${false}
|
||||
${'shows confidential badge when issue is confidential'} | ${true}
|
||||
`('$title', async ({ isConfidential }) => {
|
||||
wrapper.setProps({ isConfidential });
|
||||
|
||||
|
@ -551,8 +556,8 @@ describe('Issuable output', () => {
|
|||
|
||||
it.each`
|
||||
title | isLocked
|
||||
${'does not show locked badge when issue is not locked'} | ${true}
|
||||
${'shows locked badge when issue is locked'} | ${false}
|
||||
${'does not show locked badge when issue is not locked'} | ${false}
|
||||
${'shows locked badge when issue is locked'} | ${true}
|
||||
`('$title', async ({ isLocked }) => {
|
||||
wrapper.setProps({ isLocked });
|
||||
|
||||
|
@ -560,6 +565,27 @@ describe('Issuable output', () => {
|
|||
|
||||
expect(findLockedBadge().exists()).toBe(isLocked);
|
||||
});
|
||||
|
||||
it.each`
|
||||
title | isHidden
|
||||
${'does not show hidden badge when issue is not hidden'} | ${false}
|
||||
${'shows hidden badge when issue is hidden'} | ${true}
|
||||
`('$title', async ({ isHidden }) => {
|
||||
wrapper.setProps({ isHidden });
|
||||
|
||||
await nextTick();
|
||||
|
||||
const hiddenBadge = findHiddenBadge();
|
||||
|
||||
expect(hiddenBadge.exists()).toBe(isHidden);
|
||||
|
||||
if (isHidden) {
|
||||
expect(hiddenBadge.attributes('title')).toBe(
|
||||
'This issue is hidden because its author has been banned',
|
||||
);
|
||||
expect(getBinding(hiddenBadge.element, 'gl-tooltip')).not.toBeUndefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ describe('RunnerUpdateForm', () => {
|
|||
? ACCESS_LEVEL_REF_PROTECTED
|
||||
: ACCESS_LEVEL_NOT_PROTECTED,
|
||||
runUntagged: findRunUntaggedCheckbox().element.checked,
|
||||
locked: findLockedCheckbox().element.checked,
|
||||
locked: findLockedCheckbox().element?.checked || false,
|
||||
ipAddress: findIpInput().element.value,
|
||||
maximumTimeout: findMaxJobTimeoutInput().element.value || null,
|
||||
tagList: findTagsInput().element.value.split(',').filter(Boolean),
|
||||
|
@ -153,15 +153,15 @@ describe('RunnerUpdateForm', () => {
|
|||
});
|
||||
|
||||
it.each`
|
||||
runnerType | attrDisabled | outcome
|
||||
${INSTANCE_TYPE} | ${'disabled'} | ${'disabled'}
|
||||
${GROUP_TYPE} | ${'disabled'} | ${'disabled'}
|
||||
${PROJECT_TYPE} | ${undefined} | ${'enabled'}
|
||||
`(`When runner is $runnerType, locked field is $outcome`, ({ runnerType, attrDisabled }) => {
|
||||
runnerType | exists | outcome
|
||||
${INSTANCE_TYPE} | ${false} | ${'hidden'}
|
||||
${GROUP_TYPE} | ${false} | ${'hidden'}
|
||||
${PROJECT_TYPE} | ${true} | ${'shown'}
|
||||
`(`When runner is $runnerType, locked field is $outcome`, ({ runnerType, exists }) => {
|
||||
const runner = { ...mockRunner, runnerType };
|
||||
createComponent({ props: { runner } });
|
||||
|
||||
expect(findLockedCheckbox().attributes('disabled')).toBe(attrDisabled);
|
||||
expect(findLockedCheckbox().exists()).toBe(exists);
|
||||
});
|
||||
|
||||
describe('On submit, runner gets updated', () => {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import { createLocalVue } from '@vue/test-utils';
|
||||
import Vuex from 'vuex';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { createStore as createMrStore } from '~/mr_notes/stores';
|
||||
import createIssueStore from '~/notes/stores';
|
||||
import IssuableHeaderWarnings from '~/vue_shared/components/issuable/issuable_header_warnings.vue';
|
||||
|
@ -12,52 +14,53 @@ localVue.use(Vuex);
|
|||
|
||||
describe('IssuableHeaderWarnings', () => {
|
||||
let wrapper;
|
||||
let store;
|
||||
|
||||
const findConfidentialIcon = () => wrapper.find('[data-testid="confidential"]');
|
||||
const findLockedIcon = () => wrapper.find('[data-testid="locked"]');
|
||||
const findConfidentialIcon = () => wrapper.findByTestId('confidential');
|
||||
const findLockedIcon = () => wrapper.findByTestId('locked');
|
||||
const findHiddenIcon = () => wrapper.findByTestId('hidden');
|
||||
|
||||
const renderTestMessage = (renders) => (renders ? 'renders' : 'does not render');
|
||||
|
||||
const setLock = (locked) => {
|
||||
store.getters.getNoteableData.discussion_locked = locked;
|
||||
};
|
||||
|
||||
const setConfidential = (confidential) => {
|
||||
store.getters.getNoteableData.confidential = confidential;
|
||||
};
|
||||
|
||||
const createComponent = () => {
|
||||
wrapper = shallowMount(IssuableHeaderWarnings, { store, localVue });
|
||||
const createComponent = ({ store, provide }) => {
|
||||
wrapper = shallowMountExtended(IssuableHeaderWarnings, {
|
||||
store,
|
||||
localVue,
|
||||
provide,
|
||||
directives: {
|
||||
GlTooltip: createMockDirective(),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
store = null;
|
||||
});
|
||||
|
||||
describe.each`
|
||||
issuableType
|
||||
${ISSUABLE_TYPE_ISSUE} | ${ISSUABLE_TYPE_MR}
|
||||
`(`when issuableType=$issuableType`, ({ issuableType }) => {
|
||||
beforeEach(() => {
|
||||
store = issuableType === ISSUABLE_TYPE_ISSUE ? createIssueStore() : createMrStore();
|
||||
createComponent();
|
||||
});
|
||||
|
||||
describe.each`
|
||||
lockStatus | confidentialStatus
|
||||
${true} | ${true}
|
||||
${true} | ${false}
|
||||
${false} | ${true}
|
||||
${false} | ${false}
|
||||
lockStatus | confidentialStatus | hiddenStatus
|
||||
${true} | ${true} | ${false}
|
||||
${true} | ${false} | ${false}
|
||||
${false} | ${true} | ${false}
|
||||
${false} | ${false} | ${false}
|
||||
${true} | ${true} | ${true}
|
||||
${true} | ${false} | ${true}
|
||||
${false} | ${true} | ${true}
|
||||
${false} | ${false} | ${true}
|
||||
`(
|
||||
`when locked=$lockStatus and confidential=$confidentialStatus`,
|
||||
({ lockStatus, confidentialStatus }) => {
|
||||
`when locked=$lockStatus, confidential=$confidentialStatus, and hidden=$hiddenStatus`,
|
||||
({ lockStatus, confidentialStatus, hiddenStatus }) => {
|
||||
const store = issuableType === ISSUABLE_TYPE_ISSUE ? createIssueStore() : createMrStore();
|
||||
|
||||
beforeEach(() => {
|
||||
setLock(lockStatus);
|
||||
setConfidential(confidentialStatus);
|
||||
store.getters.getNoteableData.confidential = confidentialStatus;
|
||||
store.getters.getNoteableData.discussion_locked = lockStatus;
|
||||
|
||||
createComponent({ store, provide: { hidden: hiddenStatus } });
|
||||
});
|
||||
|
||||
it(`${renderTestMessage(lockStatus)} the locked icon`, () => {
|
||||
|
@ -67,6 +70,19 @@ describe('IssuableHeaderWarnings', () => {
|
|||
it(`${renderTestMessage(confidentialStatus)} the confidential icon`, () => {
|
||||
expect(findConfidentialIcon().exists()).toBe(confidentialStatus);
|
||||
});
|
||||
|
||||
it(`${renderTestMessage(confidentialStatus)} the hidden icon`, () => {
|
||||
const hiddenIcon = findHiddenIcon();
|
||||
|
||||
expect(hiddenIcon.exists()).toBe(hiddenStatus);
|
||||
|
||||
if (hiddenStatus) {
|
||||
expect(hiddenIcon.attributes('title')).toBe(
|
||||
'This issue is hidden because its author has been banned',
|
||||
);
|
||||
expect(getBinding(hiddenIcon.element, 'gl-tooltip')).not.toBeUndefined();
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
@ -285,7 +285,8 @@ RSpec.describe IssuablesHelper do
|
|||
initialDescriptionText: 'issue text',
|
||||
initialTaskStatus: '0 of 0 tasks completed',
|
||||
issueType: 'issue',
|
||||
iid: issue.iid.to_s
|
||||
iid: issue.iid.to_s,
|
||||
isHidden: false
|
||||
}
|
||||
expect(helper.issuable_initial_data(issue)).to match(hash_including(expected_data))
|
||||
end
|
||||
|
|
|
@ -410,4 +410,55 @@ RSpec.describe IssuesHelper do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#issue_hidden?' do
|
||||
context 'when issue is hidden' do
|
||||
let_it_be(:banned_user) { build(:user, :banned) }
|
||||
let_it_be(:hidden_issue) { build(:issue, author: banned_user) }
|
||||
|
||||
context 'when `ban_user_feature_flag` feature flag is enabled' do
|
||||
it 'returns `true`' do
|
||||
expect(helper.issue_hidden?(hidden_issue)).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when `ban_user_feature_flag` feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(ban_user_feature_flag: false)
|
||||
end
|
||||
|
||||
it 'returns `false`' do
|
||||
expect(helper.issue_hidden?(hidden_issue)).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when issue is not hidden' do
|
||||
it 'returns `false`' do
|
||||
expect(helper.issue_hidden?(issue)).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#hidden_issue_icon' do
|
||||
let_it_be(:banned_user) { build(:user, :banned) }
|
||||
let_it_be(:hidden_issue) { build(:issue, author: banned_user) }
|
||||
let_it_be(:mock_svg) { '<svg></svg>'.html_safe }
|
||||
|
||||
before do
|
||||
allow(helper).to receive(:sprite_icon).and_return(mock_svg)
|
||||
end
|
||||
|
||||
context 'when issue is hidden' do
|
||||
it 'returns icon with tooltip' do
|
||||
expect(helper.hidden_issue_icon(hidden_issue)).to eq("<span class=\"has-tooltip\" title=\"This issue is hidden because its author has been banned\">#{mock_svg}</span>")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when issue is not hidden' do
|
||||
it 'returns `nil`' do
|
||||
expect(helper.hidden_issue_icon(issue)).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -70,6 +70,28 @@ RSpec.describe NotifyHelper do
|
|||
expect(helper.invited_join_url(token, member))
|
||||
.to eq("http://test.host/-/invites/#{token}?experiment_name=invite_email_preview_text&invite_type=initial_email")
|
||||
end
|
||||
|
||||
context 'when invite_email_from is enabled' do
|
||||
before do
|
||||
stub_experiments(invite_email_from: :control)
|
||||
end
|
||||
|
||||
it 'has correct params' do
|
||||
expect(helper.invited_join_url(token, member))
|
||||
.to eq("http://test.host/-/invites/#{token}?experiment_name=invite_email_from&invite_type=initial_email")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invite_email_from is enabled' do
|
||||
before do
|
||||
stub_experiments(invite_email_from: :control)
|
||||
end
|
||||
|
||||
it 'has correct params' do
|
||||
expect(helper.invited_join_url(token, member))
|
||||
.to eq("http://test.host/-/invites/#{token}?experiment_name=invite_email_from&invite_type=initial_email")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invite_email_preview_text is disabled' do
|
||||
|
|
|
@ -69,18 +69,69 @@ RSpec.describe Gitlab::Database::LoadBalancing::ServiceDiscovery do
|
|||
end
|
||||
|
||||
describe '#perform_service_discovery' do
|
||||
it 'reports exceptions to Sentry' do
|
||||
error = StandardError.new
|
||||
context 'without any failures' do
|
||||
it 'runs once' do
|
||||
expect(service)
|
||||
.to receive(:refresh_if_necessary).once
|
||||
|
||||
expect(service)
|
||||
.to receive(:refresh_if_necessary)
|
||||
.and_raise(error)
|
||||
expect(service).not_to receive(:sleep)
|
||||
|
||||
expect(Gitlab::ErrorTracking)
|
||||
.to receive(:track_exception)
|
||||
.with(error)
|
||||
expect(Gitlab::ErrorTracking).not_to receive(:track_exception)
|
||||
|
||||
service.perform_service_discovery
|
||||
service.perform_service_discovery
|
||||
end
|
||||
end
|
||||
context 'with failures' do
|
||||
before do
|
||||
allow(Gitlab::ErrorTracking).to receive(:track_exception)
|
||||
allow(service).to receive(:sleep)
|
||||
end
|
||||
|
||||
let(:valid_retry_sleep_duration) { satisfy { |val| described_class::RETRY_DELAY_RANGE.include?(val) } }
|
||||
|
||||
it 'retries service discovery when under the retry limit' do
|
||||
error = StandardError.new
|
||||
|
||||
expect(service)
|
||||
.to receive(:refresh_if_necessary)
|
||||
.and_raise(error).exactly(described_class::MAX_DISCOVERY_RETRIES - 1).times.ordered
|
||||
|
||||
expect(service)
|
||||
.to receive(:sleep).with(valid_retry_sleep_duration)
|
||||
.exactly(described_class::MAX_DISCOVERY_RETRIES - 1).times
|
||||
|
||||
expect(service).to receive(:refresh_if_necessary).and_return(45).ordered
|
||||
|
||||
expect(service.perform_service_discovery).to eq(45)
|
||||
end
|
||||
|
||||
it 'does not retry service discovery after exceeding the limit' do
|
||||
error = StandardError.new
|
||||
|
||||
expect(service)
|
||||
.to receive(:refresh_if_necessary)
|
||||
.and_raise(error).exactly(described_class::MAX_DISCOVERY_RETRIES).times
|
||||
|
||||
expect(service)
|
||||
.to receive(:sleep).with(valid_retry_sleep_duration)
|
||||
.exactly(described_class::MAX_DISCOVERY_RETRIES).times
|
||||
|
||||
service.perform_service_discovery
|
||||
end
|
||||
|
||||
it 'reports exceptions to Sentry' do
|
||||
error = StandardError.new
|
||||
|
||||
expect(service)
|
||||
.to receive(:refresh_if_necessary)
|
||||
.and_raise(error).exactly(described_class::MAX_DISCOVERY_RETRIES).times
|
||||
|
||||
expect(Gitlab::ErrorTracking)
|
||||
.to receive(:track_exception)
|
||||
.with(error).exactly(described_class::MAX_DISCOVERY_RETRIES).times
|
||||
|
||||
service.perform_service_discovery
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -224,6 +275,16 @@ RSpec.describe Gitlab::Database::LoadBalancing::ServiceDiscovery do
|
|||
expect(service.addresses_from_dns).to eq([90, addresses])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the resolver returns an empty response' do
|
||||
let(:packet) { double(:packet, answer: []) }
|
||||
|
||||
let(:record_type) { 'A' }
|
||||
|
||||
it 'raises EmptyDnsResponse' do
|
||||
expect { service.addresses_from_dns }.to raise_error(Gitlab::Database::LoadBalancing::ServiceDiscovery::EmptyDnsResponse)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#new_wait_time_for' do
|
||||
|
|
|
@ -70,24 +70,6 @@ RSpec.describe Gitlab::Database::Transaction::Context do
|
|||
it { expect(subject.duration).to be >= 0 }
|
||||
end
|
||||
|
||||
context 'when depth is low' do
|
||||
it 'does not log data upon COMMIT' do
|
||||
expect(subject).not_to receive(:application_info)
|
||||
|
||||
subject.commit
|
||||
end
|
||||
|
||||
it 'does not log data upon ROLLBACK' do
|
||||
expect(subject).not_to receive(:application_info)
|
||||
|
||||
subject.rollback
|
||||
end
|
||||
|
||||
it '#should_log? returns false' do
|
||||
expect(subject.should_log?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'logs transaction data' do
|
||||
it 'logs once upon COMMIT' do
|
||||
expect(subject).to receive(:application_info).and_call_original
|
||||
|
@ -116,17 +98,9 @@ RSpec.describe Gitlab::Database::Transaction::Context do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when depth exceeds threshold' do
|
||||
before do
|
||||
subject.set_depth(described_class::LOG_DEPTH_THRESHOLD + 1)
|
||||
end
|
||||
|
||||
it_behaves_like 'logs transaction data'
|
||||
end
|
||||
|
||||
context 'when savepoints count exceeds threshold' do
|
||||
before do
|
||||
data[:savepoints] = described_class::LOG_SAVEPOINTS_THRESHOLD + 1
|
||||
data[:savepoints] = 1
|
||||
end
|
||||
|
||||
it_behaves_like 'logs transaction data'
|
||||
|
|
|
@ -1710,83 +1710,42 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
|
|||
end
|
||||
|
||||
describe '#set_full_path' do
|
||||
shared_examples '#set_full_path' do
|
||||
before do
|
||||
repository_rugged.config["gitlab.fullpath"] = repository_path
|
||||
end
|
||||
before do
|
||||
repository_rugged.config["gitlab.fullpath"] = repository_path
|
||||
end
|
||||
|
||||
context 'is given a path' do
|
||||
it 'writes it to disk' do
|
||||
repository.set_full_path(full_path: "not-the/real-path.git")
|
||||
context 'is given a path' do
|
||||
it 'writes it to disk' do
|
||||
repository.set_full_path(full_path: "not-the/real-path.git")
|
||||
|
||||
config = File.read(File.join(repository_path, "config"))
|
||||
config = File.read(File.join(repository_path, "config"))
|
||||
|
||||
expect(config).to include("[gitlab]")
|
||||
expect(config).to include("fullpath = not-the/real-path.git")
|
||||
end
|
||||
end
|
||||
|
||||
context 'it is given an empty path' do
|
||||
it 'does not write it to disk' do
|
||||
repository.set_full_path(full_path: "")
|
||||
|
||||
config = File.read(File.join(repository_path, "config"))
|
||||
|
||||
expect(config).to include("[gitlab]")
|
||||
expect(config).to include("fullpath = #{repository_path}")
|
||||
end
|
||||
end
|
||||
|
||||
context 'repository does not exist' do
|
||||
it 'raises NoRepository and does not call Gitaly WriteConfig' do
|
||||
repository = Gitlab::Git::Repository.new('default', 'does/not/exist.git', '', 'group/project')
|
||||
|
||||
expect(repository.gitaly_repository_client).not_to receive(:set_full_path)
|
||||
|
||||
expect do
|
||||
repository.set_full_path(full_path: 'foo/bar.git')
|
||||
end.to raise_error(Gitlab::Git::Repository::NoRepository)
|
||||
end
|
||||
expect(config).to include("[gitlab]")
|
||||
expect(config).to include("fullpath = not-the/real-path.git")
|
||||
end
|
||||
end
|
||||
|
||||
context 'with :set_full_path enabled' do
|
||||
before do
|
||||
stub_feature_flags(set_full_path: true)
|
||||
context 'it is given an empty path' do
|
||||
it 'does not write it to disk' do
|
||||
repository.set_full_path(full_path: "")
|
||||
|
||||
config = File.read(File.join(repository_path, "config"))
|
||||
|
||||
expect(config).to include("[gitlab]")
|
||||
expect(config).to include("fullpath = #{repository_path}")
|
||||
end
|
||||
|
||||
it_behaves_like '#set_full_path'
|
||||
end
|
||||
|
||||
context 'with :set_full_path disabled' do
|
||||
before do
|
||||
stub_feature_flags(set_full_path: false)
|
||||
context 'repository does not exist' do
|
||||
it 'raises NoRepository and does not call Gitaly WriteConfig' do
|
||||
repository = Gitlab::Git::Repository.new('default', 'does/not/exist.git', '', 'group/project')
|
||||
|
||||
expect(repository.gitaly_repository_client).not_to receive(:set_full_path)
|
||||
|
||||
expect do
|
||||
repository.set_full_path(full_path: 'foo/bar.git')
|
||||
end.to raise_error(Gitlab::Git::Repository::NoRepository)
|
||||
end
|
||||
|
||||
it_behaves_like '#set_full_path'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#set_config' do
|
||||
let(:repository) { mutable_repository }
|
||||
let(:entries) do
|
||||
{
|
||||
'test.foo1' => 'bla bla',
|
||||
'test.foo2' => 1234,
|
||||
'test.foo3' => true
|
||||
}
|
||||
end
|
||||
|
||||
it 'can set config settings' do
|
||||
expect(repository.set_config(entries)).to be_nil
|
||||
|
||||
expect(repository_rugged.config['test.foo1']).to eq('bla bla')
|
||||
expect(repository_rugged.config['test.foo2']).to eq('1234')
|
||||
expect(repository_rugged.config['test.foo3']).to eq('true')
|
||||
end
|
||||
|
||||
after do
|
||||
entries.keys.each { |k| repository_rugged.config.delete(k) }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -834,6 +834,35 @@ RSpec.describe Notify do
|
|||
invite_type: Emails::Members::INITIAL_INVITE,
|
||||
experiment_name: 'invite_email_preview_text'))
|
||||
end
|
||||
|
||||
it 'tracks the sent invite' do
|
||||
expect(experiment(:invite_email_preview_text)).to track(:assignment)
|
||||
.with_context(actor: project_member)
|
||||
.on_next_instance
|
||||
|
||||
invite_email.deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invite_email_from enabled', :experiment do
|
||||
before do
|
||||
stub_experiments(invite_email_from: :control)
|
||||
end
|
||||
|
||||
it 'has the correct invite_url with params' do
|
||||
is_expected.to have_link('Join now',
|
||||
href: invite_url(project_member.invite_token,
|
||||
invite_type: Emails::Members::INITIAL_INVITE,
|
||||
experiment_name: 'invite_email_from'))
|
||||
end
|
||||
|
||||
it 'tracks the sent invite' do
|
||||
expect(experiment(:invite_email_from)).to track(:assignment)
|
||||
.with_context(actor: project_member)
|
||||
.on_next_instance
|
||||
|
||||
invite_email.deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invite email sent is tracked', :snowplow do
|
||||
|
|
|
@ -27,17 +27,17 @@ RSpec.describe IssuePolicy do
|
|||
end
|
||||
|
||||
it 'allows support_bot to read issues, create and set metadata on new issues' do
|
||||
expect(permissions(support_bot, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(support_bot, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(support_bot, new_issue)).to be_allowed(:create_issue, :set_issue_metadata)
|
||||
expect(permissions(support_bot, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(support_bot, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(support_bot, new_issue)).to be_allowed(:create_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'support bot with service desk disabled' do
|
||||
it 'allows support_bot to read issues, create and set metadata on new issues' do
|
||||
expect(permissions(support_bot, issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(support_bot, issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(support_bot, new_issue)).to be_disallowed(:create_issue, :set_issue_metadata)
|
||||
it 'does not allow support_bot to read issues, create and set metadata on new issues' do
|
||||
expect(permissions(support_bot, issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(support_bot, issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(support_bot, new_issue)).to be_disallowed(:create_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -60,50 +60,50 @@ RSpec.describe IssuePolicy do
|
|||
|
||||
it 'allows guests to read issues' do
|
||||
expect(permissions(guest, issue)).to be_allowed(:read_issue, :read_issue_iid)
|
||||
expect(permissions(guest, issue)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(guest, issue)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
|
||||
expect(permissions(guest, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid)
|
||||
expect(permissions(guest, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(guest, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
|
||||
expect(permissions(guest, new_issue)).to be_allowed(:create_issue, :set_issue_metadata)
|
||||
expect(permissions(guest, new_issue)).to be_allowed(:create_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'allows reporters to read, update, and admin issues' do
|
||||
expect(permissions(reporter, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(reporter, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(reporter, new_issue)).to be_allowed(:create_issue, :set_issue_metadata)
|
||||
expect(permissions(reporter, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(reporter, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(reporter, new_issue)).to be_allowed(:create_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'allows reporters from group links to read, update, and admin issues' do
|
||||
expect(permissions(reporter_from_group_link, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(reporter_from_group_link, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(reporter_from_group_link, new_issue)).to be_allowed(:create_issue, :set_issue_metadata)
|
||||
expect(permissions(reporter_from_group_link, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(reporter_from_group_link, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(reporter_from_group_link, new_issue)).to be_allowed(:create_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'allows issue authors to read and update their issues' do
|
||||
expect(permissions(author, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue)
|
||||
expect(permissions(author, issue)).to be_disallowed(:admin_issue, :set_issue_metadata)
|
||||
expect(permissions(author, issue)).to be_disallowed(:admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
|
||||
expect(permissions(author, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid)
|
||||
expect(permissions(author, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(author, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
|
||||
expect(permissions(author, new_issue)).to be_allowed(:create_issue, :set_issue_metadata)
|
||||
expect(permissions(author, new_issue)).to be_allowed(:create_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'allows issue assignees to read and update their issues' do
|
||||
expect(permissions(assignee, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue)
|
||||
expect(permissions(assignee, issue)).to be_disallowed(:admin_issue, :set_issue_metadata)
|
||||
expect(permissions(assignee, issue)).to be_disallowed(:admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
|
||||
expect(permissions(assignee, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid)
|
||||
expect(permissions(assignee, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(assignee, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
|
||||
expect(permissions(assignee, new_issue)).to be_allowed(:create_issue, :set_issue_metadata)
|
||||
expect(permissions(assignee, new_issue)).to be_allowed(:create_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'does not allow non-members to read, update or create issues' do
|
||||
expect(permissions(non_member, issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(non_member, issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(non_member, new_issue)).to be_disallowed(:create_issue, :set_issue_metadata)
|
||||
expect(permissions(non_member, issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(non_member, issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(non_member, new_issue)).to be_disallowed(:create_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it_behaves_like 'support bot with service desk disabled'
|
||||
|
@ -115,49 +115,49 @@ RSpec.describe IssuePolicy do
|
|||
|
||||
it 'does not allow non-members to read confidential issues' do
|
||||
expect(permissions(non_member, confidential_issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue)
|
||||
expect(permissions(non_member, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(non_member, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'does not allow guests to read confidential issues' do
|
||||
expect(permissions(guest, confidential_issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue)
|
||||
expect(permissions(guest, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(guest, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'allows reporters to read, update, and admin confidential issues' do
|
||||
expect(permissions(reporter, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(reporter, confidential_issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(reporter, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(reporter, confidential_issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'allows reporters from group links to read, update, and admin confidential issues' do
|
||||
expect(permissions(reporter_from_group_link, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(reporter_from_group_link, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'allows issue authors to read and update their confidential issues' do
|
||||
expect(permissions(author, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue)
|
||||
expect(permissions(author, confidential_issue)).to be_disallowed(:admin_issue, :set_issue_metadata)
|
||||
expect(permissions(author, confidential_issue)).to be_disallowed(:admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
|
||||
expect(permissions(author, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue)
|
||||
expect(permissions(author, confidential_issue_no_assignee)).to be_disallowed(:admin_issue, :set_issue_metadata)
|
||||
expect(permissions(author, confidential_issue_no_assignee)).to be_disallowed(:admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'does not allow issue author to read or update confidential issue moved to an private project' do
|
||||
confidential_issue.project = create(:project, :private)
|
||||
|
||||
expect(permissions(author, confidential_issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :set_issue_metadata)
|
||||
expect(permissions(author, confidential_issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'allows issue assignees to read and update their confidential issues' do
|
||||
expect(permissions(assignee, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue)
|
||||
expect(permissions(assignee, confidential_issue)).to be_disallowed(:admin_issue, :set_issue_metadata)
|
||||
expect(permissions(assignee, confidential_issue)).to be_disallowed(:admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
|
||||
expect(permissions(assignee, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(assignee, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'does not allow issue assignees to read or update confidential issue moved to an private project' do
|
||||
confidential_issue.project = create(:project, :private)
|
||||
|
||||
expect(permissions(assignee, confidential_issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :set_issue_metadata)
|
||||
expect(permissions(assignee, confidential_issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -180,48 +180,48 @@ RSpec.describe IssuePolicy do
|
|||
|
||||
it 'does not allow anonymous user to create todos' do
|
||||
expect(permissions(nil, issue)).to be_allowed(:read_issue)
|
||||
expect(permissions(nil, issue)).to be_disallowed(:create_todo, :update_subscription, :set_issue_metadata)
|
||||
expect(permissions(nil, new_issue)).to be_disallowed(:create_issue, :set_issue_metadata)
|
||||
expect(permissions(nil, issue)).to be_disallowed(:create_todo, :update_subscription, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(nil, new_issue)).to be_disallowed(:create_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'allows guests to read issues' do
|
||||
expect(permissions(guest, issue)).to be_allowed(:read_issue, :read_issue_iid, :create_todo, :update_subscription)
|
||||
expect(permissions(guest, issue)).to be_disallowed(:update_issue, :admin_issue, :reopen_issue, :set_issue_metadata)
|
||||
expect(permissions(guest, issue)).to be_disallowed(:update_issue, :admin_issue, :reopen_issue, :set_issue_metadata, :set_confidentiality)
|
||||
|
||||
expect(permissions(guest, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid)
|
||||
expect(permissions(guest, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :reopen_issue, :set_issue_metadata)
|
||||
expect(permissions(guest, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :reopen_issue, :set_issue_metadata, :set_confidentiality)
|
||||
|
||||
expect(permissions(guest, issue_locked)).to be_allowed(:read_issue, :read_issue_iid)
|
||||
expect(permissions(guest, issue_locked)).to be_disallowed(:update_issue, :admin_issue, :reopen_issue, :set_issue_metadata)
|
||||
expect(permissions(guest, issue_locked)).to be_disallowed(:update_issue, :admin_issue, :reopen_issue, :set_issue_metadata, :set_confidentiality)
|
||||
|
||||
expect(permissions(guest, new_issue)).to be_allowed(:create_issue, :set_issue_metadata)
|
||||
expect(permissions(guest, new_issue)).to be_allowed(:create_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'allows reporters to read, update, reopen, and admin issues' do
|
||||
expect(permissions(reporter, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :reopen_issue, :set_issue_metadata)
|
||||
expect(permissions(reporter, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :reopen_issue, :set_issue_metadata)
|
||||
expect(permissions(reporter, issue_locked)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(reporter, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :reopen_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(reporter, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :reopen_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(reporter, issue_locked)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(reporter, issue_locked)).to be_disallowed(:reopen_issue)
|
||||
expect(permissions(reporter, new_issue)).to be_allowed(:create_issue, :set_issue_metadata)
|
||||
expect(permissions(reporter, new_issue)).to be_allowed(:create_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'allows reporters from group links to read, update, reopen and admin issues' do
|
||||
expect(permissions(reporter_from_group_link, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :reopen_issue, :set_issue_metadata)
|
||||
expect(permissions(reporter_from_group_link, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :reopen_issue, :set_issue_metadata)
|
||||
expect(permissions(reporter_from_group_link, issue_locked)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(reporter_from_group_link, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :reopen_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(reporter_from_group_link, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :reopen_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(reporter_from_group_link, issue_locked)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(reporter_from_group_link, issue_locked)).to be_disallowed(:reopen_issue)
|
||||
expect(permissions(reporter, new_issue)).to be_allowed(:create_issue, :set_issue_metadata)
|
||||
expect(permissions(reporter, new_issue)).to be_allowed(:create_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'allows issue authors to read, reopen and update their issues' do
|
||||
expect(permissions(author, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :reopen_issue)
|
||||
expect(permissions(author, issue)).to be_disallowed(:admin_issue, :set_issue_metadata)
|
||||
expect(permissions(author, issue)).to be_disallowed(:admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
|
||||
expect(permissions(author, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid)
|
||||
expect(permissions(author, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :reopen_issue, :set_issue_metadata)
|
||||
expect(permissions(author, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :reopen_issue, :set_issue_metadata, :set_confidentiality)
|
||||
|
||||
expect(permissions(author, issue_locked)).to be_allowed(:read_issue, :read_issue_iid, :update_issue)
|
||||
expect(permissions(author, issue_locked)).to be_disallowed(:admin_issue, :reopen_issue, :set_issue_metadata)
|
||||
expect(permissions(author, issue_locked)).to be_disallowed(:admin_issue, :reopen_issue, :set_issue_metadata, :set_confidentiality)
|
||||
|
||||
expect(permissions(author, new_issue)).to be_allowed(:create_issue)
|
||||
expect(permissions(author, new_issue)).to be_disallowed(:set_issue_metadata)
|
||||
|
@ -229,13 +229,13 @@ RSpec.describe IssuePolicy do
|
|||
|
||||
it 'allows issue assignees to read, reopen and update their issues' do
|
||||
expect(permissions(assignee, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :reopen_issue)
|
||||
expect(permissions(assignee, issue)).to be_disallowed(:admin_issue, :set_issue_metadata)
|
||||
expect(permissions(assignee, issue)).to be_disallowed(:admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
|
||||
expect(permissions(assignee, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid)
|
||||
expect(permissions(assignee, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :reopen_issue, :set_issue_metadata)
|
||||
expect(permissions(assignee, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :reopen_issue, :set_issue_metadata, :set_confidentiality)
|
||||
|
||||
expect(permissions(assignee, issue_locked)).to be_allowed(:read_issue, :read_issue_iid, :update_issue)
|
||||
expect(permissions(assignee, issue_locked)).to be_disallowed(:admin_issue, :reopen_issue, :set_issue_metadata)
|
||||
expect(permissions(assignee, issue_locked)).to be_disallowed(:admin_issue, :reopen_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'allows non-members to read and create issues' do
|
||||
|
@ -249,22 +249,25 @@ RSpec.describe IssuePolicy do
|
|||
expect(permissions(non_member, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid)
|
||||
end
|
||||
|
||||
it 'does not allow non-members to update, admin or set metadata' do
|
||||
expect(permissions(non_member, issue)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(non_member, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata)
|
||||
it 'does not allow non-members to update, admin or set metadata except for set confidential flag' do
|
||||
expect(permissions(non_member, issue)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(non_member, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(non_member, new_issue)).to be_disallowed(:set_issue_metadata)
|
||||
# this is allowed for non-members in a public project, as we want to let users report security issues
|
||||
# see https://gitlab.com/gitlab-org/gitlab/-/issues/337665
|
||||
expect(permissions(non_member, new_issue)).to be_allowed(:set_confidentiality)
|
||||
end
|
||||
|
||||
it 'allows support_bot to read issues' do
|
||||
# support_bot is still allowed read access in public projects through :public_access permission,
|
||||
# see project_policy public_access rules policy (rule { can?(:public_access) }.policy {...})
|
||||
expect(permissions(support_bot, issue)).to be_allowed(:read_issue, :read_issue_iid)
|
||||
expect(permissions(support_bot, issue)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(support_bot, issue)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
|
||||
expect(permissions(support_bot, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid)
|
||||
expect(permissions(support_bot, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(support_bot, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
|
||||
expect(permissions(support_bot, new_issue)).to be_disallowed(:create_issue, :set_issue_metadata)
|
||||
expect(permissions(support_bot, new_issue)).to be_disallowed(:create_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it_behaves_like 'support bot with service desk enabled'
|
||||
|
@ -318,9 +321,9 @@ RSpec.describe IssuePolicy do
|
|||
end
|
||||
|
||||
it 'does not allow non-members to update or create issues' do
|
||||
expect(permissions(non_member, issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(non_member, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(non_member, new_issue)).to be_disallowed(:create_issue, :set_issue_metadata)
|
||||
expect(permissions(non_member, issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(non_member, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
expect(permissions(non_member, new_issue)).to be_disallowed(:create_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it_behaves_like 'support bot with service desk disabled'
|
||||
|
@ -333,31 +336,31 @@ RSpec.describe IssuePolicy do
|
|||
|
||||
it 'does not allow guests to read confidential issues' do
|
||||
expect(permissions(guest, confidential_issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue)
|
||||
expect(permissions(guest, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(guest, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'allows reporters to read, update, and admin confidential issues' do
|
||||
expect(permissions(reporter, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue)
|
||||
expect(permissions(reporter, confidential_issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(reporter, confidential_issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'allows reporter from group links to read, update, and admin confidential issues' do
|
||||
expect(permissions(reporter_from_group_link, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue)
|
||||
expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'allows issue authors to read and update their confidential issues' do
|
||||
expect(permissions(author, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue)
|
||||
expect(permissions(author, confidential_issue)).to be_disallowed(:admin_issue, :set_issue_metadata)
|
||||
expect(permissions(author, confidential_issue)).to be_disallowed(:admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
|
||||
expect(permissions(author, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(author, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
|
||||
it 'allows issue assignees to read and update their confidential issues' do
|
||||
expect(permissions(assignee, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue)
|
||||
expect(permissions(assignee, confidential_issue)).to be_disallowed(:admin_issue, :set_issue_metadata)
|
||||
expect(permissions(assignee, confidential_issue)).to be_disallowed(:admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
|
||||
expect(permissions(assignee, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
|
||||
expect(permissions(assignee, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1879,6 +1879,26 @@ RSpec.describe API::Commits do
|
|||
expect(json_response['line_type']).to eq('new')
|
||||
end
|
||||
|
||||
it 'correctly adds a note for the "old" line type' do
|
||||
commit = project.repository.commit("markdown")
|
||||
commit_id = commit.id
|
||||
route = "/projects/#{project_id}/repository/commits/#{commit_id}/comments"
|
||||
|
||||
post api(route, current_user), params: {
|
||||
note: 'My comment',
|
||||
path: commit.raw_diffs.first.old_path,
|
||||
line: 4,
|
||||
line_type: 'old'
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(response).to match_response_schema('public_api/v4/commit_note')
|
||||
expect(json_response['note']).to eq('My comment')
|
||||
expect(json_response['path']).to eq(commit.raw_diffs.first.old_path)
|
||||
expect(json_response['line']).to eq(4)
|
||||
expect(json_response['line_type']).to eq('old')
|
||||
end
|
||||
|
||||
context 'when ref does not exist' do
|
||||
let(:commit_id) { 'unknown' }
|
||||
|
||||
|
|
Loading…
Reference in New Issue