Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
9643359dd3
commit
4e65fc3589
|
|
@ -437,7 +437,6 @@ lib/gitlab/checks/**
|
|||
/doc/administration/maintenance_mode/ @axil
|
||||
/doc/administration/merge_request_diffs.md @aqualls
|
||||
/doc/administration/monitoring/github_imports.md @eread @ashrafkhamis
|
||||
/doc/administration/monitoring/gitlab_self_monitoring_project/ @msedlakjakubowski
|
||||
/doc/administration/monitoring/index.md @msedlakjakubowski
|
||||
/doc/administration/monitoring/ip_allowlist.md @jglassman1
|
||||
/doc/administration/monitoring/performance/gitlab_configuration.md @msedlakjakubowski
|
||||
|
|
@ -565,6 +564,7 @@ lib/gitlab/checks/**
|
|||
/doc/api/license.md @fneill
|
||||
/doc/api/linked_epics.md @msedlakjakubowski
|
||||
/doc/api/lint.md @marcel.amirault
|
||||
/doc/api/managed_licenses.md @fneill
|
||||
/doc/api/markdown.md @msedlakjakubowski
|
||||
/doc/api/member_roles.md @jglassman1
|
||||
/doc/api/members.md @jglassman1
|
||||
|
|
@ -652,6 +652,7 @@ lib/gitlab/checks/**
|
|||
/doc/ci/chatops/ @eread @ashrafkhamis
|
||||
/doc/ci/cloud_deployment/ @phillipwells
|
||||
/doc/ci/cloud_services/ @marcel.amirault
|
||||
/doc/ci/components/ @marcel.amirault
|
||||
/doc/ci/directed_acyclic_graph/ @marcel.amirault
|
||||
/doc/ci/docker/using_docker_images.md @fneill
|
||||
/doc/ci/environments/ @phillipwells
|
||||
|
|
@ -982,6 +983,7 @@ lib/gitlab/checks/**
|
|||
/doc/user/tasks.md @msedlakjakubowski
|
||||
/doc/user/todos.md @msedlakjakubowski
|
||||
/doc/user/usage_quotas.md @fneill
|
||||
/doc/user/workspace/ @ashrafkhamis
|
||||
# End rake-managed-docs-block
|
||||
|
||||
[Authentication and Authorization] @gitlab-org/manage/authentication-and-authorization/approvers
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ trigger-omnibus-env-ce:
|
|||
extends:
|
||||
- .trigger-omnibus-env-ce
|
||||
variables:
|
||||
FOSS_ONLY: "1" # set FOSS_ONLY because we don't pass it via trigger job
|
||||
FOSS_ONLY: "1" # set FOSS_ONLY because we don't pass it via trigger job
|
||||
|
||||
# TODO: enable once ee jobs are added
|
||||
# trigger-omnibus:
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ rails-production-server-boot:
|
|||
- sed --in-place "s:/home/git/gitlab:${PWD}:" config/puma.rb
|
||||
- echo 'bind "tcp://127.0.0.1:3000"' >> config/puma.rb
|
||||
- bundle exec puma --environment production --config config/puma.rb &
|
||||
- sleep 40 # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114124#note_1309506358
|
||||
- sleep 40 # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114124#note_1309506358
|
||||
- retry_times_sleep 10 5 "curl http://127.0.0.1:3000"
|
||||
- kill $(jobs -p)
|
||||
|
||||
|
|
|
|||
|
|
@ -214,9 +214,9 @@ stages:
|
|||
fi
|
||||
- |
|
||||
bundle exec relate-failure-issue \
|
||||
--input-files "$CI_PROJECT_DIR/gitlab-qa-run-*/**/rspec-*.json" \
|
||||
--input-files "${CI_PROJECT_DIR}/gitlab-qa-run-*/**/rspec-*.json" \
|
||||
--project "gitlab-org/gitlab" \
|
||||
--token "$RELATE_TEST_FAILURE_TOKEN"
|
||||
--token "${RELATE_TEST_FAILURE_TOKEN}"
|
||||
|
||||
.generate-test-session:
|
||||
extends:
|
||||
|
|
@ -247,7 +247,6 @@ stages:
|
|||
- .ruby-image
|
||||
stage: notify
|
||||
variables:
|
||||
QA_RSPEC_XML_FILE_PATTERN: $CI_PROJECT_DIR/gitlab-qa-run-*/**/rspec-*.xml
|
||||
SLACK_ICON_EMOJI: ci_failing
|
||||
STATUS_SYM: ☠️
|
||||
STATUS: failed
|
||||
|
|
@ -259,7 +258,7 @@ stages:
|
|||
echo "Test suite passed. Exiting..."
|
||||
exit 0
|
||||
fi
|
||||
- bundle exec gitlab-qa-report --prepare-stage-reports "$QA_RSPEC_XML_FILE_PATTERN" # generate summary
|
||||
- bundle exec prepare-stage-reports --input-files "${CI_PROJECT_DIR}/gitlab-qa-run-*/**/rspec-*.xml"
|
||||
- !reference [.notify-slack-qa, script]
|
||||
|
||||
# ==========================================
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ review-build-cng:
|
|||
.review-workflow-base:
|
||||
image: ${REVIEW_APPS_IMAGE}
|
||||
retry:
|
||||
max: 2 # This is confusing but this means "3 runs at max"
|
||||
max: 2 # This is confusing but this means "3 runs at max"
|
||||
variables:
|
||||
HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
|
||||
DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ notify-slack:
|
|||
TYPE: "(review-app) "
|
||||
when: on_failure
|
||||
script:
|
||||
- bundle exec gitlab-qa-report --prepare-stage-reports "$CI_PROJECT_DIR/qa/tmp/rspec-*.xml" # generate summary
|
||||
- bundle exec prepare-stage-reports --input-files "${CI_PROJECT_DIR}/qa/tmp/rspec-*.xml"
|
||||
- !reference [.notify-slack-qa, script]
|
||||
|
||||
export-test-metrics:
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ Cop/UserAdmin:
|
|||
- 'app/controllers/sessions_controller.rb'
|
||||
- 'app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb'
|
||||
- 'app/graphql/resolvers/admin/analytics/usage_trends/measurements_resolver.rb'
|
||||
- 'app/models/concerns/protected_ref_access.rb'
|
||||
- 'app/models/concerns/spammable.rb'
|
||||
- 'app/models/merge_requests_closing_issues.rb'
|
||||
- 'app/models/protected_branch.rb'
|
||||
|
|
@ -15,7 +16,6 @@ Cop/UserAdmin:
|
|||
- 'app/services/projects/fork_service.rb'
|
||||
- 'app/services/users/build_service.rb'
|
||||
- 'ee/app/controllers/ee/projects_controller.rb'
|
||||
- 'ee/app/models/concerns/ee/protected_ref_access.rb'
|
||||
- 'ee/app/models/ee/user.rb'
|
||||
- 'ee/app/policies/ee/group_policy.rb'
|
||||
- 'ee/app/services/ee/groups/create_service.rb'
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ export default {
|
|||
<gl-loading-icon
|
||||
v-if="labelsFetchInProgress"
|
||||
class="labels-fetch-loading gl-align-items-center gl-w-full gl-h-full gl-mb-3"
|
||||
size="lg"
|
||||
size="sm"
|
||||
/>
|
||||
<template v-else>
|
||||
<gl-dropdown-item
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="gl-mt-3" data-testid="embedded-labels-list">
|
||||
<gl-label
|
||||
v-for="label in sortedSelectedLabels"
|
||||
:key="label.id"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlFormGroup, GlIcon } from '@gitlab/ui';
|
||||
import { GlFormGroup } from '@gitlab/ui';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import LabelsSelect from '~/sidebar/components/labels/labels_select_widget/labels_select_root.vue';
|
||||
import { __ } from '~/locale';
|
||||
|
|
@ -7,7 +7,6 @@ import { __ } from '~/locale';
|
|||
export default {
|
||||
components: {
|
||||
GlFormGroup,
|
||||
GlIcon,
|
||||
LabelsSelect,
|
||||
},
|
||||
inject: [
|
||||
|
|
@ -50,10 +49,6 @@ export default {
|
|||
<gl-form-group class="row" label-class="gl-display-none">
|
||||
<label class="col-12 gl-display-flex gl-align-center gl-mb-1">
|
||||
{{ $options.i18n.fieldLabel }}
|
||||
<div class="gl-ml-3">
|
||||
<gl-icon name="labels" />
|
||||
<span class="gl-font-base gl-line-height-24">{{ selectedLabels.length }}</span>
|
||||
</div>
|
||||
</label>
|
||||
<div class="col-12">
|
||||
<div class="issuable-form-select-holder">
|
||||
|
|
|
|||
|
|
@ -454,7 +454,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
|
||||
def require_incident_for_incident_routes
|
||||
return unless params[:incident_tab].present?
|
||||
return if issue.incident?
|
||||
return if issue.work_item_type&.incident?
|
||||
|
||||
# Redirect instead of 404 to gracefully handle
|
||||
# issue type changes
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ module Mutations
|
|||
end
|
||||
|
||||
def authorize!(object)
|
||||
raise_noteable_not_incident! if object && !object.try(:incident?)
|
||||
raise_noteable_not_incident! if object && !object.try(:incident_type_issue?)
|
||||
|
||||
super
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ module Resolvers
|
|||
type ::Types::Achievements::UserAchievementType.connection_type, null: true
|
||||
|
||||
def resolve_with_lookahead
|
||||
user_achievements = object.user_achievements.not_revoked
|
||||
user_achievements = object.user_achievements.not_revoked.order_by_id_asc
|
||||
|
||||
apply_lookahead(user_achievements)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -45,7 +45,9 @@ module Types
|
|||
Types::Achievements::UserAchievementType.connection_type,
|
||||
null: true,
|
||||
alpha: { milestone: '15.10' },
|
||||
description: "Recipients for the achievement."
|
||||
description: "Recipients for the achievement.",
|
||||
extras: [:lookahead],
|
||||
resolver: ::Resolvers::Achievements::UserAchievementsResolver
|
||||
|
||||
def avatar_url
|
||||
object.avatar_url(only_path: false)
|
||||
|
|
|
|||
|
|
@ -72,7 +72,8 @@ module FormHelper
|
|||
multi_select: true,
|
||||
'input-meta': 'name',
|
||||
'always-show-selectbox': true,
|
||||
current_user_info: UserSerializer.new.represent(current_user)
|
||||
current_user_info: UserSerializer.new.represent(current_user),
|
||||
testid: 'assignee-ids-dropdown-toggle'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@ module IssuablesHelper
|
|||
end
|
||||
|
||||
def incident_only_initial_data(issue)
|
||||
return {} unless issue.incident?
|
||||
return {} unless issue.incident_type_issue?
|
||||
|
||||
{
|
||||
hasLinkedAlerts: issue.alert_management_alerts.any?,
|
||||
|
|
@ -396,6 +396,35 @@ module IssuablesHelper
|
|||
}
|
||||
end
|
||||
|
||||
def issuable_label_selector_data(project, issuable)
|
||||
initial_labels = issuable.labels.map do |label|
|
||||
{
|
||||
__typename: "Label",
|
||||
id: label.id,
|
||||
title: label.title,
|
||||
description: label.description,
|
||||
color: label.color,
|
||||
text_color: label.text_color
|
||||
}
|
||||
end
|
||||
|
||||
filter_base_path =
|
||||
if issuable.issuable_type == "merge_request"
|
||||
project_merge_requests_path(project)
|
||||
else
|
||||
project_issues_path(project)
|
||||
end
|
||||
|
||||
{
|
||||
field_name: "#{issuable.class.model_name.param_key}[label_ids][]",
|
||||
full_path: project.full_path,
|
||||
initial_labels: initial_labels.to_json,
|
||||
issuable_type: issuable.issuable_type,
|
||||
labels_filter_base_path: filter_base_path,
|
||||
labels_manage_path: project_labels_path(project)
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sidebar_gutter_collapsed?
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ module IssuesHelper
|
|||
|
||||
def issue_header_actions_data(project, issuable, current_user, issuable_sidebar)
|
||||
new_issuable_params = { issue: {}, add_related_issue: issuable.iid }
|
||||
if issuable.incident?
|
||||
if issuable.work_item_type&.incident?
|
||||
new_issuable_params[:issuable_template] = 'incident'
|
||||
new_issuable_params[:issue][:issue_type] = 'incident'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ module Achievements
|
|||
optional: true
|
||||
|
||||
scope :not_revoked, -> { where(revoked_by_user_id: nil) }
|
||||
scope :order_by_id_asc, -> { order(id: :asc) }
|
||||
|
||||
def revoked?
|
||||
revoked_by_user_id.present?
|
||||
|
|
|
|||
|
|
@ -174,6 +174,10 @@ module Issuable
|
|||
end
|
||||
end
|
||||
|
||||
def issuable_type
|
||||
self.class.name.underscore
|
||||
end
|
||||
|
||||
# We want to use optimistic lock for cases when only title or description are involved
|
||||
# http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
|
||||
def locking_enabled?
|
||||
|
|
@ -197,15 +201,15 @@ module Issuable
|
|||
end
|
||||
|
||||
def supports_severity?
|
||||
incident?
|
||||
incident_type_issue?
|
||||
end
|
||||
|
||||
def supports_escalation?
|
||||
incident?
|
||||
incident_type_issue?
|
||||
end
|
||||
|
||||
def incident?
|
||||
is_a?(Issue) && super
|
||||
def incident_type_issue?
|
||||
is_a?(Issue) && work_item_type&.incident?
|
||||
end
|
||||
|
||||
def supports_issue_type?
|
||||
|
|
|
|||
|
|
@ -6,18 +6,24 @@ module ProtectedRefAccess
|
|||
class_methods do
|
||||
def human_access_levels
|
||||
{
|
||||
Gitlab::Access::DEVELOPER => "Developers + Maintainers",
|
||||
Gitlab::Access::MAINTAINER => "Maintainers",
|
||||
Gitlab::Access::NO_ACCESS => "No one"
|
||||
}
|
||||
Gitlab::Access::DEVELOPER => 'Developers + Maintainers',
|
||||
Gitlab::Access::MAINTAINER => 'Maintainers',
|
||||
Gitlab::Access::ADMIN => 'Instance admins',
|
||||
Gitlab::Access::NO_ACCESS => 'No one'
|
||||
}.slice(*allowed_access_levels)
|
||||
end
|
||||
|
||||
def allowed_access_levels
|
||||
[
|
||||
Gitlab::Access::MAINTAINER,
|
||||
levels = [
|
||||
Gitlab::Access::DEVELOPER,
|
||||
Gitlab::Access::MAINTAINER,
|
||||
Gitlab::Access::ADMIN,
|
||||
Gitlab::Access::NO_ACCESS
|
||||
]
|
||||
|
||||
return levels unless Gitlab.com?
|
||||
|
||||
levels.excluding(Gitlab::Access::ADMIN)
|
||||
end
|
||||
|
||||
def humanize(access_level)
|
||||
|
|
@ -47,6 +53,7 @@ module ProtectedRefAccess
|
|||
|
||||
def check_access(current_user)
|
||||
return false if current_user.nil? || no_access?
|
||||
return current_user.admin? if admin_access?
|
||||
|
||||
yield if block_given?
|
||||
|
||||
|
|
@ -55,6 +62,10 @@ module ProtectedRefAccess
|
|||
|
||||
private
|
||||
|
||||
def admin_access?
|
||||
role? && access_level == ::Gitlab::Access::ADMIN
|
||||
end
|
||||
|
||||
def no_access?
|
||||
role? && access_level == Gitlab::Access::NO_ACCESS
|
||||
end
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ class Issue < ApplicationRecord
|
|||
DueNextMonthAndPreviousTwoWeeks = DueDateStruct.new('Due Next Month And Previous Two Weeks', 'next_month_and_previous_two_weeks').freeze
|
||||
|
||||
IssueTypeOutOfSyncError = Class.new(StandardError)
|
||||
ForbiddenColumnUsed = Class.new(StandardError)
|
||||
|
||||
SORTING_PREFERENCE_FIELD = :issues_sort
|
||||
MAX_BRANCH_TEMPLATE = 255
|
||||
|
|
@ -139,6 +140,28 @@ class Issue < ApplicationRecord
|
|||
|
||||
enum issue_type: WorkItems::Type.base_types
|
||||
|
||||
# TODO: Remove with https://gitlab.com/gitlab-org/gitlab/-/issues/402699
|
||||
WorkItems::Type.base_types.each do |base_type, _value|
|
||||
define_method "#{base_type}?".to_sym do
|
||||
error_message = <<~ERROR
|
||||
`#{base_type}?` uses the `issue_type` column underneath. As we want to remove the column,
|
||||
its usage is forbidden. You should use the `work_item_types` table instead.
|
||||
|
||||
# Before
|
||||
|
||||
issue.requirement? => true
|
||||
|
||||
# After
|
||||
|
||||
issue.work_item_type.requirement? => true
|
||||
|
||||
More details in https://gitlab.com/groups/gitlab-org/-/epics/10529
|
||||
ERROR
|
||||
|
||||
raise ForbiddenColumnUsed, error_message
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :issuing_parent, :project
|
||||
alias_attribute :issuing_parent_id, :project_id
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class PersonalAccessToken < ApplicationRecord
|
|||
|
||||
# PATs are 20 characters + optional configurable settings prefix (0..20)
|
||||
TOKEN_LENGTH_RANGE = (20..40).freeze
|
||||
MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS = 365
|
||||
|
||||
serialize :scopes, Array # rubocop:disable Cop/ActiveRecordSerialize
|
||||
|
||||
|
|
@ -48,6 +49,7 @@ class PersonalAccessToken < ApplicationRecord
|
|||
|
||||
validates :scopes, presence: true
|
||||
validate :validate_scopes
|
||||
validate :expires_at_before_instance_max_expiry_date, on: :create
|
||||
|
||||
def revoke!
|
||||
update!(revoked: true)
|
||||
|
|
@ -57,6 +59,19 @@ class PersonalAccessToken < ApplicationRecord
|
|||
!revoked? && !expired?
|
||||
end
|
||||
|
||||
# fall back to default value until background migration has updated all
|
||||
# existing PATs and we can add a validation
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/369123
|
||||
def expires_at=(value)
|
||||
datetime = if Feature.enabled?(:default_pat_expiration)
|
||||
value.presence || MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now
|
||||
else
|
||||
value
|
||||
end
|
||||
|
||||
super(datetime)
|
||||
end
|
||||
|
||||
override :simple_sorts
|
||||
def self.simple_sorts
|
||||
super.merge(
|
||||
|
|
@ -108,6 +123,15 @@ class PersonalAccessToken < ApplicationRecord
|
|||
def prefix_from_application_current_settings
|
||||
self.class.token_prefix
|
||||
end
|
||||
|
||||
def expires_at_before_instance_max_expiry_date
|
||||
return unless Feature.enabled?(:default_pat_expiration)
|
||||
return unless expires_at
|
||||
|
||||
if expires_at > MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now
|
||||
errors.add(:expires_at, _('must expire in 365 days'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
PersonalAccessToken.prepend_mod_with('PersonalAccessToken')
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class IssuablePolicy < BasePolicy
|
|||
|
||||
condition(:is_author) { @subject&.author == @user }
|
||||
|
||||
condition(:is_incident) { @subject.incident? }
|
||||
condition(:is_incident) { @subject.incident_type_issue? }
|
||||
|
||||
desc "Issuable is hidden"
|
||||
condition(:hidden, scope: :subject) { @subject.hidden? }
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ module IncidentManagement
|
|||
include Gitlab::Utils::UsageData
|
||||
|
||||
def track_incident_action(current_user, target, action)
|
||||
return unless target.incident?
|
||||
return unless target.incident_type_issue?
|
||||
|
||||
event = "incident_management_#{action}"
|
||||
track_usage_event(event, current_user.id)
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ module Issues
|
|||
issue.namespace.execute_hooks(issue_data, hooks_scope)
|
||||
issue.namespace.execute_integrations(issue_data, hooks_scope)
|
||||
|
||||
execute_incident_hooks(issue, issue_data) if issue.incident?
|
||||
execute_incident_hooks(issue, issue_data) if issue.work_item_type&.incident?
|
||||
end
|
||||
|
||||
# We can remove this code after proposal in
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ module Issues
|
|||
end
|
||||
|
||||
def resolve_incident(issue)
|
||||
return unless issue.incident?
|
||||
return unless issue.work_item_type&.incident?
|
||||
|
||||
status = issue.incident_management_issuable_escalation_status || issue.build_incident_management_issuable_escalation_status
|
||||
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ module Issues
|
|||
attr_reader :spam_params, :extra_params
|
||||
|
||||
def create_timeline_event(issue)
|
||||
return unless issue.incident?
|
||||
return unless issue.work_item_type&.incident?
|
||||
|
||||
IncidentManagement::TimelineEvents::CreateService.create_incident(issue, current_user)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ module Issues
|
|||
end
|
||||
|
||||
def perform_incident_management_actions(issue)
|
||||
return unless issue.incident?
|
||||
return unless issue.work_item_type&.incident?
|
||||
|
||||
create_timeline_event(issue)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -100,7 +100,15 @@ module ResourceAccessTokens
|
|||
end
|
||||
|
||||
def create_membership(resource, user, access_level)
|
||||
resource.add_member(user, access_level, expires_at: params[:expires_at])
|
||||
resource.add_member(user, access_level, expires_at: default_pat_expiration)
|
||||
end
|
||||
|
||||
def default_pat_expiration
|
||||
if Feature.enabled?(:default_pat_expiration)
|
||||
params[:expires_at].presence || PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now
|
||||
else
|
||||
params[:expires_at]
|
||||
end
|
||||
end
|
||||
|
||||
def log_event(token)
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ module ResourceEvents
|
|||
end
|
||||
|
||||
def create_timeline_events_from(added_labels: [], removed_labels: [])
|
||||
return unless resource.incident?
|
||||
return unless resource.incident_type_issue?
|
||||
|
||||
IncidentManagement::TimelineEvents::CreateService.change_labels(
|
||||
resource,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
- return if @issue.incident?
|
||||
- return if @issue.work_item_type&.incident?
|
||||
|
||||
- requirements_link_url = help_page_path('user/project/issues/design_management', anchor: 'requirements')
|
||||
- requirements_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: requirements_link_url }
|
||||
|
|
|
|||
|
|
@ -37,12 +37,15 @@
|
|||
.issuable-form-select-holder
|
||||
= render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]"
|
||||
|
||||
.form-group.row
|
||||
= form.label :label_ids, _('Labels'), class: "col-12"
|
||||
= form.hidden_field :label_ids, multiple: true, value: ''
|
||||
.col-12
|
||||
.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"
|
||||
- if Feature.enabled?(:visible_label_selection_on_metadata, project)
|
||||
.js-issuable-form-label-selector{ data: issuable_label_selector_data(project, issuable) }
|
||||
- else
|
||||
.form-group.row
|
||||
= form.label :label_ids, _('Labels'), class: "col-12"
|
||||
= form.hidden_field :label_ids, multiple: true, value: ''
|
||||
.col-12
|
||||
.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
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
.issuable-form-select-holder.form-group.gl-mb-0.gl-display-block
|
||||
#js-type-select{ data: issuable_type_selector_data(issuable) }
|
||||
|
||||
- if issuable.incident?
|
||||
- if issuable.incident_type_issue?
|
||||
%p.form-text.text-muted
|
||||
- incident_docs_url = help_page_path('operations/incident_management/incidents.md')
|
||||
- incident_docs_start = format('<a href="%{url}" target="_blank" rel="noopener noreferrer">', url: incident_docs_url)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
name: default_pat_expiration
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120213
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/410440
|
||||
milestone: '16.0'
|
||||
type: development
|
||||
group: group::authentication and authorization
|
||||
default_enabled: true
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: visible_label_selection_on_metadata
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/88908
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/364534
|
||||
milestone: '16.0'
|
||||
type: development
|
||||
group: "group::ux paper cuts"
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
- title: "Security report schemas version 14.x.x"
|
||||
announcement_milestone: "15.3"
|
||||
removal_milestone: "16.0"
|
||||
breaking_change: true
|
||||
reporter: abellucci
|
||||
stage: Govern
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/366477
|
||||
body: |
|
||||
Version 14.x.x [security report schemas](https://gitlab.com/gitlab-org/security-products/security-report-schemas) have been removed.
|
||||
Security reports that use schema version 14.x.x will cause an error in the pipeline's **Security** tab. For more information, refer to [security report validation](https://docs.gitlab.com/ee/user/application_security/#security-report-validation).
|
||||
tiers: [Ultimate]
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
breaking_change: true # (required) Change to false if this is not a breaking change.
|
||||
reporter: derekferguson # (required) GitLab username of the person reporting the removal
|
||||
stage: Secure # (required) String value of the stage that the feature was created in. e.g., Growth
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/383467 # (required) Link to the deprecation issue in GitLab
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/383467 # (required) Link to the deprecation issue in GitLab
|
||||
body: | # (required) Do not modify this line, instead modify the lines below.
|
||||
The variables `DAST_API_HOST_OVERRIDE` and `DAST_API_SPECIFICATION` have been removed from use for DAST API scans.
|
||||
|
||||
|
|
|
|||
|
|
@ -15,5 +15,5 @@
|
|||
|
||||
In your new Grafana instance, you can [configure the GitLab provided Prometheus as a data source](https://docs.gitlab.com/ee/administration/monitoring/performance/grafana_configuration.html#integration-with-gitlab-ui)
|
||||
and [connect Grafana to the GitLab UI](https://docs.gitlab.com/ee/administration/monitoring/performance/grafana_configuration.html#integration-with-gitlab-ui).
|
||||
tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
|
||||
tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
|
||||
documentation_url: https://docs.gitlab.com/ee/administration/monitoring/performance/grafana_configuration.html
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
announcement_milestone: "15.9" # (required) The milestone when this feature was first announced as deprecated.
|
||||
removal_milestone: "16.0" # (required) The milestone when this feature is planned to be removed
|
||||
breaking_change: true # (required) If this deprecation is a breaking change, set this value to true
|
||||
reporter: jreporter # (required) GitLab username of the person reporting the deprecation
|
||||
reporter: jreporter # (required) GitLab username of the person reporting the deprecation
|
||||
stage: Verify # (required) String value of the stage that the feature was created in. e.g., Growth
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/395708 # (required) Link to the deprecation issue in GitLab
|
||||
body: | # (required) Do not modify this line, instead modify the lines below.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
- title: "Non-expiring access tokens no longer supported"
|
||||
announcement_milestone: "15.4" # (required) The milestone when this feature was deprecated.
|
||||
removal_milestone: "16.0" # (required) The milestone when this feature is being removed.
|
||||
breaking_change: true # (required) Change to false if this is not a breaking change.
|
||||
reporter: jessieay # (required) GitLab username of the person reporting the removal
|
||||
stage: Manage # (required) String value of the stage that the feature was created in. e.g., Growth
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/369123
|
||||
body: | # (required) Do not modify this line, instead modify the lines below.
|
||||
Currently, you can create access tokens that have no expiration date. These access tokens are valid indefinitely, which presents a security risk if the access token is
|
||||
divulged. Because expiring access tokens are better, from GitLab 15.4 we [populate a default expiration date](https://gitlab.com/gitlab-org/gitlab/-/issues/348660).
|
||||
|
||||
In GitLab 16.0, any personal, project, or group access token that does not have an expiration date will automatically have an expiration date set at 365 days later than the current date.
|
||||
#
|
||||
# OPTIONAL FIELDS
|
||||
#
|
||||
tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
|
||||
documentation_url: # (optional) This is a link to the current documentation page
|
||||
image_url: # (optional) This is a link to a thumbnail image depicting the feature
|
||||
video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
|
||||
|
|
@ -13,5 +13,5 @@
|
|||
to PostgreSQL 13.
|
||||
- Using an externally-provided PostgreSQL 12, you must upgrade to PostgreSQL 13 or later to meet the
|
||||
[minimum version requirements](https://docs.gitlab.com/ee/install/requirements.html#postgresql-requirements).
|
||||
tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
|
||||
tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
|
||||
documentation_url: https://docs.gitlab.com/ee/administration/package_information/postgresql_versions.html
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
breaking_change: false # (required) Change to false if this is not a breaking change.
|
||||
reporter: DarrenEastman # (required) GitLab username of the person reporting the removal
|
||||
stage: Verify # (required) String value of the stage that the feature was created in. e.g., Growth
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/379064 # (required) Link to the deprecation issue in GitLab
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/379064 # (required) Link to the deprecation issue in GitLab
|
||||
body: | # (required) Do not modify this line, instead modify the lines below.
|
||||
In GitLab 13.6 and later, users can [specify any runner configuration in the GitLab Runner Helm chart](https://docs.gitlab.com/runner/install/kubernetes.html). When this features was released, we deprecated the fields in the GitLab Helm Chart configuration specific to the runner. As of v1.0 of the GitLab Runner Helm chart (GitLab 16.0), the following fields have been removed and are no longer supported:
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@
|
|||
breaking_change: false # (required) Change to false if this is not a breaking change.
|
||||
reporter: DarrenEastman # (required) GitLab username of the person reporting the removal
|
||||
stage: Verify # (required) String value of the stage that the feature was created in. e.g., Growth
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab-runner/-/issues/31001 # (required) Link to the deprecation issue in GitLab
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab-runner/-/issues/31001 # (required) Link to the deprecation issue in GitLab
|
||||
body: | # (required) Do not modify this line, instead modify the lines below.
|
||||
As of GitLab 16.0, GitLab Runner images based on Windows Server 2004 and 20H2 will not be provided as these operating systems are end-of-life.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
breaking_change: true # (required) Change to false if this is not a breaking change.
|
||||
reporter: DarrenEastman # (required) GitLab username of the person reporting the removal
|
||||
stage: Verify # (required) String value of the stage that the feature was created in. e.g., Growth
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344648 # (required) Link to the deprecation issue in GitLab
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344648 # (required) Link to the deprecation issue in GitLab
|
||||
body: | # (required) Do not modify this line, instead modify the lines below.
|
||||
In GitLab 16.0 and later, the GraphQL query for runners will no longer return the statuses `PAUSED` and `ACTIVE`.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveShimoZentaoIntegrationRecords < Gitlab::Database::Migration[2.1]
|
||||
TYPES = %w[Integrations::Shimo Integrations::Zentao]
|
||||
BATCH_SIZE = 100
|
||||
|
||||
disable_ddl_transaction!
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
def up
|
||||
return if Gitlab.jh?
|
||||
|
||||
define_batchable_model(:integrations)
|
||||
.where(type_new: TYPES)
|
||||
.each_batch(of: BATCH_SIZE) { |relation, _index| relation.delete_all }
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class BackfillCorrectedSecureFilesExpirations < Gitlab::Database::Migration[2.1]
|
||||
disable_ddl_transaction!
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_ci
|
||||
|
||||
BATCH_SIZE = 1000
|
||||
|
||||
def up
|
||||
each_batch_range('ci_secure_files', of: BATCH_SIZE) do |min, max|
|
||||
sql = <<-SQL
|
||||
SELECT id
|
||||
FROM ci_secure_files
|
||||
WHERE name ILIKE any (array['%.cer', '%.p12'])
|
||||
AND ci_secure_files.id BETWEEN #{min} AND #{max}
|
||||
SQL
|
||||
|
||||
rows = execute(sql)
|
||||
|
||||
rows.each do |row|
|
||||
::Ci::ParseSecureFileMetadataWorker.perform_async(row["id"])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def down; end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
9e822fbc2c7ce8044d0b38c5f1a9056431792e83fc9ed83056444c094e16c484
|
||||
|
|
@ -0,0 +1 @@
|
|||
eaec908173fb60b88867e14c73c6ba7d6079742bae7ead59fa021d6d57e622da
|
||||
|
|
@ -855,7 +855,7 @@ Parameters:
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
| ------------------- | -------------- | -------- | ----------- |
|
||||
| `discussion_id` | integer | yes | The ID of a discussion item. |
|
||||
| `discussion_id` | string | yes | The ID of a discussion item. |
|
||||
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding). |
|
||||
| `merge_request_iid` | integer | yes | The IID of a merge request. |
|
||||
|
||||
|
|
@ -1023,7 +1023,7 @@ Parameters:
|
|||
| Attribute | Type | Required | Description |
|
||||
| ------------------- | -------------- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding). |
|
||||
| `discussion_id` | integer | yes | The ID of a thread. |
|
||||
| `discussion_id` | string | yes | The ID of a thread. |
|
||||
| `merge_request_iid` | integer | yes | The IID of a merge request. |
|
||||
| `resolved` | boolean | yes | Resolve or unresolve the discussion. |
|
||||
|
||||
|
|
@ -1047,7 +1047,7 @@ Parameters:
|
|||
| ------------------- | -------------- | -------- | ----------- |
|
||||
| `body` | string | yes | The content of the note or reply. |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding). |
|
||||
| `discussion_id` | integer | yes | The ID of a thread. |
|
||||
| `discussion_id` | string | yes | The ID of a thread. |
|
||||
| `merge_request_iid` | integer | yes | The IID of a merge request. |
|
||||
| `note_id` | integer | yes | The ID of a thread note. |
|
||||
| `created_at` | string | no | Date time string, ISO 8601 formatted, such as `2016-03-11T03:45:40Z`. Requires administrator or project/group owner rights. |
|
||||
|
|
@ -1069,7 +1069,7 @@ Parameters:
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
| ------------------- | -------------- | -------- | ----------- |
|
||||
| `discussion_id` | integer | yes | The ID of a thread. |
|
||||
| `discussion_id` | string | yes | The ID of a thread. |
|
||||
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding). |
|
||||
| `merge_request_iid` | integer | yes | The IID of a merge request. |
|
||||
| `note_id` | integer | yes | The ID of a thread note. |
|
||||
|
|
@ -1100,7 +1100,7 @@ Parameters:
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
| ------------------- | -------------- | -------- | ----------- |
|
||||
| `discussion_id` | integer | yes | The ID of a thread. |
|
||||
| `discussion_id` | string | yes | The ID of a thread. |
|
||||
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding). |
|
||||
| `merge_request_iid` | integer | yes | The IID of a merge request. |
|
||||
| `note_id` | integer | yes | The ID of a thread note. |
|
||||
|
|
|
|||
|
|
@ -1463,6 +1463,7 @@ Input type: `CiAiGenerateConfigInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationciaigenerateconfigclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationciaigenerateconfigerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationciaigenerateconfigusermessage"></a>`userMessage` | [`AiMessageType`](#aimessagetype) | User chat message. |
|
||||
|
||||
### `Mutation.ciCdSettingsUpdate`
|
||||
|
||||
|
|
|
|||
|
|
@ -73,3 +73,41 @@ Some additional information is included at the bottom of the comment:
|
|||
migration (ending in `.log`) are available there, and only accessible by
|
||||
database maintainers or with an access request. Details of the specific
|
||||
batched background migration batches sampled are also available.
|
||||
|
||||
## Test changes to the database testing pipeline
|
||||
|
||||
To test a change to the database testing pipeline itself, you need:
|
||||
|
||||
1. A merge request against GitLab Org.
|
||||
1. The change to be tested must be present on a branch on GitLab Ops.
|
||||
|
||||
Use this self-documented script to test a merge request on GitLab Org against an arbitrary branch on GitLab Ops:
|
||||
|
||||
```shell
|
||||
#! /usr/bin/env bash
|
||||
|
||||
# The following must be set on a per-invocation basis:
|
||||
TESTING_TRIGGER_TOKEN='[REDACTED]' # Testing trigger token created in the CI section of the project
|
||||
CI_COMMIT_REF_NAME='55-post-notice-on-failure' # The branch on ops that you want to run against
|
||||
CI_MERGE_REQUEST_IID='117901' # Merge request ID of the MR on gitlab.com that you want to test
|
||||
SHA="fed6dd8a58d75a0e053a4972765b4fc08c5814a3" # The commit SHA of the HEAD of the branch you want to test on gitlab-org/gitlab
|
||||
|
||||
# The following should not be changed between invocations:
|
||||
CI_JOB_URL='https://gitlab.com/gitlab-org/database-team/gitlab-com-database-testing/-/jobs/1590162939'
|
||||
# It doesn't appear that CI_JOB_URL has to be set to anything in particular for the pipeline to run
|
||||
# successfully, but this would normally be the URL to the upstream job that invokes the DB testing pipeline.
|
||||
CI_MERGE_REQUEST_PROJECT_ID='278964' # gitlab-org/gitlab numeric ID. Shouldn't change.
|
||||
CI_PROJECT_ID="gitlab-org/gitlab" # The slug identifying gitlab-org/gitlab.
|
||||
|
||||
curl --verbose --request POST \
|
||||
--form "token=$TESTING_TRIGGER_TOKEN" \
|
||||
--form "ref=$CI_COMMIT_REF_NAME" \
|
||||
--form "variables[TOP_UPSTREAM_MERGE_REQUEST_IID]=$CI_MERGE_REQUEST_IID" \
|
||||
--form "variables[TOP_UPSTREAM_MERGE_REQUEST_PROJECT_ID]=$CI_MERGE_REQUEST_PROJECT_ID" \
|
||||
--form "variables[TOP_UPSTREAM_SOURCE_JOB]=$CI_JOB_URL" \
|
||||
--form "variables[TOP_UPSTREAM_SOURCE_PROJECT]=$CI_PROJECT_ID" \
|
||||
--form "variables[VALIDATION_PIPELINE]=true" \
|
||||
--form "variables[GITLAB_COMMIT_SHA]=$SHA" \
|
||||
--form "variables[TRIGGER_SOURCE]=$CI_JOB_URL" \
|
||||
"https://ops.gitlab.net/api/v4/projects/429/trigger/pipeline"
|
||||
```
|
||||
|
|
|
|||
|
|
@ -278,9 +278,9 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap
|
|||
|
||||
### 15.11.0
|
||||
|
||||
- Upgrades to GitLab 15.11 directly from GitLab versions 15.5.0 and earlier on self-managed installs will fail due to a missing migration until the fix for [issue 408304](https://gitlab.com/gitlab-org/gitlab/-/issues/408304) is released in an upcoming patch release. Affected users wanting to upgrade to 15.11.x can either:
|
||||
- Upgrades to GitLab 15.11 directly from GitLab versions 15.5.0 and earlier on self-managed installs will fail due to a missing migration until the fix for [issue 408304](https://gitlab.com/gitlab-org/gitlab/-/issues/408304) is released in version 15.11.3. Affected users wanting to upgrade to 15.11 can either:
|
||||
- Perform an intermediate upgrade to any version between 15.5 and 15.10 before upgrading to 15.11, or
|
||||
- Target the forthcoming patch release.
|
||||
- Target version 15.11.3 or later.
|
||||
|
||||
### 15.10.5
|
||||
|
||||
|
|
|
|||
|
|
@ -190,6 +190,17 @@ The [**Maximum number of active pipelines per project** limit](https://docs.gitl
|
|||
- [**Pipelines rate limits**](https://docs.gitlab.com/ee/user/admin_area/settings/rate_limit_on_pipelines_creation.html).
|
||||
- [**Total number of jobs in currently active pipelines**](https://docs.gitlab.com/ee/user/admin_area/settings/continuous_integration.html#set-cicd-limits).
|
||||
|
||||
### Non-expiring access tokens no longer supported
|
||||
|
||||
WARNING:
|
||||
This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
|
||||
Review the details carefully before upgrading.
|
||||
|
||||
Currently, you can create access tokens that have no expiration date. These access tokens are valid indefinitely, which presents a security risk if the access token is
|
||||
divulged. Because expiring access tokens are better, from GitLab 15.4 we [populate a default expiration date](https://gitlab.com/gitlab-org/gitlab/-/issues/348660).
|
||||
|
||||
In GitLab 16.0, any personal, project, or group access token that does not have an expiration date will automatically have an expiration date set at 365 days later than the current date.
|
||||
|
||||
### Non-standard default Redis ports are no longer supported
|
||||
|
||||
WARNING:
|
||||
|
|
@ -323,6 +334,15 @@ Review the details carefully before upgrading.
|
|||
|
||||
From GitLab 15.9, all Release links are external. The `external` field in the Releases and Release link APIs was deprecated in 15.9, and removed in GitLab 16.0.
|
||||
|
||||
### Security report schemas version 14.x.x
|
||||
|
||||
WARNING:
|
||||
This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
|
||||
Review the details carefully before upgrading.
|
||||
|
||||
Version 14.x.x [security report schemas](https://gitlab.com/gitlab-org/security-products/security-report-schemas) have been removed.
|
||||
Security reports that use schema version 14.x.x will cause an error in the pipeline's **Security** tab. For more information, refer to [security report validation](https://docs.gitlab.com/ee/user/application_security/#security-report-validation).
|
||||
|
||||
### Stop publishing GitLab Runner images based on Windows Server 2004 and 20H2
|
||||
|
||||
As of GitLab 16.0, GitLab Runner images based on Windows Server 2004 and 20H2 will not be provided as these operating systems are end-of-life.
|
||||
|
|
|
|||
|
|
@ -28,11 +28,7 @@ associated with a group rather than a project or user.
|
|||
In self-managed instances, group access tokens are subject to the same [maximum lifetime limits](../../admin_area/settings/account_and_limit_settings.md#limit-the-lifetime-of-access-tokens) as personal access tokens if the limit is set.
|
||||
|
||||
WARNING:
|
||||
The ability to create group access tokens without expiry was
|
||||
[deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/369122) in GitLab 15.4 and is planned for removal in GitLab
|
||||
16.0. When this ability is removed, existing group access tokens without an expiry are planned to have an expiry added.
|
||||
The automatic adding of an expiry occurs on GitLab.com during the 16.0 milestone. The automatic adding of an expiry
|
||||
occurs on self-managed instances when they are upgraded to GitLab 16.0. This change is a breaking change.
|
||||
The ability to create group access tokens without an expiry date was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/369122) in GitLab 15.4 and [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/392855) in GitLab 16.0. In GitLab 16.0 and later, existing group access tokens without an expiry date are automatically given an expiry date 365 days later than the current date. The automatic adding of an expiry date occurs on GitLab.com during the 16.0 milestone. The automatic adding of an expiry date occurs on self-managed instances when they are upgraded to GitLab 16.0. This change is a breaking change.
|
||||
|
||||
You can use group access tokens:
|
||||
|
||||
|
|
@ -52,13 +48,18 @@ configured for personal access tokens.
|
|||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214045) in GitLab 14.7.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/348660) in GitLab 15.3, default expiration of 30 days and default role of Guest is populated in the UI.
|
||||
> - Ability to create non-expiring group access tokens [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/392855) in GitLab 16.0.
|
||||
|
||||
To create a group access token:
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Settings > Access Tokens**.
|
||||
1. Enter a name. The token name is visible to any user with permissions to view the group.
|
||||
1. Optional. Enter an expiry date for the token. The token will expire on that date at midnight UTC. An instance-wide [maximum lifetime](../../admin_area/settings/account_and_limit_settings.md#limit-the-lifetime-of-access-tokens) setting can limit the maximum allowable lifetime in self-managed instances.
|
||||
1. Enter an expiry date for the token:
|
||||
- The token expires on that date at midnight UTC.
|
||||
- If you do not enter an expiry date, the expiry date is automatically set to 365 days later than the current date.
|
||||
- By default, this date can be a maximum of 365 days later than the current date.
|
||||
- An instance-wide [maximum lifetime](../../admin_area/settings/account_and_limit_settings.md#limit-the-lifetime-of-access-tokens) setting can limit the maximum allowable lifetime in self-managed instances.
|
||||
1. Select a role for the token.
|
||||
1. Select the [desired scopes](#scopes-for-a-group-access-token).
|
||||
1. Select **Create group access token**.
|
||||
|
|
|
|||
|
|
@ -20,11 +20,7 @@ Personal access tokens can be an alternative to [OAuth2](../../api/oauth2.md) an
|
|||
In both cases, you authenticate with a personal access token in place of your password.
|
||||
|
||||
WARNING:
|
||||
The ability to create personal access tokens without expiry was
|
||||
[deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/369122) in GitLab 15.4 and is planned for removal in GitLab
|
||||
16.0. When this ability is removed, existing personal access tokens without an expiry are planned to have an expiry added.
|
||||
The automatic adding of an expiry occurs on GitLab.com during the 16.0 milestone. The automatic adding of an expiry
|
||||
occurs on self-managed instances when they are upgraded to GitLab 16.0. This change is a breaking change.
|
||||
The ability to create personal access tokens without expiry was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/369122) in GitLab 15.4 and [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/392855) in GitLab 16.0. In GitLab 16.0 and later, existing personal access tokens without an expiry date are automatically given an expiry date of 365 days later than the current date. The automatic adding of an expiry date occurs on GitLab.com during the 16.0 milestone. The automatic adding of an expiry date occurs on self-managed instances when they are upgraded to GitLab 16.0. This change is a breaking change.
|
||||
|
||||
Personal access tokens are:
|
||||
|
||||
|
|
@ -47,14 +43,18 @@ Use impersonation tokens to automate authentication as a specific user.
|
|||
|
||||
## Create a personal access token
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/348660) in GitLab 15.3, default expiration of 30 days is populated in the UI.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/348660) in GitLab 15.3, default expiration of 30 days is populated in the UI.
|
||||
> - Ability to create non-expiring personal access tokens [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/392855) in GitLab 16.0.
|
||||
|
||||
You can create as many personal access tokens as you like.
|
||||
|
||||
1. In the upper-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. On the left sidebar, select **Access Tokens**.
|
||||
1. Enter a name and optional expiry date for the token.
|
||||
1. Enter a name and expiry date for the token.
|
||||
- The token expires on that date at midnight UTC.
|
||||
- If you do not enter an expiry date, the expiry date is automatically set to 365 days later than the current date.
|
||||
- By default, this date can be a maximum of 365 days later than the current date.
|
||||
1. Select the [desired scopes](#personal-access-token-scopes).
|
||||
1. Select **Create personal access token**.
|
||||
|
||||
|
|
|
|||
|
|
@ -28,11 +28,7 @@ and [personal access tokens](../../profile/personal_access_tokens.md).
|
|||
In self-managed instances, project access tokens are subject to the same [maximum lifetime limits](../../admin_area/settings/account_and_limit_settings.md#limit-the-lifetime-of-access-tokens) as personal access tokens if the limit is set.
|
||||
|
||||
WARNING:
|
||||
The ability to create project access tokens without expiry was
|
||||
[deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/369122) in GitLab 15.4 and is planned for removal in GitLab
|
||||
16.0. When this ability is removed, existing project access tokens without an expiry are planned to have an expiry added.
|
||||
The automatic adding of an expiry occurs on GitLab.com during the 16.0 milestone. The automatic adding of an expiry
|
||||
occurs on self-managed instances when they are upgraded to GitLab 16.0. This change is a breaking change.
|
||||
The ability to create project access tokens without expiry was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/369122) in GitLab 15.4 and [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/392855) in GitLab 16.0. In GitLab 16.0 and later, existing project access tokens without an expiry date are automatically given an expiry date of 365 days later than the current date. The automatic adding of an expiry date occurs on GitLab.com during the 16.0 milestone. The automatic adding of an expiry date occurs on self-managed instances when they are upgraded to GitLab 16.0. This change is a breaking change.
|
||||
|
||||
You can use project access tokens:
|
||||
|
||||
|
|
@ -52,14 +48,18 @@ configured for personal access tokens.
|
|||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89114) in GitLab 15.1, Owners can select Owner role for project access tokens.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/348660) in GitLab 15.3, default expiration of 30 days and default role of Guest is populated in the UI.
|
||||
> - Ability to create non-expiring project access tokens [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/392855) in GitLab 16.0.
|
||||
|
||||
To create a project access token:
|
||||
|
||||
1. On the top bar, select **Main menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Settings > Access Tokens**.
|
||||
1. Enter a name. The token name is visible to any user with permissions to view the project.
|
||||
1. Optional. Enter an expiry date for the token. The token expires on that date at midnight UTC. An instance-wide [maximum lifetime](../../admin_area/settings/account_and_limit_settings.md#limit-the-lifetime-of-access-tokens) setting can limit the maximum allowable lifetime in self-managed instances.
|
||||
|
||||
1. Enter an expiry date for the token.
|
||||
- The token expires on that date at midnight UTC.
|
||||
- If you do not enter an expiry date, the expiry date is automatically set to 365 days later than the current date.
|
||||
- By default, this date can be a maximum of 365 days later than the current date.
|
||||
- An instance-wide [maximum lifetime](../../admin_area/settings/account_and_limit_settings.md#limit-the-lifetime-of-access-tokens) setting can limit the maximum allowable lifetime in self-managed instances.
|
||||
1. Select a role for the token.
|
||||
1. Select the [desired scopes](#scopes-for-a-project-access-token).
|
||||
1. Select **Create project access token**.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Workspaces (Beta) **(PREMIUM)**
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10122) in GitLab 16.0 [with a flag](../../administration/feature_flags.md) named `remote_development_feature_flag`. Disabled by default.
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10122) in GitLab 16.0 [with a flag](../../administration/feature_flags.md) named `remote_development_feature_flag`. Enabled by default.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `remote_development_feature_flag`. On GitLab.com, this feature is not available. The feature is not ready for production use.
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ module Gitlab
|
|||
DEVELOPER = 30
|
||||
MAINTAINER = 40
|
||||
OWNER = 50
|
||||
ADMIN = 60
|
||||
|
||||
# Branch protection settings
|
||||
PROTECTION_NONE = 0
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ module Gitlab
|
|||
if data['gitlab_schema'].nil?
|
||||
raise(
|
||||
UnknownSchemaError,
|
||||
"#{file_path} must specify a valid gitlab_schema for #{key_name}." \
|
||||
"#{file_path} must specify a valid gitlab_schema for #{key_name}. " \
|
||||
"See https://docs.gitlab.com/ee/development/database/database_dictionary.html"
|
||||
)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ module Gitlab
|
|||
execution_message { _('Issue has been promoted to incident') }
|
||||
types Issue
|
||||
condition do
|
||||
!quick_action_target.incident? &&
|
||||
!quick_action_target.work_item_type&.incident? &&
|
||||
current_user.can?(:"set_#{quick_action_target.issue_type}_metadata", quick_action_target)
|
||||
end
|
||||
command :promote_to_incident do
|
||||
|
|
@ -298,7 +298,7 @@ module Gitlab
|
|||
params '<timeline comment> | <date(YYYY-MM-DD)> <time(HH:MM)>'
|
||||
types Issue
|
||||
condition do
|
||||
quick_action_target.incident? &&
|
||||
quick_action_target.work_item_type&.incident? &&
|
||||
current_user.can?(:admin_incident_management_timeline_event, quick_action_target)
|
||||
end
|
||||
parse_params do |event_params|
|
||||
|
|
|
|||
|
|
@ -502,6 +502,7 @@ namespace :gitlab do
|
|||
milestone = version.release.segments.first(2).join('.')
|
||||
|
||||
classes = {}
|
||||
ignored_tables = %w[p_ci_builds]
|
||||
|
||||
Gitlab::Database.database_base_models.each do |_, model_class|
|
||||
tables = model_class.connection.tables
|
||||
|
|
@ -524,6 +525,7 @@ namespace :gitlab do
|
|||
|
||||
sources.each do |source_name|
|
||||
next if source_name.start_with?('_test_') # Ignore test tables
|
||||
next if ignored_tables.include?(source_name)
|
||||
|
||||
database = model_class.connection_db_config.name
|
||||
file = dictionary_file_path(source_name, views, database)
|
||||
|
|
|
|||
|
|
@ -50979,7 +50979,7 @@ msgstr ""
|
|||
msgid "Workspaces"
|
||||
msgstr ""
|
||||
|
||||
msgid "Workspaces|A workspace is a virtual sandbox environment for your code in GitLab. You can create a workspace on its own or as part of a project."
|
||||
msgid "Workspaces|A workspace is a virtual sandbox environment for your code in GitLab. You can create a workspace for a public project."
|
||||
msgstr ""
|
||||
|
||||
msgid "Workspaces|Cancel"
|
||||
|
|
@ -53917,6 +53917,9 @@ msgstr ""
|
|||
msgid "must contain only a discord user ID."
|
||||
msgstr ""
|
||||
|
||||
msgid "must expire in 365 days"
|
||||
msgstr ""
|
||||
|
||||
msgid "must have a repository"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@
|
|||
"webpack-dev-server": "4.15.0",
|
||||
"xhr-mock": "^2.5.1",
|
||||
"yarn-check-webpack-plugin": "^1.2.0",
|
||||
"yarn-deduplicate": "^6.0.0"
|
||||
"yarn-deduplicate": "^6.0.2"
|
||||
},
|
||||
"blockedDependencies": {
|
||||
"bootstrap-vue": "https://docs.gitlab.com/ee/development/fe_guide/dependencies.html#bootstrapvue"
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ RSpec.describe 'New/edit issue', :js, feature_category: :team_planning do
|
|||
let_it_be(:confidential_issue) { create(:issue, project: project, assignees: [user], milestone: milestone, confidential: true) }
|
||||
|
||||
let(:current_user) { user }
|
||||
let(:visible_label_selection_on_metadata) { false }
|
||||
|
||||
before_all do
|
||||
project.add_maintainer(user)
|
||||
|
|
@ -27,6 +28,7 @@ RSpec.describe 'New/edit issue', :js, feature_category: :team_planning do
|
|||
|
||||
before do
|
||||
stub_licensed_features(multiple_issue_assignees: false, issue_weights: false)
|
||||
stub_feature_flags(visible_label_selection_on_metadata: visible_label_selection_on_metadata)
|
||||
|
||||
sign_in(current_user)
|
||||
end
|
||||
|
|
@ -114,74 +116,232 @@ RSpec.describe 'New/edit issue', :js, feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
|
||||
it 'allows user to create new issue' do
|
||||
fill_in 'issue_title', with: 'title'
|
||||
fill_in 'issue_description', with: 'title'
|
||||
context 'with the visible_label_selection_on_metadata feature flag enabled' do
|
||||
let(:visible_label_selection_on_metadata) { true }
|
||||
|
||||
expect(find('a', text: 'Assign to me')).to be_visible
|
||||
click_button 'Unassigned'
|
||||
it 'allows user to create new issue' do
|
||||
fill_in 'issue_title', with: 'title'
|
||||
fill_in 'issue_description', with: 'title'
|
||||
|
||||
wait_for_requests
|
||||
expect(find('a', text: 'Assign to me')).to be_visible
|
||||
click_button 'Unassigned'
|
||||
|
||||
page.within '.dropdown-menu-user' do
|
||||
click_link user2.name
|
||||
end
|
||||
expect(find('input[name="issue[assignee_ids][]"]', visible: false).value).to match(user2.id.to_s)
|
||||
page.within '.js-assignee-search' do
|
||||
expect(page).to have_content user2.name
|
||||
end
|
||||
expect(find('a', text: 'Assign to me')).to be_visible
|
||||
wait_for_requests
|
||||
|
||||
click_link 'Assign to me'
|
||||
assignee_ids = page.all('input[name="issue[assignee_ids][]"]', visible: false)
|
||||
page.within '.dropdown-menu-user' do
|
||||
click_link user2.name
|
||||
end
|
||||
expect(find('input[name="issue[assignee_ids][]"]', visible: false).value).to match(user2.id.to_s)
|
||||
page.within '.js-assignee-search' do
|
||||
expect(page).to have_content user2.name
|
||||
end
|
||||
expect(find('a', text: 'Assign to me')).to be_visible
|
||||
|
||||
expect(assignee_ids[0].value).to match(user.id.to_s)
|
||||
click_link 'Assign to me'
|
||||
assignee_ids = page.all('input[name="issue[assignee_ids][]"]', visible: false)
|
||||
|
||||
page.within '.js-assignee-search' do
|
||||
expect(page).to have_content user.name
|
||||
end
|
||||
expect(find('a', text: 'Assign to me', visible: false)).not_to be_visible
|
||||
expect(assignee_ids[0].value).to match(user.id.to_s)
|
||||
|
||||
click_button 'Select milestone'
|
||||
click_button milestone.title
|
||||
expect(find('input[name="issue[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
|
||||
expect(page).to have_button milestone.title
|
||||
page.within '.js-assignee-search' do
|
||||
expect(page).to have_content user.name
|
||||
end
|
||||
expect(find('a', text: 'Assign to me', visible: false)).not_to be_visible
|
||||
|
||||
click_button 'Labels'
|
||||
page.within '.dropdown-menu-labels' do
|
||||
click_link label.title
|
||||
click_link label2.title
|
||||
end
|
||||
click_button 'Select milestone'
|
||||
click_button milestone.title
|
||||
expect(find('input[name="issue[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
|
||||
expect(page).to have_button milestone.title
|
||||
|
||||
find('.js-issuable-form-dropdown.js-label-select').click
|
||||
|
||||
page.within '.js-label-select' do
|
||||
expect(page).to have_content label.title
|
||||
end
|
||||
expect(page.all('input[name="issue[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
|
||||
expect(page.all('input[name="issue[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
|
||||
|
||||
click_button 'Create issue'
|
||||
|
||||
page.within '.issuable-sidebar' do
|
||||
page.within '.assignee' do
|
||||
expect(page).to have_content "Assignee"
|
||||
click_button _('Select label')
|
||||
wait_for_all_requests
|
||||
page.within '[data-testid="sidebar-labels"]' do
|
||||
click_button label.title
|
||||
click_button label2.title
|
||||
click_button _('Close')
|
||||
wait_for_requests
|
||||
page.within('[data-testid="embedded-labels-list"]') do
|
||||
expect(page).to have_content(label.title)
|
||||
expect(page).to have_content(label2.title)
|
||||
end
|
||||
end
|
||||
|
||||
page.within '.milestone' do
|
||||
expect(page).to have_content milestone.title
|
||||
click_button 'Create issue'
|
||||
|
||||
page.within '.issuable-sidebar' do
|
||||
page.within '.assignee' do
|
||||
expect(page).to have_content "Assignee"
|
||||
end
|
||||
|
||||
page.within '.milestone' do
|
||||
expect(page).to have_content milestone.title
|
||||
end
|
||||
|
||||
page.within '.labels' do
|
||||
expect(page).to have_content label.title
|
||||
expect(page).to have_content label2.title
|
||||
end
|
||||
end
|
||||
|
||||
page.within '.labels' do
|
||||
page.within '.breadcrumbs' do
|
||||
issue = Issue.find_by(title: 'title')
|
||||
|
||||
expect(page).to have_text("Issues #{issue.to_reference}")
|
||||
end
|
||||
end
|
||||
|
||||
it 'correctly updates the dropdown toggle when removing a label' do
|
||||
click_button _('Select label')
|
||||
|
||||
wait_for_all_requests
|
||||
|
||||
page.within '[data-testid="sidebar-labels"]' do
|
||||
click_button label.title
|
||||
click_button _('Close')
|
||||
|
||||
wait_for_requests
|
||||
|
||||
page.within('[data-testid="embedded-labels-list"]') do
|
||||
expect(page).to have_content(label.title)
|
||||
end
|
||||
|
||||
expect(page.find('.gl-dropdown-button-text')).to have_content(label.title)
|
||||
end
|
||||
|
||||
click_button label.title, class: 'gl-dropdown-toggle'
|
||||
|
||||
wait_for_all_requests
|
||||
|
||||
page.within '[data-testid="sidebar-labels"]' do
|
||||
click_button label.title, class: 'dropdown-item'
|
||||
click_button _('Close')
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).not_to have_selector('[data-testid="embedded-labels-list"]')
|
||||
expect(page.find('.gl-dropdown-button-text')).to have_content(_('Select label'))
|
||||
end
|
||||
end
|
||||
|
||||
it 'clears label search input field when a label is selected', :js do
|
||||
click_button _('Select label')
|
||||
|
||||
wait_for_all_requests
|
||||
|
||||
page.within '[data-testid="sidebar-labels"]' do
|
||||
search_field = find('input[type="search"]')
|
||||
|
||||
search_field.native.send_keys(label.title)
|
||||
|
||||
expect(page).to have_css('.gl-search-box-by-type-clear')
|
||||
|
||||
click_button label.title, class: 'dropdown-item'
|
||||
|
||||
expect(page).not_to have_css('.gl-search-box-by-type-clear')
|
||||
expect(search_field.value).to eq ''
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the visible_label_selection_on_metadata feature flag disabled' do
|
||||
let(:visible_label_selection_on_metadata) { false }
|
||||
|
||||
it 'allows user to create new issue' do
|
||||
fill_in 'issue_title', with: 'title'
|
||||
fill_in 'issue_description', with: 'title'
|
||||
|
||||
expect(find('a', text: 'Assign to me')).to be_visible
|
||||
click_button 'Unassigned'
|
||||
|
||||
wait_for_requests
|
||||
|
||||
page.within '.dropdown-menu-user' do
|
||||
click_link user2.name
|
||||
end
|
||||
expect(find('input[name="issue[assignee_ids][]"]', visible: false).value).to match(user2.id.to_s)
|
||||
page.within '.js-assignee-search' do
|
||||
expect(page).to have_content user2.name
|
||||
end
|
||||
expect(find('a', text: 'Assign to me')).to be_visible
|
||||
|
||||
click_link 'Assign to me'
|
||||
assignee_ids = page.all('input[name="issue[assignee_ids][]"]', visible: false)
|
||||
|
||||
expect(assignee_ids[0].value).to match(user.id.to_s)
|
||||
|
||||
page.within '.js-assignee-search' do
|
||||
expect(page).to have_content user.name
|
||||
end
|
||||
expect(find('a', text: 'Assign to me', visible: false)).not_to be_visible
|
||||
|
||||
click_button 'Select milestone'
|
||||
click_button milestone.title
|
||||
expect(find('input[name="issue[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
|
||||
expect(page).to have_button milestone.title
|
||||
|
||||
click_button 'Labels'
|
||||
page.within '.dropdown-menu-labels' do
|
||||
click_link label.title
|
||||
click_link label2.title
|
||||
end
|
||||
|
||||
find('.js-issuable-form-dropdown.js-label-select').click
|
||||
|
||||
page.within '.js-label-select' do
|
||||
expect(page).to have_content label.title
|
||||
expect(page).to have_content label2.title
|
||||
end
|
||||
expect(page.all('input[name="issue[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
|
||||
expect(page.all('input[name="issue[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
|
||||
|
||||
click_button 'Create issue'
|
||||
|
||||
page.within '.issuable-sidebar' do
|
||||
page.within '.assignee' do
|
||||
expect(page).to have_content "Assignee"
|
||||
end
|
||||
|
||||
page.within '.milestone' do
|
||||
expect(page).to have_content milestone.title
|
||||
end
|
||||
|
||||
page.within '.labels' do
|
||||
expect(page).to have_content label.title
|
||||
expect(page).to have_content label2.title
|
||||
end
|
||||
end
|
||||
|
||||
page.within '.breadcrumbs' do
|
||||
issue = Issue.find_by(title: 'title')
|
||||
|
||||
expect(page).to have_text("Issues #{issue.to_reference}")
|
||||
end
|
||||
end
|
||||
|
||||
page.within '.breadcrumbs' do
|
||||
issue = Issue.find_by(title: 'title')
|
||||
it 'correctly updates the dropdown toggle when removing a label' do
|
||||
click_button 'Labels'
|
||||
|
||||
expect(page).to have_text("Issues #{issue.to_reference}")
|
||||
page.within '.dropdown-menu-labels' do
|
||||
click_link label.title
|
||||
end
|
||||
|
||||
expect(find('.js-label-select')).to have_content(label.title)
|
||||
|
||||
page.within '.dropdown-menu-labels' do
|
||||
click_link label.title
|
||||
end
|
||||
|
||||
expect(find('.js-label-select')).to have_content('Labels')
|
||||
end
|
||||
|
||||
it 'clears label search input field when a label is selected' do
|
||||
click_button 'Labels'
|
||||
|
||||
page.within '.dropdown-menu-labels' do
|
||||
search_field = find('input[type="search"]')
|
||||
|
||||
search_field.set(label2.title)
|
||||
click_link label2.title
|
||||
expect(search_field.value).to eq ''
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -193,34 +353,6 @@ RSpec.describe 'New/edit issue', :js, feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
|
||||
it 'correctly updates the dropdown toggle when removing a label' do
|
||||
click_button 'Labels'
|
||||
|
||||
page.within '.dropdown-menu-labels' do
|
||||
click_link label.title
|
||||
end
|
||||
|
||||
expect(find('.js-label-select')).to have_content(label.title)
|
||||
|
||||
page.within '.dropdown-menu-labels' do
|
||||
click_link label.title
|
||||
end
|
||||
|
||||
expect(find('.js-label-select')).to have_content('Labels')
|
||||
end
|
||||
|
||||
it 'clears label search input field when a label is selected' do
|
||||
click_button 'Labels'
|
||||
|
||||
page.within '.dropdown-menu-labels' do
|
||||
search_field = find('input[type="search"]')
|
||||
|
||||
search_field.set(label2.title)
|
||||
click_link label2.title
|
||||
expect(search_field.value).to eq ''
|
||||
end
|
||||
end
|
||||
|
||||
it 'correctly updates the selected user when changing assignee' do
|
||||
click_button 'Unassigned'
|
||||
|
||||
|
|
@ -426,42 +558,100 @@ RSpec.describe 'New/edit issue', :js, feature_category: :team_planning do
|
|||
visit edit_project_issue_path(project, issue)
|
||||
end
|
||||
|
||||
it 'allows user to update issue' do
|
||||
expect(find('input[name="issue[assignee_ids][]"]', visible: false).value).to match(user.id.to_s)
|
||||
expect(find('input[name="issue[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
|
||||
expect(find('a', text: 'Assign to me', visible: false)).not_to be_visible
|
||||
context 'with the visible_label_selection_on_metadata feature flag enabled' do
|
||||
let(:visible_label_selection_on_metadata) { true }
|
||||
|
||||
page.within '.js-user-search' do
|
||||
expect(page).to have_content user.name
|
||||
end
|
||||
it 'allows user to update issue' do
|
||||
expect(find('input[name="issue[assignee_ids][]"]', visible: false).value).to match(user.id.to_s)
|
||||
expect(find('input[name="issue[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
|
||||
expect(find('a', text: 'Assign to me', visible: false)).not_to be_visible
|
||||
|
||||
expect(page).to have_button milestone.title
|
||||
|
||||
click_button 'Labels'
|
||||
page.within '.dropdown-menu-labels' do
|
||||
click_link label.title
|
||||
click_link label2.title
|
||||
end
|
||||
page.within '.js-label-select' do
|
||||
expect(page).to have_content label.title
|
||||
end
|
||||
expect(page.all('input[name="issue[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
|
||||
expect(page.all('input[name="issue[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
|
||||
|
||||
click_button 'Save changes'
|
||||
|
||||
page.within '.issuable-sidebar' do
|
||||
page.within '.assignee' do
|
||||
page.within '.js-user-search' do
|
||||
expect(page).to have_content user.name
|
||||
end
|
||||
|
||||
page.within '.milestone' do
|
||||
expect(page).to have_content milestone.title
|
||||
expect(page).to have_button milestone.title
|
||||
|
||||
click_button _('Select label')
|
||||
|
||||
wait_for_all_requests
|
||||
|
||||
page.within '[data-testid="sidebar-labels"]' do
|
||||
click_button label.title
|
||||
click_button label2.title
|
||||
click_button _('Close')
|
||||
|
||||
wait_for_requests
|
||||
|
||||
page.within('[data-testid="embedded-labels-list"]') do
|
||||
expect(page).to have_content(label.title)
|
||||
expect(page).to have_content(label2.title)
|
||||
end
|
||||
end
|
||||
|
||||
page.within '.labels' do
|
||||
expect(page.all('input[name="issue[label_ids][]"]', visible: false)
|
||||
.map(&:value))
|
||||
.to contain_exactly(label.id.to_s, label2.id.to_s)
|
||||
|
||||
click_button 'Save changes'
|
||||
|
||||
page.within '.issuable-sidebar' do
|
||||
page.within '.assignee' do
|
||||
expect(page).to have_content user.name
|
||||
end
|
||||
|
||||
page.within '.milestone' do
|
||||
expect(page).to have_content milestone.title
|
||||
end
|
||||
|
||||
page.within '.labels' do
|
||||
expect(page).to have_content label.title
|
||||
expect(page).to have_content label2.title
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the visible_label_selection_on_metadata feature flag disabled' do
|
||||
let(:visible_label_selection_on_metadata) { false }
|
||||
|
||||
it 'allows user to update issue' do
|
||||
expect(find('input[name="issue[assignee_ids][]"]', visible: false).value).to match(user.id.to_s)
|
||||
expect(find('input[name="issue[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
|
||||
expect(find('a', text: 'Assign to me', visible: false)).not_to be_visible
|
||||
|
||||
page.within '.js-user-search' do
|
||||
expect(page).to have_content user.name
|
||||
end
|
||||
|
||||
expect(page).to have_button milestone.title
|
||||
|
||||
click_button 'Labels'
|
||||
page.within '.dropdown-menu-labels' do
|
||||
click_link label.title
|
||||
click_link label2.title
|
||||
end
|
||||
page.within '.js-label-select' do
|
||||
expect(page).to have_content label.title
|
||||
expect(page).to have_content label2.title
|
||||
end
|
||||
expect(page.all('input[name="issue[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
|
||||
expect(page.all('input[name="issue[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
|
||||
|
||||
click_button 'Save changes'
|
||||
|
||||
page.within '.issuable-sidebar' do
|
||||
page.within '.assignee' do
|
||||
expect(page).to have_content user.name
|
||||
end
|
||||
|
||||
page.within '.milestone' do
|
||||
expect(page).to have_content milestone.title
|
||||
end
|
||||
|
||||
page.within '.labels' do
|
||||
expect(page).to have_content label.title
|
||||
expect(page).to have_content label2.title
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -552,22 +742,55 @@ RSpec.describe 'New/edit issue', :js, feature_category: :team_planning do
|
|||
visit new_project_issue_path(sub_group_project)
|
||||
end
|
||||
|
||||
it 'creates project label from dropdown' do
|
||||
click_button 'Labels'
|
||||
context 'with the visible_label_selection_on_metadata feature flag enabled', :js do
|
||||
let(:visible_label_selection_on_metadata) { true }
|
||||
|
||||
click_link 'Create project label'
|
||||
it 'creates project label from dropdown' do
|
||||
find('[data-testid="labels-select-dropdown-contents"] button').click
|
||||
|
||||
page.within '.dropdown-new-label' do
|
||||
fill_in 'new_label_name', with: 'test label'
|
||||
first('.suggest-colors-dropdown a').click
|
||||
wait_for_all_requests
|
||||
|
||||
click_button 'Create'
|
||||
page.within '[data-testid="sidebar-labels"]' do
|
||||
click_button _('Create project label')
|
||||
|
||||
wait_for_requests
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
page.within '.js-labels-create' do
|
||||
find('[data-testid="label-title-input"]').fill_in with: 'test label'
|
||||
first('.suggest-colors-dropdown a').click
|
||||
|
||||
click_button 'Create'
|
||||
|
||||
wait_for_all_requests
|
||||
end
|
||||
|
||||
page.within '.js-labels-list' do
|
||||
expect(page).to have_button 'test label'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
page.within '.dropdown-menu-labels' do
|
||||
expect(page).to have_link 'test label'
|
||||
context 'with the visible_label_selection_on_metadata feature flag disabled' do
|
||||
let(:visible_label_selection_on_metadata) { false }
|
||||
|
||||
it 'creates project label from dropdown' do
|
||||
click_button 'Labels'
|
||||
|
||||
click_link 'Create project label'
|
||||
|
||||
page.within '.dropdown-new-label' do
|
||||
fill_in 'new_label_name', with: 'test label'
|
||||
first('.suggest-colors-dropdown a').click
|
||||
|
||||
click_button 'Create'
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
page.within '.dropdown-menu-labels' do
|
||||
expect(page).to have_link 'test label'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ RSpec.describe "User creates issue", feature_category: :team_planning do
|
|||
let_it_be(:project) { create(:project_empty_repo, :public) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
let(:visible_label_selection_on_metadata) { false }
|
||||
|
||||
context "when unauthenticated" do
|
||||
before do
|
||||
sign_out(:user)
|
||||
|
|
@ -34,6 +36,7 @@ RSpec.describe "User creates issue", feature_category: :team_planning do
|
|||
|
||||
context "when signed in as guest", :js do
|
||||
before do
|
||||
stub_feature_flags(visible_label_selection_on_metadata: visible_label_selection_on_metadata)
|
||||
project.add_guest(user)
|
||||
sign_in(user)
|
||||
|
||||
|
|
@ -92,18 +95,50 @@ RSpec.describe "User creates issue", feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
|
||||
it "creates issue" do
|
||||
issue_title = "500 error on profile"
|
||||
context 'with the visible_label_selection_on_metadata feature flag enabled' do
|
||||
let(:visible_label_selection_on_metadata) { true }
|
||||
|
||||
fill_in("Title", with: issue_title)
|
||||
click_button("Label")
|
||||
click_link(label_titles.first)
|
||||
click_button("Create issue")
|
||||
it "creates issue" do
|
||||
issue_title = "500 error on profile"
|
||||
|
||||
expect(page).to have_content(issue_title)
|
||||
.and have_content(user.name)
|
||||
.and have_content(project.name)
|
||||
.and have_content(label_titles.first)
|
||||
fill_in("Title", with: issue_title)
|
||||
|
||||
click_button _('Select label')
|
||||
|
||||
wait_for_all_requests
|
||||
|
||||
page.within '[data-testid="sidebar-labels"]' do
|
||||
click_button label_titles.first
|
||||
click_button _('Close')
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
click_button("Create issue")
|
||||
|
||||
expect(page).to have_content(issue_title)
|
||||
.and have_content(user.name)
|
||||
.and have_content(project.name)
|
||||
.and have_content(label_titles.first)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the visible_label_selection_on_metadata feature flag disabled' do
|
||||
let(:visible_label_selection_on_metadata) { false }
|
||||
|
||||
it "creates issue" do
|
||||
issue_title = "500 error on profile"
|
||||
|
||||
fill_in("Title", with: issue_title)
|
||||
click_button("Label")
|
||||
click_link(label_titles.first)
|
||||
click_button("Create issue")
|
||||
|
||||
expect(page).to have_content(issue_title)
|
||||
.and have_content(user.name)
|
||||
.and have_content(project.name)
|
||||
.and have_content(label_titles.first)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -157,28 +157,71 @@ RSpec.describe 'Labels Hierarchy', :js, feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when creating new issuable' do
|
||||
context 'with the visible_label_selection_on_metadata feature flag enabled' do
|
||||
before do
|
||||
visit new_project_issue_path(project_1)
|
||||
stub_feature_flags(visible_label_selection_on_metadata: true)
|
||||
end
|
||||
|
||||
it 'is able to assign ancestor group labels' do
|
||||
fill_in 'issue_title', with: 'new created issue'
|
||||
fill_in 'issue_description', with: 'new issue description'
|
||||
context 'when creating new issuable' do
|
||||
before do
|
||||
visit new_project_issue_path(project_1)
|
||||
end
|
||||
|
||||
find(".js-label-select").click
|
||||
wait_for_requests
|
||||
it 'is able to assign ancestor group labels' do
|
||||
fill_in 'issue_title', with: 'new created issue'
|
||||
fill_in 'issue_description', with: 'new issue description'
|
||||
|
||||
find('a.label-item', text: grandparent_group_label.title).click
|
||||
find('a.label-item', text: parent_group_label.title).click
|
||||
find('a.label-item', text: project_label_1.title).click
|
||||
click_button _('Select label')
|
||||
|
||||
find('.btn-confirm').click
|
||||
wait_for_all_requests
|
||||
|
||||
expect(page.find('.issue-details h1.title')).to have_content('new created issue')
|
||||
expect(page).to have_selector('span.gl-label-text', text: grandparent_group_label.title)
|
||||
expect(page).to have_selector('span.gl-label-text', text: parent_group_label.title)
|
||||
expect(page).to have_selector('span.gl-label-text', text: project_label_1.title)
|
||||
page.within '[data-testid="sidebar-labels"]' do
|
||||
click_button grandparent_group_label.title
|
||||
click_button parent_group_label.title
|
||||
click_button project_label_1.title
|
||||
click_button _('Close')
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
find('.btn-confirm').click
|
||||
|
||||
expect(page.find('.issue-details h1.title')).to have_content('new created issue')
|
||||
expect(page).to have_selector('span.gl-label-text', text: grandparent_group_label.title)
|
||||
expect(page).to have_selector('span.gl-label-text', text: parent_group_label.title)
|
||||
expect(page).to have_selector('span.gl-label-text', text: project_label_1.title)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the visible_label_selection_on_metadata feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(visible_label_selection_on_metadata: false)
|
||||
end
|
||||
|
||||
context 'when creating new issuable' do
|
||||
before do
|
||||
visit new_project_issue_path(project_1)
|
||||
end
|
||||
|
||||
it 'is able to assign ancestor group labels' do
|
||||
fill_in 'issue_title', with: 'new created issue'
|
||||
fill_in 'issue_description', with: 'new issue description'
|
||||
|
||||
find(".js-label-select").click
|
||||
wait_for_requests
|
||||
|
||||
find('a.label-item', text: grandparent_group_label.title).click
|
||||
find('a.label-item', text: parent_group_label.title).click
|
||||
find('a.label-item', text: project_label_1.title).click
|
||||
|
||||
find('.btn-confirm').click
|
||||
|
||||
expect(page.find('.issue-details h1.title')).to have_content('new created issue')
|
||||
expect(page).to have_selector('span.gl-label-text', text: grandparent_group_label.title)
|
||||
expect(page).to have_selector('span.gl-label-text', text: parent_group_label.title)
|
||||
expect(page).to have_selector('span.gl-label-text', text: project_label_1.title)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -9,35 +9,158 @@ RSpec.describe 'Merge request > User creates MR', feature_category: :code_review
|
|||
stub_licensed_features(multiple_merge_request_assignees: false)
|
||||
end
|
||||
|
||||
context 'non-fork merge request' do
|
||||
include_context 'merge request create context'
|
||||
it_behaves_like 'a creatable merge request'
|
||||
shared_examples 'a creatable merge request with visible selected labels' do
|
||||
include WaitForRequests
|
||||
include ListboxHelpers
|
||||
|
||||
it 'creates new merge request', :js do
|
||||
find('[data-testid="assignee-ids-dropdown-toggle"]').click
|
||||
page.within '.dropdown-menu-user' do
|
||||
click_link user2.name
|
||||
end
|
||||
|
||||
expect(find('input[name="merge_request[assignee_ids][]"]', visible: false).value).to match(user2.id.to_s)
|
||||
page.within '[data-testid="assignee-ids-dropdown-toggle"]' do
|
||||
expect(page).to have_content user2.name
|
||||
end
|
||||
|
||||
click_link 'Assign to me'
|
||||
|
||||
expect(find('input[name="merge_request[assignee_ids][]"]', visible: false).value).to match(user.id.to_s)
|
||||
page.within '[data-testid="assignee-ids-dropdown-toggle"]' do
|
||||
expect(page).to have_content user.name
|
||||
end
|
||||
|
||||
click_button 'Select milestone'
|
||||
click_button milestone.title
|
||||
expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
|
||||
expect(page).to have_button milestone.title
|
||||
|
||||
click_button _('Select label')
|
||||
wait_for_all_requests
|
||||
page.within '[data-testid="sidebar-labels"]' do
|
||||
click_button label.title
|
||||
click_button label2.title
|
||||
click_button _('Close')
|
||||
wait_for_requests
|
||||
page.within('[data-testid="embedded-labels-list"]') do
|
||||
expect(page).to have_content(label.title)
|
||||
expect(page).to have_content(label2.title)
|
||||
end
|
||||
end
|
||||
|
||||
click_button 'Create merge request'
|
||||
|
||||
page.within '.issuable-sidebar' do
|
||||
page.within '.assignee' do
|
||||
expect(page).to have_content user.name
|
||||
end
|
||||
|
||||
page.within '.milestone' do
|
||||
expect(page).to have_content milestone.title
|
||||
end
|
||||
|
||||
page.within '.labels' do
|
||||
expect(page).to have_content label.title
|
||||
expect(page).to have_content label2.title
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'updates the branches when selecting a new target project', :js do
|
||||
target_project_member = target_project.first_owner
|
||||
::Branches::CreateService.new(target_project, target_project_member)
|
||||
.execute('a-brand-new-branch-to-test', 'master')
|
||||
|
||||
visit project_new_merge_request_path(source_project)
|
||||
|
||||
first('.js-target-project').click
|
||||
select_listbox_item(target_project.full_path)
|
||||
|
||||
wait_for_requests
|
||||
|
||||
first('.js-target-branch').click
|
||||
|
||||
find('.gl-listbox-search-input').set('a-brand-new-branch-to-test')
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect_listbox_item('a-brand-new-branch-to-test')
|
||||
end
|
||||
end
|
||||
|
||||
context 'from a forked project' do
|
||||
let(:canonical_project) { create(:project, :public, :repository) }
|
||||
|
||||
let(:source_project) do
|
||||
fork_project(canonical_project, user,
|
||||
repository: true,
|
||||
namespace: user.namespace)
|
||||
context 'with the visible_label_selection_on_metadata feature flag enabled' do
|
||||
before do
|
||||
stub_feature_flags(visible_label_selection_on_metadata: true)
|
||||
end
|
||||
|
||||
context 'to canonical project' do
|
||||
context 'non-fork merge request' do
|
||||
include_context 'merge request create context'
|
||||
it_behaves_like 'a creatable merge request'
|
||||
it_behaves_like 'a creatable merge request with visible selected labels'
|
||||
end
|
||||
|
||||
context 'to another forked project' do
|
||||
let(:target_project) do
|
||||
context 'from a forked project' do
|
||||
let(:canonical_project) { create(:project, :public, :repository) }
|
||||
|
||||
let(:source_project) do
|
||||
fork_project(canonical_project, user,
|
||||
repository: true,
|
||||
namespace: user.namespace)
|
||||
end
|
||||
|
||||
context 'to canonical project' do
|
||||
include_context 'merge request create context'
|
||||
it_behaves_like 'a creatable merge request with visible selected labels'
|
||||
end
|
||||
|
||||
context 'to another forked project' do
|
||||
let(:target_project) do
|
||||
fork_project(canonical_project, user,
|
||||
repository: true,
|
||||
namespace: user.namespace)
|
||||
end
|
||||
|
||||
include_context 'merge request create context'
|
||||
it_behaves_like 'a creatable merge request with visible selected labels'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the visible_label_selection_on_metadata feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(visible_label_selection_on_metadata: false)
|
||||
end
|
||||
|
||||
context 'non-fork merge request' do
|
||||
include_context 'merge request create context'
|
||||
it_behaves_like 'a creatable merge request'
|
||||
end
|
||||
|
||||
context 'from a forked project' do
|
||||
let(:canonical_project) { create(:project, :public, :repository) }
|
||||
|
||||
let(:source_project) do
|
||||
fork_project(canonical_project, user,
|
||||
repository: true,
|
||||
namespace: user.namespace)
|
||||
end
|
||||
|
||||
context 'to canonical project' do
|
||||
include_context 'merge request create context'
|
||||
it_behaves_like 'a creatable merge request'
|
||||
end
|
||||
|
||||
context 'to another forked project' do
|
||||
let(:target_project) do
|
||||
fork_project(canonical_project, user,
|
||||
repository: true,
|
||||
namespace: user.namespace)
|
||||
end
|
||||
|
||||
include_context 'merge request create context'
|
||||
it_behaves_like 'a creatable merge request'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'source project', :js do
|
||||
|
|
|
|||
|
|
@ -5,19 +5,227 @@ require 'spec_helper'
|
|||
RSpec.describe 'Merge request > User edits MR', feature_category: :code_review_workflow do
|
||||
include ProjectForksHelper
|
||||
|
||||
shared_examples 'an editable merge request with visible selected labels' do
|
||||
it 'updates merge request', :js do
|
||||
find('.js-assignee-search').click
|
||||
page.within '.dropdown-menu-user' do
|
||||
click_link user.name
|
||||
end
|
||||
expect(find('input[name="merge_request[assignee_ids][]"]', visible: false).value).to match(user.id.to_s)
|
||||
page.within '.js-assignee-search' do
|
||||
expect(page).to have_content user.name
|
||||
end
|
||||
|
||||
find('.js-reviewer-search').click
|
||||
page.within '.dropdown-menu-user' do
|
||||
click_link user.name
|
||||
end
|
||||
expect(find('input[name="merge_request[reviewer_ids][]"]', visible: false).value).to match(user.id.to_s)
|
||||
page.within '.js-reviewer-search' do
|
||||
expect(page).to have_content user.name
|
||||
end
|
||||
|
||||
click_button 'Select milestone'
|
||||
click_button milestone.title
|
||||
expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
|
||||
expect(page).to have_button milestone.title
|
||||
|
||||
click_button _('Select label')
|
||||
wait_for_all_requests
|
||||
page.within '[data-testid="sidebar-labels"]' do
|
||||
click_button label.title
|
||||
click_button label2.title
|
||||
click_button _('Close')
|
||||
wait_for_requests
|
||||
page.within('[data-testid="embedded-labels-list"]') do
|
||||
expect(page).to have_content(label.title)
|
||||
expect(page).to have_content(label2.title)
|
||||
end
|
||||
end
|
||||
|
||||
click_button 'Save changes'
|
||||
|
||||
page.within '.issuable-sidebar' do
|
||||
page.within '.assignee' do
|
||||
expect(page).to have_content user.name
|
||||
end
|
||||
|
||||
page.within '.reviewer' do
|
||||
expect(page).to have_content user.name
|
||||
end
|
||||
|
||||
page.within '.milestone' do
|
||||
expect(page).to have_content milestone.title
|
||||
end
|
||||
|
||||
page.within '.labels' do
|
||||
expect(page).to have_content label.title
|
||||
expect(page).to have_content label2.title
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'description has autocomplete', :js do
|
||||
find('#merge_request_description').native.send_keys('')
|
||||
fill_in 'merge_request_description', with: user.to_reference[0..4]
|
||||
|
||||
page.within('.atwho-view') do
|
||||
expect(page).to have_content(user2.name)
|
||||
end
|
||||
end
|
||||
|
||||
it 'description has quick action autocomplete', :js do
|
||||
find('#merge_request_description').native.send_keys('/')
|
||||
|
||||
expect(page).to have_selector('.atwho-container')
|
||||
end
|
||||
|
||||
it 'has class js-quick-submit in form' do
|
||||
expect(page).to have_selector('.js-quick-submit')
|
||||
end
|
||||
|
||||
it 'warns about version conflict', :js do
|
||||
merge_request.update!(title: "New title")
|
||||
|
||||
fill_in 'merge_request_title', with: 'bug 345'
|
||||
fill_in 'merge_request_description', with: 'bug description'
|
||||
|
||||
click_button 'Save changes'
|
||||
|
||||
expect(page).to have_content 'Someone edited the merge request the same time you did'
|
||||
end
|
||||
|
||||
it 'preserves description textarea height', :js do
|
||||
long_description = %q(
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
Etiam ac ornare ligula, ut tempus arcu.
|
||||
Etiam ultricies accumsan dolor vitae faucibus.
|
||||
Donec at elit lacus.
|
||||
Mauris orci ante, aliquam quis lorem eget, convallis faucibus arcu.
|
||||
Aenean at pulvinar lacus.
|
||||
Ut viverra quam massa, molestie ornare tortor dignissim a.
|
||||
Suspendisse tristique pellentesque tellus, id lacinia metus elementum id.
|
||||
Nam tristique, arcu rhoncus faucibus viverra, lacus ipsum sagittis ligula, vitae convallis odio lacus a nibh.
|
||||
Ut tincidunt est purus, ac vestibulum augue maximus in.
|
||||
Suspendisse vel erat et mi ultricies semper.
|
||||
Pellentesque volutpat pellentesque consequat.
|
||||
|
||||
Cras congue nec ligula tristique viverra.
|
||||
Curabitur fringilla fringilla fringilla.
|
||||
Donec rhoncus dignissim orci ut accumsan.
|
||||
Ut rutrum urna a rhoncus varius.
|
||||
Maecenas blandit, mauris nec accumsan gravida, augue nibh finibus magna, sed maximus turpis libero nec neque
|
||||
Suspendisse at semper est.
|
||||
Nunc imperdiet dapibus dui, varius sollicitudin erat luctus non.
|
||||
Sed pellentesque ligula eget posuere facilisis.
|
||||
Donec dictum commodo volutpat.
|
||||
Donec egestas dui ac magna sollicitudin bibendum.
|
||||
Vivamus purus neque, ullamcorper ac feugiat et, tempus sit amet metus.
|
||||
Praesent quis viverra neque.
|
||||
Sed bibendum viverra est, eu aliquam mi ornare vitae.
|
||||
Proin et dapibus ipsum.
|
||||
Nunc tortor diam, malesuada nec interdum vel, placerat quis justo.
|
||||
Ut viverra at erat eu laoreet.
|
||||
|
||||
Pellentesque commodo, diam sit amet dignissim condimentum, tortor justo pretium est,
|
||||
non venenatis metus eros ut nunc.
|
||||
Etiam ut neque eget sem dapibus aliquam.
|
||||
Curabitur vel elit lorem.
|
||||
Nulla nec enim elit.
|
||||
Sed ut ex id justo facilisis convallis at ac augue.
|
||||
Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;
|
||||
Nullam cursus egestas turpis non tristique.
|
||||
Suspendisse in erat sem.
|
||||
Fusce libero elit, fermentum gravida mauris id, auctor iaculis felis.
|
||||
Nullam vulputate tempor laoreet.
|
||||
|
||||
Nam tempor et magna sed convallis.
|
||||
Fusce sit amet sollicitudin risus, a ullamcorper lacus.
|
||||
Morbi gravida quis sem eget porttitor.
|
||||
Donec eu egestas mauris, in elementum tortor.
|
||||
Sed eget ex mi.
|
||||
Mauris iaculis tortor ut est auctor, nec dignissim quam sagittis.
|
||||
Suspendisse vel metus non quam suscipit tincidunt.
|
||||
Cras molestie lacus non justo finibus sodales quis vitae erat.
|
||||
In a porttitor nisi, id sollicitudin urna.
|
||||
Ut at felis tellus.
|
||||
Suspendisse potenti.
|
||||
|
||||
Maecenas leo ligula, varius at neque vitae, ornare maximus justo.
|
||||
Nullam convallis luctus risus et vulputate.
|
||||
Duis suscipit faucibus iaculis.
|
||||
Etiam quis tortor faucibus, tristique tellus sit amet, sodales neque.
|
||||
Nulla dapibus nisi vel aliquet consequat.
|
||||
Etiam faucibus, metus eget condimentum iaculis, enim urna lobortis sem, id efficitur eros sapien nec nisi.
|
||||
Aenean ut finibus ex.
|
||||
)
|
||||
|
||||
fill_in 'merge_request_description', with: long_description
|
||||
|
||||
height = get_textarea_height
|
||||
click_button("Preview")
|
||||
click_button("Continue editing")
|
||||
new_height = get_textarea_height
|
||||
|
||||
expect(height).to eq(new_height)
|
||||
end
|
||||
|
||||
context 'when "Remove source branch" is set' do
|
||||
before do
|
||||
merge_request.update!(merge_params: { 'force_remove_source_branch' => '1' })
|
||||
end
|
||||
|
||||
it 'allows to unselect "Remove source branch"', :js do
|
||||
expect(merge_request.merge_params['force_remove_source_branch']).to be_truthy
|
||||
|
||||
visit edit_project_merge_request_path(target_project, merge_request)
|
||||
uncheck 'Delete source branch when merge request is accepted'
|
||||
|
||||
click_button 'Save changes'
|
||||
|
||||
expect(page).to have_unchecked_field 'remove-source-branch-input'
|
||||
expect(page).to have_content 'Delete source branch'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
stub_licensed_features(multiple_merge_request_assignees: false)
|
||||
end
|
||||
|
||||
context 'non-fork merge request' do
|
||||
include_context 'merge request edit context'
|
||||
it_behaves_like 'an editable merge request'
|
||||
context 'with the visible_label_selection_on_metadata feature flag enabled' do
|
||||
before do
|
||||
stub_feature_flags(visible_label_selection_on_metadata: true)
|
||||
end
|
||||
|
||||
context 'non-fork merge request' do
|
||||
include_context 'merge request edit context'
|
||||
it_behaves_like 'an editable merge request with visible selected labels'
|
||||
end
|
||||
|
||||
context 'for a forked project' do
|
||||
let(:source_project) { fork_project(target_project, nil, repository: true) }
|
||||
|
||||
include_context 'merge request edit context'
|
||||
it_behaves_like 'an editable merge request with visible selected labels'
|
||||
end
|
||||
end
|
||||
|
||||
context 'for a forked project' do
|
||||
let(:source_project) { fork_project(target_project, nil, repository: true) }
|
||||
context 'with the visible_label_selection_on_metadata feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(visible_label_selection_on_metadata: false)
|
||||
end
|
||||
|
||||
include_context 'merge request edit context'
|
||||
it_behaves_like 'an editable merge request'
|
||||
context 'non-fork merge request' do
|
||||
include_context 'merge request edit context'
|
||||
it_behaves_like 'an editable merge request'
|
||||
end
|
||||
|
||||
context 'for a forked project' do
|
||||
let(:source_project) { fork_project(target_project, nil, repository: true) }
|
||||
|
||||
include_context 'merge request edit context'
|
||||
it_behaves_like 'an editable merge request'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlIcon } from '@gitlab/ui';
|
||||
import {
|
||||
mockRegularLabel,
|
||||
mockScopedLabel,
|
||||
|
|
@ -24,8 +23,6 @@ const workspaceType = WORKSPACE_PROJECT;
|
|||
describe('IssuableLabelSelector', () => {
|
||||
let wrapper;
|
||||
|
||||
const findTitle = () => wrapper.find('label').text().replace(/\s+/, ' ');
|
||||
const findLabelIcon = () => wrapper.findComponent(GlIcon);
|
||||
const findAllHiddenInputs = () => wrapper.findAll('input[type="hidden"]');
|
||||
const findLabelSelector = () => wrapper.findComponent(LabelsSelect);
|
||||
|
||||
|
|
@ -47,23 +44,11 @@ describe('IssuableLabelSelector', () => {
|
|||
});
|
||||
};
|
||||
|
||||
const expectTitleWithCount = (count) => {
|
||||
const title = findTitle();
|
||||
|
||||
expect(title).toContain(__('Labels'));
|
||||
expect(title).toContain(count.toString());
|
||||
};
|
||||
|
||||
describe('by default', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = createComponent();
|
||||
});
|
||||
|
||||
it('has the selected labels count', () => {
|
||||
expectTitleWithCount(0);
|
||||
expect(findLabelIcon().props('name')).toBe('labels');
|
||||
});
|
||||
|
||||
it('has the label selector', () => {
|
||||
expect(findLabelSelector().props()).toMatchObject({
|
||||
allowLabelRemove,
|
||||
|
|
@ -89,7 +74,6 @@ describe('IssuableLabelSelector', () => {
|
|||
it('passing initial labels applies them to the form', () => {
|
||||
wrapper = createComponent({ initialLabels: [mockRegularLabel, mockScopedLabel] });
|
||||
|
||||
expectTitleWithCount(2);
|
||||
expect(findLabelSelector().props('selectedLabels')).toStrictEqual([
|
||||
mockRegularLabel,
|
||||
mockScopedLabel,
|
||||
|
|
@ -103,13 +87,11 @@ describe('IssuableLabelSelector', () => {
|
|||
it('updates the selected labels on the `updateSelectedLabels` event', async () => {
|
||||
wrapper = createComponent();
|
||||
|
||||
expectTitleWithCount(0);
|
||||
expect(findLabelSelector().props('selectedLabels')).toStrictEqual([]);
|
||||
expect(findAllHiddenInputs()).toHaveLength(0);
|
||||
|
||||
await findLabelSelector().vm.$emit('updateSelectedLabels', { labels: [mockRegularLabel] });
|
||||
|
||||
expectTitleWithCount(1);
|
||||
expect(findLabelSelector().props('selectedLabels')).toStrictEqual([mockRegularLabel]);
|
||||
expect(findAllHiddenInputs().wrappers.map((input) => input.element.value)).toStrictEqual([
|
||||
`${mockRegularLabel.id}`,
|
||||
|
|
@ -119,7 +101,6 @@ describe('IssuableLabelSelector', () => {
|
|||
it('updates the selected labels on the `onLabelRemove` event', async () => {
|
||||
wrapper = createComponent({ initialLabels: [mockRegularLabel] });
|
||||
|
||||
expectTitleWithCount(1);
|
||||
expect(findLabelSelector().props('selectedLabels')).toStrictEqual([mockRegularLabel]);
|
||||
expect(findAllHiddenInputs().wrappers.map((input) => input.element.value)).toStrictEqual([
|
||||
`${mockRegularLabel.id}`,
|
||||
|
|
@ -127,7 +108,6 @@ describe('IssuableLabelSelector', () => {
|
|||
|
||||
await findLabelSelector().vm.$emit('onLabelRemove', mockRegularLabel.id);
|
||||
|
||||
expectTitleWithCount(0);
|
||||
expect(findLabelSelector().props('selectedLabels')).toStrictEqual([]);
|
||||
expect(findAllHiddenInputs()).toHaveLength(0);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -728,4 +728,61 @@ RSpec.describe IssuablesHelper, feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#issuable_label_selector_data' do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
|
||||
context 'with a new issuable' do
|
||||
let_it_be(:issuable) { build(:issue, project: project) }
|
||||
|
||||
it 'returns the expected data' do
|
||||
expect(helper.issuable_label_selector_data(project, issuable)).to match({
|
||||
field_name: "#{issuable.class.model_name.param_key}[label_ids][]",
|
||||
full_path: project.full_path,
|
||||
initial_labels: '[]',
|
||||
issuable_type: issuable.issuable_type,
|
||||
labels_filter_base_path: project_issues_path(project),
|
||||
labels_manage_path: project_labels_path(project)
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an existing issuable' do
|
||||
let_it_be(:label) { create(:label, name: 'Bug') }
|
||||
let_it_be(:label2) { create(:label, name: 'Community contribution') }
|
||||
let_it_be(:issuable) do
|
||||
create(:merge_request, source_project: project, target_project: project, labels: [label, label2])
|
||||
end
|
||||
|
||||
it 'returns the expected data' do
|
||||
initial_labels = [
|
||||
{
|
||||
__typename: "Label",
|
||||
id: label.id,
|
||||
title: label.title,
|
||||
description: label.description,
|
||||
color: label.color,
|
||||
text_color: label.text_color
|
||||
},
|
||||
{
|
||||
__typename: "Label",
|
||||
id: label2.id,
|
||||
title: label2.title,
|
||||
description: label2.description,
|
||||
color: label2.color,
|
||||
text_color: label2.text_color
|
||||
}
|
||||
]
|
||||
|
||||
expect(helper.issuable_label_selector_data(project, issuable)).to match({
|
||||
field_name: "#{issuable.class.model_name.param_key}[label_ids][]",
|
||||
full_path: project.full_path,
|
||||
initial_labels: initial_labels.to_json,
|
||||
issuable_type: issuable.issuable_type,
|
||||
labels_filter_base_path: project_merge_requests_path(project),
|
||||
labels_manage_path: project_labels_path(project)
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ RSpec.describe API::Entities::PersonalAccessToken do
|
|||
user_id: user.id,
|
||||
last_used_at: nil,
|
||||
active: true,
|
||||
expires_at: nil
|
||||
expires_at: token.expires_at.iso8601
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
require_migration!
|
||||
|
||||
RSpec.describe RemoveShimoZentaoIntegrationRecords, feature_category: :integrations do
|
||||
let(:integrations) { table(:integrations) }
|
||||
let(:zentao_tracker_data) { table(:zentao_tracker_data) }
|
||||
|
||||
before do
|
||||
integrations.create!(id: 1, type_new: 'Integrations::MockMonitoring')
|
||||
integrations.create!(id: 2, type_new: 'Integrations::Redmine')
|
||||
integrations.create!(id: 3, type_new: 'Integrations::Confluence')
|
||||
|
||||
integrations.create!(id: 4, type_new: 'Integrations::Shimo')
|
||||
integrations.create!(id: 5, type_new: 'Integrations::Zentao')
|
||||
integrations.create!(id: 6, type_new: 'Integrations::Zentao')
|
||||
zentao_tracker_data.create!(id: 1, integration_id: 5)
|
||||
zentao_tracker_data.create!(id: 2, integration_id: 6)
|
||||
end
|
||||
|
||||
context 'with CE/EE env' do
|
||||
it 'destroys all shimo and zentao integrations' do
|
||||
migrate!
|
||||
|
||||
expect(integrations.count).to eq(3) # keep other integrations
|
||||
expect(integrations.where(type_new: described_class::TYPES).count).to eq(0)
|
||||
expect(zentao_tracker_data.count).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with JiHu env' do
|
||||
before do
|
||||
allow(Gitlab).to receive(:jh?).and_return(true)
|
||||
end
|
||||
|
||||
it 'keeps shimo and zentao integrations' do
|
||||
migrate!
|
||||
|
||||
expect(integrations.count).to eq(6)
|
||||
expect(integrations.where(type_new: described_class::TYPES).count).to eq(3)
|
||||
expect(zentao_tracker_data.count).to eq(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
require_migration!
|
||||
|
||||
RSpec.describe BackfillCorrectedSecureFilesExpirations, migration: :gitlab_ci, feature_category: :mobile_devops do
|
||||
let(:migration) { described_class.new }
|
||||
let(:ci_secure_files) { table(:ci_secure_files) }
|
||||
|
||||
let!(:file1) { ci_secure_files.create!(project_id: 1, name: "file.cer", file: "foo", checksum: 'bar') }
|
||||
let!(:file2) { ci_secure_files.create!(project_id: 1, name: "file.p12", file: "foo", checksum: 'bar') }
|
||||
let!(:file3) { ci_secure_files.create!(project_id: 1, name: "file.jks", file: "foo", checksum: 'bar') }
|
||||
|
||||
describe '#up' do
|
||||
it 'enqueues the ParseSecureFileMetadataWorker job for relevant file types', :aggregate_failures do
|
||||
expect(::Ci::ParseSecureFileMetadataWorker).to receive(:perform_async).with(file1.id)
|
||||
expect(::Ci::ParseSecureFileMetadataWorker).to receive(:perform_async).with(file2.id)
|
||||
expect(::Ci::ParseSecureFileMetadataWorker).not_to receive(:perform_async).with(file3.id)
|
||||
|
||||
migration.up
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -976,7 +976,7 @@ RSpec.describe Issuable do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#incident?' do
|
||||
describe '#incident_type_issue?' do
|
||||
where(:issuable_type, :incident) do
|
||||
:issue | false
|
||||
:incident | true
|
||||
|
|
@ -986,7 +986,7 @@ RSpec.describe Issuable do
|
|||
with_them do
|
||||
let(:issuable) { build_stubbed(issuable_type) }
|
||||
|
||||
subject { issuable.incident? }
|
||||
subject { issuable.incident_type_issue? }
|
||||
|
||||
it { is_expected.to eq(incident) }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2001,4 +2001,18 @@ RSpec.describe Issue, feature_category: :team_planning do
|
|||
|
||||
it { is_expected.to eq(WorkItems::Type.default_by_type(::Issue::DEFAULT_ISSUE_TYPE)) }
|
||||
end
|
||||
|
||||
describe 'issue_type enum generated methods' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be(:issue) { create(:issue, project: reusable_project) }
|
||||
|
||||
where(issue_type: WorkItems::Type.base_types.keys)
|
||||
|
||||
with_them do
|
||||
it 'raises an error if called' do
|
||||
expect { issue.public_send("#{issue_type}?".to_sym) }.to raise_error(Issue::ForbiddenColumnUsed)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -267,6 +267,41 @@ RSpec.describe PersonalAccessToken, feature_category: :system_access do
|
|||
expect(personal_access_token).not_to be_valid
|
||||
expect(personal_access_token.errors[:scopes].first).to eq "can only contain available scopes"
|
||||
end
|
||||
|
||||
context 'validates expires_at' do
|
||||
let(:max_expiration_date) { described_class::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now }
|
||||
|
||||
context 'when default_pat_expiration feature flag is true' do
|
||||
context 'when expires_in is less than MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS days' do
|
||||
it 'is valid' do
|
||||
personal_access_token.expires_at = max_expiration_date - 1.day
|
||||
|
||||
expect(personal_access_token).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context 'when expires_in is more than MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS days' do
|
||||
it 'is invalid' do
|
||||
personal_access_token.expires_at = max_expiration_date + 1.day
|
||||
|
||||
expect(personal_access_token).not_to be_valid
|
||||
expect(personal_access_token.errors[:expires_at].first).to eq('must expire in 365 days')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when default_pat_expiration feature flag is false' do
|
||||
before do
|
||||
stub_feature_flags(default_pat_expiration: false)
|
||||
end
|
||||
|
||||
it 'allows any expires_at value' do
|
||||
personal_access_token.expires_at = max_expiration_date + 1.day
|
||||
|
||||
expect(personal_access_token).to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'scopes' do
|
||||
|
|
@ -289,7 +324,7 @@ RSpec.describe PersonalAccessToken, feature_category: :system_access do
|
|||
let_it_be(:revoked_token) { create(:personal_access_token, revoked: true) }
|
||||
let_it_be(:valid_token_and_notified) { create(:personal_access_token, expires_at: 2.days.from_now, expire_notification_delivered: true) }
|
||||
let_it_be(:valid_token) { create(:personal_access_token, expires_at: 2.days.from_now) }
|
||||
let_it_be(:long_expiry_token) { create(:personal_access_token, expires_at: '999999-12-31'.to_date) }
|
||||
let_it_be(:long_expiry_token) { create(:personal_access_token, expires_at: described_class::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now) }
|
||||
|
||||
context 'in one day' do
|
||||
it "doesn't have any tokens" do
|
||||
|
|
@ -427,4 +462,36 @@ RSpec.describe PersonalAccessToken, feature_category: :system_access do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#expires_at=' do
|
||||
let(:personal_access_token) { described_class.new }
|
||||
|
||||
context 'when default_pat_expiration feature flag is true' do
|
||||
context 'expires_at set to empty value' do
|
||||
[nil, ""].each do |expires_in_value|
|
||||
it 'defaults to PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS' do
|
||||
personal_access_token.expires_at = expires_in_value
|
||||
|
||||
freeze_time do
|
||||
expect(personal_access_token.expires_at).to eq(
|
||||
PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now.to_date
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when default_pat_expiration feature flag is false' do
|
||||
before do
|
||||
stub_feature_flags(default_pat_expiration: false)
|
||||
end
|
||||
|
||||
it 'does not set a default' do
|
||||
personal_access_token.expires_at = nil
|
||||
|
||||
expect(personal_access_token.expires_at).to eq(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,4 +4,5 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe ProtectedBranch::MergeAccessLevel, feature_category: :source_code_management do
|
||||
include_examples 'protected branch access'
|
||||
include_examples 'protected ref access allowed_access_levels'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe ProtectedBranch::PushAccessLevel, feature_category: :source_code_management do
|
||||
include_examples 'protected branch access'
|
||||
include_examples 'protected ref access allowed_access_levels'
|
||||
|
||||
describe 'associations' do
|
||||
it { is_expected.to belong_to(:deploy_key) }
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe ProtectedTag::CreateAccessLevel, feature_category: :source_code_management do
|
||||
include_examples 'protected tag access'
|
||||
include_examples 'protected ref access allowed_access_levels'
|
||||
|
||||
describe 'associations' do
|
||||
it { is_expected.to belong_to(:deploy_key) }
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ RSpec.describe 'UserAchievements', feature_category: :user_profile do
|
|||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group, :public) }
|
||||
let_it_be(:achievement) { create(:achievement, namespace: group) }
|
||||
let_it_be(:user_achievement1) { create(:user_achievement, achievement: achievement, user: user) }
|
||||
let_it_be(:user_achievement2) { create(:user_achievement, :revoked, achievement: achievement, user: user) }
|
||||
let_it_be(:non_revoked_achievement1) { create(:user_achievement, achievement: achievement, user: user) }
|
||||
let_it_be(:non_revoked_achievement2) { create(:user_achievement, :revoked, achievement: achievement, user: user) }
|
||||
let_it_be(:fields) do
|
||||
<<~HEREDOC
|
||||
id
|
||||
|
|
@ -51,11 +51,10 @@ RSpec.describe 'UserAchievements', feature_category: :user_profile do
|
|||
|
||||
it_behaves_like 'a working graphql query'
|
||||
|
||||
it 'returns all user_achievements' do
|
||||
it 'returns all non_revoked user_achievements' do
|
||||
expect(graphql_data_at(:namespace, :achievements, :nodes, :userAchievements, :nodes))
|
||||
.to contain_exactly(
|
||||
a_graphql_entity_for(user_achievement1),
|
||||
a_graphql_entity_for(user_achievement2)
|
||||
a_graphql_entity_for(non_revoked_achievement1)
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -65,9 +64,7 @@ RSpec.describe 'UserAchievements', feature_category: :user_profile do
|
|||
end.count
|
||||
|
||||
user2 = create(:user)
|
||||
create_list(:achievement, 3, namespace: group) do |a|
|
||||
create(:user_achievement, achievement: a, user: user2)
|
||||
end
|
||||
create(:user_achievement, achievement: achievement, user: user2)
|
||||
|
||||
expect { post_graphql(query, current_user: user) }.not_to exceed_all_query_limit(control_count)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ RSpec.describe 'UserAchievements', feature_category: :user_profile do
|
|||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group, :private) }
|
||||
let_it_be(:achievement) { create(:achievement, namespace: group) }
|
||||
let_it_be(:user_achievements) { create_list(:user_achievement, 2, achievement: achievement, user: user) }
|
||||
let_it_be(:non_revoked_achievement) { create(:user_achievement, achievement: achievement, user: user) }
|
||||
let_it_be(:revoked_achievement) { create(:user_achievement, :revoked, achievement: achievement, user: user) }
|
||||
let_it_be(:fields) do
|
||||
<<~HEREDOC
|
||||
userAchievements {
|
||||
|
|
@ -47,10 +48,9 @@ RSpec.describe 'UserAchievements', feature_category: :user_profile do
|
|||
|
||||
it_behaves_like 'a working graphql query'
|
||||
|
||||
it 'returns all user_achievements' do
|
||||
it 'returns all non_revoked user_achievements' do
|
||||
expect(graphql_data_at(:user, :userAchievements, :nodes)).to contain_exactly(
|
||||
a_graphql_entity_for(user_achievements[0]),
|
||||
a_graphql_entity_for(user_achievements[1])
|
||||
a_graphql_entity_for(non_revoked_achievement)
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -88,8 +88,7 @@ RSpec.describe 'UserAchievements', feature_category: :user_profile do
|
|||
|
||||
it 'returns all achievements' do
|
||||
expect(graphql_data_at(:user, :userAchievements, :nodes)).to contain_exactly(
|
||||
a_graphql_entity_for(user_achievements[0]),
|
||||
a_graphql_entity_for(user_achievements[1])
|
||||
a_graphql_entity_for(non_revoked_achievement)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
|
|||
let_it_be(:project, reload: true) { create(:project, :repository, :wiki_repo) }
|
||||
let_it_be(:personal_snippet) { create(:personal_snippet, :repository, author: user) }
|
||||
let_it_be(:project_snippet) { create(:project_snippet, :repository, author: user, project: project) }
|
||||
let_it_be(:max_pat_access_token_lifetime) do
|
||||
PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now.to_date.freeze
|
||||
end
|
||||
|
||||
let(:key) { create(:key, user: user) }
|
||||
let(:secret_token) { Gitlab::Shell.secret_token }
|
||||
|
|
@ -194,39 +197,68 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
|
|||
expect(json_response['message']).to match(/\AInvalid scope: 'badscope'. Valid scopes are: /)
|
||||
end
|
||||
|
||||
it 'returns a token without expiry when the expires_at parameter is missing' do
|
||||
token_size = (PersonalAccessToken.token_prefix || '').size + 20
|
||||
it 'returns a token with expiry when it receives a valid expires_at parameter' do
|
||||
freeze_time do
|
||||
token_size = (PersonalAccessToken.token_prefix || '').size + 20
|
||||
|
||||
post api('/internal/personal_access_token'),
|
||||
params: {
|
||||
key_id: key.id,
|
||||
name: 'newtoken',
|
||||
scopes: %w(read_api read_repository)
|
||||
},
|
||||
headers: gitlab_shell_internal_api_request_header
|
||||
post api('/internal/personal_access_token'),
|
||||
params: {
|
||||
key_id: key.id,
|
||||
name: 'newtoken',
|
||||
scopes: %w(read_api read_repository),
|
||||
expires_at: max_pat_access_token_lifetime
|
||||
},
|
||||
headers: gitlab_shell_internal_api_request_header
|
||||
|
||||
expect(json_response['success']).to be_truthy
|
||||
expect(json_response['token']).to match(/\A\S{#{token_size}}\z/)
|
||||
expect(json_response['scopes']).to match_array(%w(read_api read_repository))
|
||||
expect(json_response['expires_at']).to be_nil
|
||||
expect(json_response['success']).to be_truthy
|
||||
expect(json_response['token']).to match(/\A\S{#{token_size}}\z/)
|
||||
expect(json_response['scopes']).to match_array(%w(read_api read_repository))
|
||||
expect(json_response['expires_at']).to eq(max_pat_access_token_lifetime.iso8601)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns a token with expiry when it receives a valid expires_at parameter' do
|
||||
token_size = (PersonalAccessToken.token_prefix || '').size + 20
|
||||
context 'when default_pat_expiration feature flag is true' do
|
||||
it 'returns token with expiry as PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS' do
|
||||
freeze_time do
|
||||
token_size = (PersonalAccessToken.token_prefix || '').size + 20
|
||||
|
||||
post api('/internal/personal_access_token'),
|
||||
params: {
|
||||
key_id: key.id,
|
||||
name: 'newtoken',
|
||||
scopes: %w(read_api read_repository),
|
||||
expires_at: '9001-11-17'
|
||||
},
|
||||
headers: gitlab_shell_internal_api_request_header
|
||||
post api('/internal/personal_access_token'),
|
||||
params: {
|
||||
key_id: key.id,
|
||||
name: 'newtoken',
|
||||
scopes: %w(read_api read_repository)
|
||||
},
|
||||
headers: gitlab_shell_internal_api_request_header
|
||||
|
||||
expect(json_response['success']).to be_truthy
|
||||
expect(json_response['token']).to match(/\A\S{#{token_size}}\z/)
|
||||
expect(json_response['scopes']).to match_array(%w(read_api read_repository))
|
||||
expect(json_response['expires_at']).to eq('9001-11-17')
|
||||
expect(json_response['success']).to be_truthy
|
||||
expect(json_response['token']).to match(/\A\S{#{token_size}}\z/)
|
||||
expect(json_response['scopes']).to match_array(%w(read_api read_repository))
|
||||
expect(json_response['expires_at']).to eq(max_pat_access_token_lifetime.iso8601)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when default_pat_expiration feature flag is false' do
|
||||
before do
|
||||
stub_feature_flags(default_pat_expiration: false)
|
||||
end
|
||||
|
||||
it 'uses nil expiration value' do
|
||||
token_size = (PersonalAccessToken.token_prefix || '').size + 20
|
||||
|
||||
post api('/internal/personal_access_token'),
|
||||
params: {
|
||||
key_id: key.id,
|
||||
name: 'newtoken',
|
||||
scopes: %w(read_api read_repository)
|
||||
},
|
||||
headers: gitlab_shell_internal_api_request_header
|
||||
|
||||
expect(json_response['success']).to be_truthy
|
||||
expect(json_response['token']).to match(/\A\S{#{token_size}}\z/)
|
||||
expect(json_response['scopes']).to match_array(%w(read_api read_repository))
|
||||
expect(json_response['expires_at']).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1210,7 +1210,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
|
|||
it 'allows issue type to be converted' do
|
||||
put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { issue_type: 'incident' }
|
||||
|
||||
expect(issue.reload.incident?).to be(true)
|
||||
expect(issue.reload.work_item_type.incident?).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -336,13 +336,33 @@ RSpec.describe API::ResourceAccessTokens, feature_category: :system_access do
|
|||
context "when 'expires_at' is not set" do
|
||||
let(:expires_at) { nil }
|
||||
|
||||
it "creates a #{source_type} access token with the params", :aggregate_failures do
|
||||
create_token
|
||||
context 'when default_pat_expiration feature flag is true' do
|
||||
it "creates a #{source_type} access token with the default expires_at value", :aggregate_failures do
|
||||
freeze_time do
|
||||
create_token
|
||||
expires_at = PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(json_response["name"]).to eq("test")
|
||||
expect(json_response["scopes"]).to eq(["api"])
|
||||
expect(json_response["expires_at"]).to eq(nil)
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(json_response["name"]).to eq("test")
|
||||
expect(json_response["scopes"]).to eq(["api"])
|
||||
expect(json_response["expires_at"]).to eq(expires_at.to_date.iso8601)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when default_pat_expiration feature flag is false' do
|
||||
before do
|
||||
stub_feature_flags(default_pat_expiration: false)
|
||||
end
|
||||
|
||||
it "creates a #{source_type} access token with the params", :aggregate_failures do
|
||||
create_token
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(json_response["name"]).to eq("test")
|
||||
expect(json_response["scopes"]).to eq(["api"])
|
||||
expect(json_response["expires_at"]).to eq(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ RSpec.describe AccessTokenEntityBase do
|
|||
revoked: false,
|
||||
created_at: token.created_at,
|
||||
scopes: token.scopes,
|
||||
expires_at: nil,
|
||||
expires_at: token.expires_at.iso8601,
|
||||
expired: false,
|
||||
expires_soon: false
|
||||
))
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ RSpec.describe Issues::CreateService, feature_category: :team_planning do
|
|||
context 'when a build_service is provided' do
|
||||
let(:result) { described_class.new(container: project, current_user: user, params: opts, spam_params: spam_params, build_service: build_service).execute }
|
||||
|
||||
let(:issue_from_builder) { WorkItem.new(project: project, title: 'Issue from builder') }
|
||||
let(:issue_from_builder) { build(:work_item, project: project, title: 'Issue from builder') }
|
||||
let(:build_service) { double(:build_service, execute: issue_from_builder) }
|
||||
|
||||
it 'uses the provided service to build the issue' do
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ RSpec.describe ResourceAccessTokens::CreateService, feature_category: :system_ac
|
|||
let_it_be(:project) { create(:project, :private) }
|
||||
let_it_be(:group) { create(:group, :private) }
|
||||
let_it_be(:params) { {} }
|
||||
let_it_be(:max_pat_access_token_lifetime) do
|
||||
PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now.to_date.freeze
|
||||
end
|
||||
|
||||
before do
|
||||
stub_config_setting(host: 'example.com')
|
||||
|
|
@ -185,20 +188,51 @@ RSpec.describe ResourceAccessTokens::CreateService, feature_category: :system_ac
|
|||
|
||||
context 'expires_at' do
|
||||
context 'when no expiration value is passed' do
|
||||
it 'uses nil expiration value' do
|
||||
response = subject
|
||||
access_token = response.payload[:access_token]
|
||||
context 'when default_pat_expiration feature flag is true' do
|
||||
it 'defaults to PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS' do
|
||||
freeze_time do
|
||||
response = subject
|
||||
access_token = response.payload[:access_token]
|
||||
|
||||
expect(access_token.expires_at).to eq(nil)
|
||||
expect(access_token.expires_at).to eq(
|
||||
max_pat_access_token_lifetime.to_date
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'expiry of the project bot member' do
|
||||
it 'project bot membership does not expire' do
|
||||
response = subject
|
||||
access_token = response.payload[:access_token]
|
||||
project_bot = access_token.user
|
||||
|
||||
expect(resource.members.find_by(user_id: project_bot.id).expires_at).to eq(
|
||||
max_pat_access_token_lifetime.to_date
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'expiry of the project bot member' do
|
||||
it 'project bot membership does not expire' do
|
||||
context 'when default_pat_expiration feature flag is false' do
|
||||
before do
|
||||
stub_feature_flags(default_pat_expiration: false)
|
||||
end
|
||||
|
||||
it 'uses nil expiration value' do
|
||||
response = subject
|
||||
access_token = response.payload[:access_token]
|
||||
project_bot = access_token.user
|
||||
|
||||
expect(resource.members.find_by(user_id: project_bot.id).expires_at).to eq(nil)
|
||||
expect(access_token.expires_at).to eq(nil)
|
||||
end
|
||||
|
||||
context 'expiry of the project bot member' do
|
||||
it 'project bot membership expires' do
|
||||
response = subject
|
||||
access_token = response.payload[:access_token]
|
||||
project_bot = access_token.user
|
||||
|
||||
expect(resource.members.find_by(user_id: project_bot.id).expires_at).to eq(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -219,7 +253,7 @@ RSpec.describe ResourceAccessTokens::CreateService, feature_category: :system_ac
|
|||
access_token = response.payload[:access_token]
|
||||
project_bot = access_token.user
|
||||
|
||||
expect(resource.members.find_by(user_id: project_bot.id).expires_at).to eq(params[:expires_at])
|
||||
expect(resource.members.find_by(user_id: project_bot.id).expires_at).to eq(access_token.expires_at)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,20 +5,20 @@ RSpec.shared_examples 'a creatable merge request' do
|
|||
include ListboxHelpers
|
||||
|
||||
it 'creates new merge request', :js do
|
||||
find('.js-assignee-search').click
|
||||
find('[data-testid="assignee-ids-dropdown-toggle"]').click
|
||||
page.within '.dropdown-menu-user' do
|
||||
click_link user2.name
|
||||
end
|
||||
|
||||
expect(find('input[name="merge_request[assignee_ids][]"]', visible: false).value).to match(user2.id.to_s)
|
||||
page.within '.js-assignee-search' do
|
||||
page.within '[data-testid="assignee-ids-dropdown-toggle"]' do
|
||||
expect(page).to have_content user2.name
|
||||
end
|
||||
|
||||
click_link 'Assign to me'
|
||||
|
||||
expect(find('input[name="merge_request[assignee_ids][]"]', visible: false).value).to match(user.id.to_s)
|
||||
page.within '.js-assignee-search' do
|
||||
page.within '[data-testid="assignee-ids-dropdown-toggle"]' do
|
||||
expect(page).to have_content user.name
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'protected ref access allowed_access_levels' do |excludes: []|
|
||||
describe '::allowed_access_levels' do
|
||||
subject { described_class.allowed_access_levels }
|
||||
|
||||
let(:all_levels) do
|
||||
[
|
||||
Gitlab::Access::DEVELOPER,
|
||||
Gitlab::Access::MAINTAINER,
|
||||
Gitlab::Access::ADMIN,
|
||||
Gitlab::Access::NO_ACCESS
|
||||
]
|
||||
end
|
||||
|
||||
context 'when running on Gitlab.com?' do
|
||||
let(:levels) { all_levels.excluding(Gitlab::Access::ADMIN, *excludes) }
|
||||
|
||||
before do
|
||||
allow(Gitlab).to receive(:com?).and_return(true)
|
||||
end
|
||||
|
||||
it { is_expected.to match_array(levels) }
|
||||
end
|
||||
|
||||
context 'when self hosted?' do
|
||||
let(:levels) { all_levels.excluding(*excludes) }
|
||||
|
||||
before do
|
||||
allow(Gitlab).to receive(:com?).and_return(false)
|
||||
end
|
||||
|
||||
it { is_expected.to match_array(levels) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -18,6 +18,21 @@ RSpec.shared_examples 'protected ref access' do |association|
|
|||
it { is_expected.not_to validate_presence_of(:access_level) }
|
||||
end
|
||||
|
||||
describe '::human_access_levels' do
|
||||
subject { described_class.human_access_levels }
|
||||
|
||||
let(:levels) do
|
||||
{
|
||||
Gitlab::Access::DEVELOPER => "Developers + Maintainers",
|
||||
Gitlab::Access::MAINTAINER => "Maintainers",
|
||||
Gitlab::Access::ADMIN => 'Instance admins',
|
||||
Gitlab::Access::NO_ACCESS => "No one"
|
||||
}.slice(*described_class.allowed_access_levels)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(levels) }
|
||||
end
|
||||
|
||||
describe '#check_access' do
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
|
||||
|
|
@ -44,6 +59,22 @@ RSpec.shared_examples 'protected ref access' do |association|
|
|||
it { expect(subject.check_access(current_user)).to eq(false) }
|
||||
end
|
||||
|
||||
context 'when instance admin access is configured' do
|
||||
let(:access_level) { Gitlab::Access::ADMIN }
|
||||
|
||||
context 'when current_user is a maintainer' do
|
||||
it { expect(subject.check_access(current_user)).to eq(false) }
|
||||
end
|
||||
|
||||
context 'when current_user is admin' do
|
||||
before do
|
||||
allow(current_user).to receive(:admin?).and_return(true)
|
||||
end
|
||||
|
||||
it { expect(subject.check_access(current_user)).to eq(true) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when current_user can push_code to project' do
|
||||
context 'and member access is high enough' do
|
||||
it { expect(subject.check_access(current_user)).to eq(true) }
|
||||
|
|
|
|||
|
|
@ -35,30 +35,76 @@ RSpec.describe 'projects/merge_requests/edit.html.haml' do
|
|||
.and_return(User.find(closed_merge_request.author_id))
|
||||
end
|
||||
|
||||
context 'when a merge request without fork' do
|
||||
it "shows editable fields" do
|
||||
unlink_project.execute
|
||||
closed_merge_request.reload
|
||||
|
||||
shared_examples 'merge request shows editable fields' do
|
||||
it 'shows editable fields' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_field('merge_request[title]')
|
||||
expect(rendered).to have_selector('input[name="merge_request[description]"]', visible: false)
|
||||
expect(rendered).to have_selector('input[name="merge_request[label_ids][]"]', visible: false)
|
||||
expect(rendered).to have_selector('.js-milestone-dropdown-root')
|
||||
expect(rendered).not_to have_selector('#merge_request_target_branch', visible: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a merge request with an existing source project is closed' do
|
||||
it "shows editable fields" do
|
||||
render
|
||||
|
||||
expect(rendered).to have_field('merge_request[title]')
|
||||
expect(rendered).to have_selector('input[name="merge_request[description]"]', visible: false)
|
||||
expect(rendered).to have_selector('input[name="merge_request[label_ids][]"]', visible: false)
|
||||
expect(rendered).to have_selector('.js-milestone-dropdown-root')
|
||||
expect(rendered).to have_selector('#merge_request_target_branch', visible: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the visible_label_selection_on_metadata feature flag enabled' do
|
||||
before do
|
||||
stub_feature_flags(visible_label_selection_on_metadata: true)
|
||||
end
|
||||
|
||||
context 'when a merge request without fork' do
|
||||
it_behaves_like 'merge request shows editable fields'
|
||||
|
||||
it "shows editable fields" do
|
||||
unlink_project.execute
|
||||
closed_merge_request.reload
|
||||
|
||||
render
|
||||
|
||||
expect(rendered).not_to have_selector('#merge_request_target_branch', visible: false)
|
||||
expect(rendered).to have_selector('.js-issuable-form-label-selector')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a merge request with an existing source project is closed' do
|
||||
it_behaves_like 'merge request shows editable fields'
|
||||
|
||||
it "shows editable fields" do
|
||||
render
|
||||
|
||||
expect(rendered).to have_selector('#merge_request_target_branch', visible: false)
|
||||
expect(rendered).to have_selector('.js-issuable-form-label-selector')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the visible_label_selection_on_metadata feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(visible_label_selection_on_metadata: false)
|
||||
end
|
||||
|
||||
context 'when a merge request without fork' do
|
||||
it_behaves_like 'merge request shows editable fields'
|
||||
|
||||
it "shows editable fields" do
|
||||
unlink_project.execute
|
||||
closed_merge_request.reload
|
||||
|
||||
render
|
||||
|
||||
expect(rendered).not_to have_selector('#merge_request_target_branch', visible: false)
|
||||
expect(rendered).not_to have_selector('.js-issuable-form-label-selector')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a merge request with an existing source project is closed' do
|
||||
it_behaves_like 'merge request shows editable fields'
|
||||
|
||||
it "shows editable fields" do
|
||||
render
|
||||
|
||||
expect(rendered).to have_selector('#merge_request_target_branch', visible: false)
|
||||
expect(rendered).not_to have_selector('.js-issuable-form-label-selector')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
31
yarn.lock
31
yarn.lock
|
|
@ -4025,12 +4025,17 @@ commander@7, commander@^7.0.0, commander@^7.2.0:
|
|||
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
|
||||
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
|
||||
|
||||
commander@^10.0.1:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
|
||||
integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==
|
||||
|
||||
commander@^6.0.0:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
|
||||
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
|
||||
|
||||
commander@^9.4.0, commander@~9.4.0:
|
||||
commander@~9.4.0:
|
||||
version "9.4.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.0.tgz#bc4a40918fefe52e22450c111ecd6b7acce6f11c"
|
||||
integrity sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==
|
||||
|
|
@ -11191,10 +11196,10 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
|
|||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||
|
||||
semver@^7.3.4, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7:
|
||||
version "7.3.7"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
|
||||
integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
|
||||
semver@^7.3.4, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7, semver@^7.5.0:
|
||||
version "7.5.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.0.tgz#ed8c5dc8efb6c629c88b23d41dc9bf40c1d96cd0"
|
||||
integrity sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
|
|
@ -12228,7 +12233,7 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
|
|||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.4.1:
|
||||
tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.4.1, tslib@^2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
|
||||
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
|
||||
|
|
@ -13348,15 +13353,15 @@ yarn-check-webpack-plugin@^1.2.0:
|
|||
dependencies:
|
||||
chalk "^2.4.2"
|
||||
|
||||
yarn-deduplicate@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-6.0.0.tgz#91bc0b7b374efe24796606df2c6b00eabb5aab62"
|
||||
integrity sha512-HjGVvuy10hetOuXeexXXT77V+6FfgS+NiW3FsmQD88yfF2kBqTpChvMglyKUlQ0xXEcI77VJazll5qKKBl3ssw==
|
||||
yarn-deduplicate@^6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-6.0.2.tgz#63498d2d4c3a8567e992a994ce0ab51aa5681f2e"
|
||||
integrity sha512-Efx4XEj82BgbRJe5gvQbZmEO7pU5DgHgxohYZp98/+GwPqdU90RXtzvHirb7hGlde0sQqk5G3J3Woyjai8hVqA==
|
||||
dependencies:
|
||||
"@yarnpkg/lockfile" "^1.1.0"
|
||||
commander "^9.4.0"
|
||||
semver "^7.3.7"
|
||||
tslib "^2.4.0"
|
||||
commander "^10.0.1"
|
||||
semver "^7.5.0"
|
||||
tslib "^2.5.0"
|
||||
|
||||
yocto-queue@^0.1.0:
|
||||
version "0.1.0"
|
||||
|
|
|
|||
Loading…
Reference in New Issue