summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-05-12 00:12:55 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-05-12 00:12:55 +0000
commit4e65fc3589914bc328539943f1164f4aff2b8d58 (patch)
tree0bdfcd44063ce9148fc121a2635bc05a6186f0eb
parent9643359dd3a54154ecf0cb8efab39599529aa90c (diff)
downloadgitlab-ce-4e65fc3589914bc328539943f1164f4aff2b8d58.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/CODEOWNERS4
-rw-r--r--.gitlab/ci/package-and-test-nightly/main.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/preflight.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/qa-common/main.gitlab-ci.yml7
-rw-r--r--.gitlab/ci/review-apps/main.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/review-apps/qa.gitlab-ci.yml2
-rw-r--r--.rubocop_todo/cop/user_admin.yml2
-rw-r--r--app/assets/javascripts/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/labels/labels_select_widget/embedded_labels_list.vue2
-rw-r--r--app/assets/javascripts/vue_shared/issuable/create/components/issuable_label_selector.vue7
-rw-r--r--app/controllers/projects/issues_controller.rb2
-rw-r--r--app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb2
-rw-r--r--app/graphql/resolvers/achievements/user_achievements_resolver.rb2
-rw-r--r--app/graphql/types/achievements/achievement_type.rb4
-rw-r--r--app/helpers/form_helper.rb3
-rw-r--r--app/helpers/issuables_helper.rb31
-rw-r--r--app/helpers/issues_helper.rb2
-rw-r--r--app/models/achievements/user_achievement.rb1
-rw-r--r--app/models/concerns/issuable.rb12
-rw-r--r--app/models/concerns/protected_ref_access.rb23
-rw-r--r--app/models/issue.rb23
-rw-r--r--app/models/personal_access_token.rb24
-rw-r--r--app/policies/issuable_policy.rb2
-rw-r--r--app/services/concerns/incident_management/usage_data.rb2
-rw-r--r--app/services/issues/base_service.rb2
-rw-r--r--app/services/issues/close_service.rb2
-rw-r--r--app/services/issues/create_service.rb2
-rw-r--r--app/services/issues/reopen_service.rb2
-rw-r--r--app/services/resource_access_tokens/create_service.rb10
-rw-r--r--app/services/resource_events/change_labels_service.rb2
-rw-r--r--app/views/projects/issues/_design_management.html.haml2
-rw-r--r--app/views/shared/issuable/form/_metadata.html.haml15
-rw-r--r--app/views/shared/issuable/form/_type_selector.html.haml2
-rw-r--r--config/feature_flags/development/default_pat_expiration.yml7
-rw-r--r--config/feature_flags/development/visible_label_selection_on_metadata.yml8
-rw-r--r--data/removals/16_0/16-0-Security-report-schemas-version-14.yml11
-rw-r--r--data/removals/16_0/16-0-dast-api-variable-removal.yml2
-rw-r--r--data/removals/16_0/16-0-grafana-chart.yml2
-rw-r--r--data/removals/16_0/16-0-limit-ci-job-token.yml2
-rw-r--r--data/removals/16_0/16-0-non-expiring-access-tokens.yml19
-rw-r--r--data/removals/16_0/16-0-postgresql-12.yml2
-rw-r--r--data/removals/16_0/16.0-config-fields-runner-helm-chart.yml2
-rw-r--r--data/removals/16_0/16.0-eol-windows-server-2004-and-20H2.yml2
-rw-r--r--data/removals/16_0/16.0-runner-api-does-not-return-paused-active.yml2
-rw-r--r--db/post_migrate/20230428085332_remove_shimo_zentao_integration_records.rb21
-rw-r--r--db/post_migrate/20230508175057_backfill_corrected_secure_files_expirations.rb27
-rw-r--r--db/schema_migrations/202304280853321
-rw-r--r--db/schema_migrations/202305081750571
-rw-r--r--doc/api/discussions.md10
-rw-r--r--doc/api/graphql/reference/index.md1
-rw-r--r--doc/development/database/database_migration_pipeline.md38
-rw-r--r--doc/update/index.md4
-rw-r--r--doc/update/removals.md20
-rw-r--r--doc/user/group/settings/group_access_tokens.md13
-rw-r--r--doc/user/profile/personal_access_tokens.md14
-rw-r--r--doc/user/project/settings/project_access_tokens.md14
-rw-r--r--doc/user/workspace/index.md2
-rw-r--r--lib/gitlab/access.rb1
-rw-r--r--lib/gitlab/database/gitlab_schema.rb2
-rw-r--r--lib/gitlab/quick_actions/issue_actions.rb4
-rw-r--r--lib/tasks/gitlab/db.rake2
-rw-r--r--locale/gitlab.pot5
-rw-r--r--package.json2
-rw-r--r--spec/features/issues/form_spec.rb431
-rw-r--r--spec/features/issues/user_creates_issue_spec.rb55
-rw-r--r--spec/features/labels_hierarchy_spec.rb73
-rw-r--r--spec/features/merge_request/user_creates_mr_spec.rb151
-rw-r--r--spec/features/merge_request/user_edits_mr_spec.rb222
-rw-r--r--spec/frontend/vue_shared/issuable/create/components/issuable_label_selector_spec.js20
-rw-r--r--spec/helpers/issuables_helper_spec.rb57
-rw-r--r--spec/lib/api/entities/personal_access_token_spec.rb2
-rw-r--r--spec/migrations/20230428085332_remove_shimo_zentao_integration_records_spec.rb46
-rw-r--r--spec/migrations/20230508175057_backfill_corrected_secure_files_expirations_spec.rb24
-rw-r--r--spec/models/concerns/issuable_spec.rb4
-rw-r--r--spec/models/issue_spec.rb14
-rw-r--r--spec/models/personal_access_token_spec.rb69
-rw-r--r--spec/models/protected_branch/merge_access_level_spec.rb1
-rw-r--r--spec/models/protected_branch/push_access_level_spec.rb1
-rw-r--r--spec/models/protected_tag/create_access_level_spec.rb1
-rw-r--r--spec/requests/api/graphql/achievements/user_achievements_query_spec.rb13
-rw-r--r--spec/requests/api/graphql/user/user_achievements_query_spec.rb11
-rw-r--r--spec/requests/api/internal/base_spec.rb86
-rw-r--r--spec/requests/api/issues/issues_spec.rb2
-rw-r--r--spec/requests/api/resource_access_tokens_spec.rb32
-rw-r--r--spec/serializers/access_token_entity_base_spec.rb2
-rw-r--r--spec/services/issues/create_service_spec.rb2
-rw-r--r--spec/services/resource_access_tokens/create_service_spec.rb52
-rw-r--r--spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/models/concerns/protected_ref_access_allowed_access_levels_examples.rb36
-rw-r--r--spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb31
-rw-r--r--spec/views/projects/merge_requests/edit.html.haml_spec.rb76
-rw-r--r--yarn.lock31
92 files changed, 1641 insertions, 355 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index 3ce956b7022..f555355bfc9 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -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
diff --git a/.gitlab/ci/package-and-test-nightly/main.gitlab-ci.yml b/.gitlab/ci/package-and-test-nightly/main.gitlab-ci.yml
index 4e240bedf3a..a5474d00cb6 100644
--- a/.gitlab/ci/package-and-test-nightly/main.gitlab-ci.yml
+++ b/.gitlab/ci/package-and-test-nightly/main.gitlab-ci.yml
@@ -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:
diff --git a/.gitlab/ci/preflight.gitlab-ci.yml b/.gitlab/ci/preflight.gitlab-ci.yml
index 04cb36354a9..e477466e5f3 100644
--- a/.gitlab/ci/preflight.gitlab-ci.yml
+++ b/.gitlab/ci/preflight.gitlab-ci.yml
@@ -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)
diff --git a/.gitlab/ci/qa-common/main.gitlab-ci.yml b/.gitlab/ci/qa-common/main.gitlab-ci.yml
index 3be1bc955c5..3f33b3e8451 100644
--- a/.gitlab/ci/qa-common/main.gitlab-ci.yml
+++ b/.gitlab/ci/qa-common/main.gitlab-ci.yml
@@ -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]
# ==========================================
diff --git a/.gitlab/ci/review-apps/main.gitlab-ci.yml b/.gitlab/ci/review-apps/main.gitlab-ci.yml
index 28416c89f68..680254a6640 100644
--- a/.gitlab/ci/review-apps/main.gitlab-ci.yml
+++ b/.gitlab/ci/review-apps/main.gitlab-ci.yml
@@ -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}"
diff --git a/.gitlab/ci/review-apps/qa.gitlab-ci.yml b/.gitlab/ci/review-apps/qa.gitlab-ci.yml
index 0cfd4bbfb93..c01317ad9bd 100644
--- a/.gitlab/ci/review-apps/qa.gitlab-ci.yml
+++ b/.gitlab/ci/review-apps/qa.gitlab-ci.yml
@@ -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:
diff --git a/.rubocop_todo/cop/user_admin.yml b/.rubocop_todo/cop/user_admin.yml
index ce16309d3f8..82f57e52888 100644
--- a/.rubocop_todo/cop/user_admin.yml
+++ b/.rubocop_todo/cop/user_admin.yml
@@ -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'
diff --git a/app/assets/javascripts/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view.vue b/app/assets/javascripts/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view.vue
index dce80af8a5e..1d8b21700c3 100644
--- a/app/assets/javascripts/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view.vue
+++ b/app/assets/javascripts/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view.vue
@@ -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
diff --git a/app/assets/javascripts/sidebar/components/labels/labels_select_widget/embedded_labels_list.vue b/app/assets/javascripts/sidebar/components/labels/labels_select_widget/embedded_labels_list.vue
index 3a93fc7f3b2..a3bacc4a674 100644
--- a/app/assets/javascripts/sidebar/components/labels/labels_select_widget/embedded_labels_list.vue
+++ b/app/assets/javascripts/sidebar/components/labels/labels_select_widget/embedded_labels_list.vue
@@ -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"
diff --git a/app/assets/javascripts/vue_shared/issuable/create/components/issuable_label_selector.vue b/app/assets/javascripts/vue_shared/issuable/create/components/issuable_label_selector.vue
index b3f9c8d9fcd..efb6e626c07 100644
--- a/app/assets/javascripts/vue_shared/issuable/create/components/issuable_label_selector.vue
+++ b/app/assets/javascripts/vue_shared/issuable/create/components/issuable_label_selector.vue
@@ -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">
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index a56c7410d0f..642d5943854 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -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
diff --git a/app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb b/app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb
index bb1da9278ff..c29dc98c872 100644
--- a/app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb
+++ b/app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb
@@ -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
diff --git a/app/graphql/resolvers/achievements/user_achievements_resolver.rb b/app/graphql/resolvers/achievements/user_achievements_resolver.rb
index bf09d80afc1..77fb15c3d93 100644
--- a/app/graphql/resolvers/achievements/user_achievements_resolver.rb
+++ b/app/graphql/resolvers/achievements/user_achievements_resolver.rb
@@ -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
diff --git a/app/graphql/types/achievements/achievement_type.rb b/app/graphql/types/achievements/achievement_type.rb
index ff4c49dac5a..ec558981465 100644
--- a/app/graphql/types/achievements/achievement_type.rb
+++ b/app/graphql/types/achievements/achievement_type.rb
@@ -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)
diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb
index a4d90716129..ed8cca20241 100644
--- a/app/helpers/form_helper.rb
+++ b/app/helpers/form_helper.rb
@@ -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'
}
}
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index d058d0f697c..3796d8f0210 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -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?
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 2f002be632d..fae8d86098e 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -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
diff --git a/app/models/achievements/user_achievement.rb b/app/models/achievements/user_achievement.rb
index 844780c6164..08ebadaa6b0 100644
--- a/app/models/achievements/user_achievement.rb
+++ b/app/models/achievements/user_achievement.rb
@@ -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?
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 6594884ca0a..b1ec6b8ba32 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -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?
diff --git a/app/models/concerns/protected_ref_access.rb b/app/models/concerns/protected_ref_access.rb
index b841211c811..c1c670db543 100644
--- a/app/models/concerns/protected_ref_access.rb
+++ b/app/models/concerns/protected_ref_access.rb
@@ -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
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 0d33c6a71aa..b7125617034 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -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
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index 3ebb2126f4d..75afff6a2fa 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -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')
diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb
index 400ac528018..60ab1785972 100644
--- a/app/policies/issuable_policy.rb
+++ b/app/policies/issuable_policy.rb
@@ -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? }
diff --git a/app/services/concerns/incident_management/usage_data.rb b/app/services/concerns/incident_management/usage_data.rb
index 775dea9b949..f7edbb80d09 100644
--- a/app/services/concerns/incident_management/usage_data.rb
+++ b/app/services/concerns/incident_management/usage_data.rb
@@ -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)
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 05090efe260..efe42fb29d5 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -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
diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb
index 87e27ef2763..e45033f2b91 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -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
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index ce19d77ca49..ba8f00d03d4 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -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
diff --git a/app/services/issues/reopen_service.rb b/app/services/issues/reopen_service.rb
index 3330c462947..f4d229ecec7 100644
--- a/app/services/issues/reopen_service.rb
+++ b/app/services/issues/reopen_service.rb
@@ -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
diff --git a/app/services/resource_access_tokens/create_service.rb b/app/services/resource_access_tokens/create_service.rb
index cfa43f5d9c8..553315f08f9 100644
--- a/app/services/resource_access_tokens/create_service.rb
+++ b/app/services/resource_access_tokens/create_service.rb
@@ -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)
diff --git a/app/services/resource_events/change_labels_service.rb b/app/services/resource_events/change_labels_service.rb
index 02182bc3a77..69e68922b91 100644
--- a/app/services/resource_events/change_labels_service.rb
+++ b/app/services/resource_events/change_labels_service.rb
@@ -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,
diff --git a/app/views/projects/issues/_design_management.html.haml b/app/views/projects/issues/_design_management.html.haml
index df5ab1d4a7c..de8725df871 100644
--- a/app/views/projects/issues/_design_management.html.haml
+++ b/app/views/projects/issues/_design_management.html.haml
@@ -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 }
diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml
index 9603178f7de..b27fd8ab7d2 100644
--- a/app/views/shared/issuable/form/_metadata.html.haml
+++ b/app/views/shared/issuable/form/_metadata.html.haml
@@ -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
diff --git a/app/views/shared/issuable/form/_type_selector.html.haml b/app/views/shared/issuable/form/_type_selector.html.haml
index 2350864f0a6..0bcdcb9e963 100644
--- a/app/views/shared/issuable/form/_type_selector.html.haml
+++ b/app/views/shared/issuable/form/_type_selector.html.haml
@@ -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)
diff --git a/config/feature_flags/development/default_pat_expiration.yml b/config/feature_flags/development/default_pat_expiration.yml
new file mode 100644
index 00000000000..b48d6a02723
--- /dev/null
+++ b/config/feature_flags/development/default_pat_expiration.yml
@@ -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
diff --git a/config/feature_flags/development/visible_label_selection_on_metadata.yml b/config/feature_flags/development/visible_label_selection_on_metadata.yml
new file mode 100644
index 00000000000..bf173b26d44
--- /dev/null
+++ b/config/feature_flags/development/visible_label_selection_on_metadata.yml
@@ -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
diff --git a/data/removals/16_0/16-0-Security-report-schemas-version-14.yml b/data/removals/16_0/16-0-Security-report-schemas-version-14.yml
new file mode 100644
index 00000000000..14d5dd7acd2
--- /dev/null
+++ b/data/removals/16_0/16-0-Security-report-schemas-version-14.yml
@@ -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]
diff --git a/data/removals/16_0/16-0-dast-api-variable-removal.yml b/data/removals/16_0/16-0-dast-api-variable-removal.yml
index 15dd4c6d781..1b67eeb0fa7 100644
--- a/data/removals/16_0/16-0-dast-api-variable-removal.yml
+++ b/data/removals/16_0/16-0-dast-api-variable-removal.yml
@@ -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.
diff --git a/data/removals/16_0/16-0-grafana-chart.yml b/data/removals/16_0/16-0-grafana-chart.yml
index 012d5043a98..3251f477bb0 100644
--- a/data/removals/16_0/16-0-grafana-chart.yml
+++ b/data/removals/16_0/16-0-grafana-chart.yml
@@ -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
diff --git a/data/removals/16_0/16-0-limit-ci-job-token.yml b/data/removals/16_0/16-0-limit-ci-job-token.yml
index 9f262f9c772..1409677233c 100644
--- a/data/removals/16_0/16-0-limit-ci-job-token.yml
+++ b/data/removals/16_0/16-0-limit-ci-job-token.yml
@@ -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.
diff --git a/data/removals/16_0/16-0-non-expiring-access-tokens.yml b/data/removals/16_0/16-0-non-expiring-access-tokens.yml
new file mode 100644
index 00000000000..5ef7df3a131
--- /dev/null
+++ b/data/removals/16_0/16-0-non-expiring-access-tokens.yml
@@ -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
diff --git a/data/removals/16_0/16-0-postgresql-12.yml b/data/removals/16_0/16-0-postgresql-12.yml
index 9aa8102154b..9c860af7dbc 100644
--- a/data/removals/16_0/16-0-postgresql-12.yml
+++ b/data/removals/16_0/16-0-postgresql-12.yml
@@ -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
diff --git a/data/removals/16_0/16.0-config-fields-runner-helm-chart.yml b/data/removals/16_0/16.0-config-fields-runner-helm-chart.yml
index 47067eda63d..63c50842a0d 100644
--- a/data/removals/16_0/16.0-config-fields-runner-helm-chart.yml
+++ b/data/removals/16_0/16.0-config-fields-runner-helm-chart.yml
@@ -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:
diff --git a/data/removals/16_0/16.0-eol-windows-server-2004-and-20H2.yml b/data/removals/16_0/16.0-eol-windows-server-2004-and-20H2.yml
index 9b47beed41b..267304f6a13 100644
--- a/data/removals/16_0/16.0-eol-windows-server-2004-and-20H2.yml
+++ b/data/removals/16_0/16.0-eol-windows-server-2004-and-20H2.yml
@@ -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.
diff --git a/data/removals/16_0/16.0-runner-api-does-not-return-paused-active.yml b/data/removals/16_0/16.0-runner-api-does-not-return-paused-active.yml
index 17ab36d36d2..7da64404eb3 100644
--- a/data/removals/16_0/16.0-runner-api-does-not-return-paused-active.yml
+++ b/data/removals/16_0/16.0-runner-api-does-not-return-paused-active.yml
@@ -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`.
diff --git a/db/post_migrate/20230428085332_remove_shimo_zentao_integration_records.rb b/db/post_migrate/20230428085332_remove_shimo_zentao_integration_records.rb
new file mode 100644
index 00000000000..079f1527e01
--- /dev/null
+++ b/db/post_migrate/20230428085332_remove_shimo_zentao_integration_records.rb
@@ -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
diff --git a/db/post_migrate/20230508175057_backfill_corrected_secure_files_expirations.rb b/db/post_migrate/20230508175057_backfill_corrected_secure_files_expirations.rb
new file mode 100644
index 00000000000..9644a555756
--- /dev/null
+++ b/db/post_migrate/20230508175057_backfill_corrected_secure_files_expirations.rb
@@ -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
diff --git a/db/schema_migrations/20230428085332 b/db/schema_migrations/20230428085332
new file mode 100644
index 00000000000..8ad6c10ada2
--- /dev/null
+++ b/db/schema_migrations/20230428085332
@@ -0,0 +1 @@
+9e822fbc2c7ce8044d0b38c5f1a9056431792e83fc9ed83056444c094e16c484 \ No newline at end of file
diff --git a/db/schema_migrations/20230508175057 b/db/schema_migrations/20230508175057
new file mode 100644
index 00000000000..959c02b49c8
--- /dev/null
+++ b/db/schema_migrations/20230508175057
@@ -0,0 +1 @@
+eaec908173fb60b88867e14c73c6ba7d6079742bae7ead59fa021d6d57e622da \ No newline at end of file
diff --git a/doc/api/discussions.md b/doc/api/discussions.md
index 3eeef5d4afc..15bbc802817 100644
--- a/doc/api/discussions.md
+++ b/doc/api/discussions.md
@@ -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. |
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 263689894ca..938c2b812e6 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -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`
diff --git a/doc/development/database/database_migration_pipeline.md b/doc/development/database/database_migration_pipeline.md
index 70f9c1523c0..a9d525e2a41 100644
--- a/doc/development/database/database_migration_pipeline.md
+++ b/doc/development/database/database_migration_pipeline.md
@@ -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"
+```
diff --git a/doc/update/index.md b/doc/update/index.md
index 74aec1bdf1e..b5442263106 100644
--- a/doc/update/index.md
+++ b/doc/update/index.md
@@ -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
diff --git a/doc/update/removals.md b/doc/update/removals.md
index d0a6952745a..ee7c0159d5a 100644
--- a/doc/update/removals.md
+++ b/doc/update/removals.md
@@ -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.
diff --git a/doc/user/group/settings/group_access_tokens.md b/doc/user/group/settings/group_access_tokens.md
index bb421e4e470..be9821e1b68 100644
--- a/doc/user/group/settings/group_access_tokens.md
+++ b/doc/user/group/settings/group_access_tokens.md
@@ -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**.
diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md
index 0d886519766..e59d7313281 100644
--- a/doc/user/profile/personal_access_tokens.md
+++ b/doc/user/profile/personal_access_tokens.md
@@ -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**.
diff --git a/doc/user/project/settings/project_access_tokens.md b/doc/user/project/settings/project_access_tokens.md
index ff69d7e4763..a9201f57155 100644
--- a/doc/user/project/settings/project_access_tokens.md
+++ b/doc/user/project/settings/project_access_tokens.md
@@ -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**.
diff --git a/doc/user/workspace/index.md b/doc/user/workspace/index.md
index 60688ac9356..0d540023c2c 100644
--- a/doc/user/workspace/index.md
+++ b/doc/user/workspace/index.md
@@ -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.
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb
index fa025a2658f..bafda11170a 100644
--- a/lib/gitlab/access.rb
+++ b/lib/gitlab/access.rb
@@ -16,6 +16,7 @@ module Gitlab
DEVELOPER = 30
MAINTAINER = 40
OWNER = 50
+ ADMIN = 60
# Branch protection settings
PROTECTION_NONE = 0
diff --git a/lib/gitlab/database/gitlab_schema.rb b/lib/gitlab/database/gitlab_schema.rb
index b04a2058be0..4394c089b22 100644
--- a/lib/gitlab/database/gitlab_schema.rb
+++ b/lib/gitlab/database/gitlab_schema.rb
@@ -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
diff --git a/lib/gitlab/quick_actions/issue_actions.rb b/lib/gitlab/quick_actions/issue_actions.rb
index 10e8c702826..d7e9e1a980b 100644
--- a/lib/gitlab/quick_actions/issue_actions.rb
+++ b/lib/gitlab/quick_actions/issue_actions.rb
@@ -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|
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index ad5595afe0e..34ccce3ba2f 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -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)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 669e97cf0e7..8f934560f07 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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 ""
diff --git a/package.json b/package.json
index 9b625983758..30dafb88268 100644
--- a/package.json
+++ b/package.json
@@ -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"
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index 560e8ddd07b..9702e43a559 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -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,110 +116,240 @@ 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
+
+ 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 _('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 issue'
- click_link 'Assign to me'
- assignee_ids = page.all('input[name="issue[assignee_ids][]"]', visible: false)
+ page.within '.issuable-sidebar' do
+ page.within '.assignee' do
+ expect(page).to have_content "Assignee"
+ end
- expect(assignee_ids[0].value).to match(user.id.to_s)
+ page.within '.milestone' do
+ expect(page).to have_content milestone.title
+ end
- page.within '.js-assignee-search' do
- expect(page).to have_content user.name
+ 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
- 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
+ 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
- click_button 'Labels'
- page.within '.dropdown-menu-labels' do
- click_link label.title
- click_link label2.title
+ 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
- find('.js-issuable-form-dropdown.js-label-select').click
+ it 'clears label search input field when a label is selected', :js do
+ click_button _('Select label')
+
+ wait_for_all_requests
- page.within '.js-label-select' do
- expect(page).to have_content label.title
+ 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
- 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)
+ end
- click_button 'Create issue'
+ 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
- page.within '.issuable-sidebar' do
- page.within '.assignee' do
- expect(page).to have_content "Assignee"
+ 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
- page.within '.milestone' do
- expect(page).to have_content milestone.title
+ 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
- page.within '.labels' do
+ 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
- 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)
- page.within '.breadcrumbs' do
- issue = Issue.find_by(title: 'title')
+ click_button 'Create issue'
- expect(page).to have_text("Issues #{issue.to_reference}")
- end
- end
+ page.within '.issuable-sidebar' do
+ page.within '.assignee' do
+ expect(page).to have_content "Assignee"
+ end
- it 'displays an error message when submitting an invalid form' do
- click_button 'Create issue'
+ page.within '.milestone' do
+ expect(page).to have_content milestone.title
+ end
- page.within('[data-testid="issue-title-input-field"]') do
- expect(page).to have_text(_('This field is required.'))
- end
- end
+ page.within '.labels' do
+ expect(page).to have_content label.title
+ expect(page).to have_content label2.title
+ end
+ end
- it 'correctly updates the dropdown toggle when removing a label' do
- click_button 'Labels'
+ page.within '.breadcrumbs' do
+ issue = Issue.find_by(title: 'title')
- page.within '.dropdown-menu-labels' do
- click_link label.title
+ expect(page).to have_text("Issues #{issue.to_reference}")
+ end
end
- expect(find('.js-label-select')).to have_content(label.title)
+ 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
- page.within '.dropdown-menu-labels' do
- click_link label.title
+ 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
- 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'
- 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
- page.within '.dropdown-menu-labels' do
- search_field = find('input[type="search"]')
+ it 'displays an error message when submitting an invalid form' do
+ click_button 'Create issue'
- search_field.set(label2.title)
- click_link label2.title
- expect(search_field.value).to eq ''
+ page.within('[data-testid="issue-title-input-field"]') do
+ expect(page).to have_text(_('This field is required.'))
end
end
@@ -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
+
+ page.within '.js-user-search' do
+ expect(page).to have_content user.name
+ end
- expect(page).to have_button milestone.title
+ 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
+ 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
+
+ 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
- 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)
+ end
- click_button 'Save changes'
+ 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 '.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
- end
+ expect(page).to have_button milestone.title
- page.within '.labels' do
+ 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
+
+ context 'with the visible_label_selection_on_metadata feature flag disabled' do
+ let(:visible_label_selection_on_metadata) { false }
- page.within '.dropdown-menu-labels' do
- expect(page).to have_link 'test label'
+ 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
diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb
index 075b81d9af4..d4148717f0a 100644
--- a/spec/features/issues/user_creates_issue_spec.rb
+++ b/spec/features/issues/user_creates_issue_spec.rb
@@ -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 }
+
+ it "creates issue" do
+ issue_title = "500 error on profile"
+
+ 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")
+ 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)
+ 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
diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb
index d6e607e80df..e8f40a1ceab 100644
--- a/spec/features/labels_hierarchy_spec.rb
+++ b/spec/features/labels_hierarchy_spec.rb
@@ -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
+
+ 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'
+
+ click_button _('Select label')
+
+ wait_for_all_requests
+
+ 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
- find(".js-label-select").click
- wait_for_requests
+ context 'with the visible_label_selection_on_metadata feature flag disabled' do
+ before do
+ stub_feature_flags(visible_label_selection_on_metadata: false)
+ end
- 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
+ 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('.btn-confirm').click
+ 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
- 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)
+ 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
diff --git a/spec/features/merge_request/user_creates_mr_spec.rb b/spec/features/merge_request/user_creates_mr_spec.rb
index 6ee20a08a47..f48315a1636 100644
--- a/spec/features/merge_request/user_creates_mr_spec.rb
+++ b/spec/features/merge_request/user_creates_mr_spec.rb
@@ -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'
- end
+ 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
- context 'from a forked project' do
- let(:canonical_project) { create(:project, :public, :repository) }
+ page.within '.milestone' do
+ expect(page).to have_content milestone.title
+ end
- let(:source_project) do
- fork_project(canonical_project, user,
- repository: true,
- namespace: user.namespace)
+ page.within '.labels' do
+ expect(page).to have_content label.title
+ expect(page).to have_content label2.title
+ end
+ end
end
- context 'to canonical project' do
+ 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 '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 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
diff --git a/spec/features/merge_request/user_edits_mr_spec.rb b/spec/features/merge_request/user_edits_mr_spec.rb
index 6fcbfd309e2..114fa5b6ecb 100644
--- a/spec/features/merge_request/user_edits_mr_spec.rb
+++ b/spec/features/merge_request/user_edits_mr_spec.rb
@@ -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
+
+ 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'
+ include_context 'merge request edit context'
+ it_behaves_like 'an editable merge request'
+ end
end
end
diff --git a/spec/frontend/vue_shared/issuable/create/components/issuable_label_selector_spec.js b/spec/frontend/vue_shared/issuable/create/components/issuable_label_selector_spec.js
index 679b1f082b4..1a490359040 100644
--- a/spec/frontend/vue_shared/issuable/create/components/issuable_label_selector_spec.js
+++ b/spec/frontend/vue_shared/issuable/create/components/issuable_label_selector_spec.js
@@ -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);
});
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 960a7e03e49..ffaffa251d1 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -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
diff --git a/spec/lib/api/entities/personal_access_token_spec.rb b/spec/lib/api/entities/personal_access_token_spec.rb
index fd3c53a21b4..7f79cc80573 100644
--- a/spec/lib/api/entities/personal_access_token_spec.rb
+++ b/spec/lib/api/entities/personal_access_token_spec.rb
@@ -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
diff --git a/spec/migrations/20230428085332_remove_shimo_zentao_integration_records_spec.rb b/spec/migrations/20230428085332_remove_shimo_zentao_integration_records_spec.rb
new file mode 100644
index 00000000000..1d2fbb6b95d
--- /dev/null
+++ b/spec/migrations/20230428085332_remove_shimo_zentao_integration_records_spec.rb
@@ -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
diff --git a/spec/migrations/20230508175057_backfill_corrected_secure_files_expirations_spec.rb b/spec/migrations/20230508175057_backfill_corrected_secure_files_expirations_spec.rb
new file mode 100644
index 00000000000..570be0e02c7
--- /dev/null
+++ b/spec/migrations/20230508175057_backfill_corrected_secure_files_expirations_spec.rb
@@ -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
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index de46da4e27b..e11c7e98287 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -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
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index ead15b0b640..6ae33fe2642 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -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
diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb
index 45b8de509fc..bd6a7c156c4 100644
--- a/spec/models/personal_access_token_spec.rb
+++ b/spec/models/personal_access_token_spec.rb
@@ -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
diff --git a/spec/models/protected_branch/merge_access_level_spec.rb b/spec/models/protected_branch/merge_access_level_spec.rb
index 7b003e369ee..92aa5fa3eee 100644
--- a/spec/models/protected_branch/merge_access_level_spec.rb
+++ b/spec/models/protected_branch/merge_access_level_spec.rb
@@ -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
diff --git a/spec/models/protected_branch/push_access_level_spec.rb b/spec/models/protected_branch/push_access_level_spec.rb
index 837703cd2b3..e56ff2241b1 100644
--- a/spec/models/protected_branch/push_access_level_spec.rb
+++ b/spec/models/protected_branch/push_access_level_spec.rb
@@ -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) }
diff --git a/spec/models/protected_tag/create_access_level_spec.rb b/spec/models/protected_tag/create_access_level_spec.rb
index 77a7d15b597..8eeccdc9b34 100644
--- a/spec/models/protected_tag/create_access_level_spec.rb
+++ b/spec/models/protected_tag/create_access_level_spec.rb
@@ -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) }
diff --git a/spec/requests/api/graphql/achievements/user_achievements_query_spec.rb b/spec/requests/api/graphql/achievements/user_achievements_query_spec.rb
index dc19f3bdc91..080f375245d 100644
--- a/spec/requests/api/graphql/achievements/user_achievements_query_spec.rb
+++ b/spec/requests/api/graphql/achievements/user_achievements_query_spec.rb
@@ -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
diff --git a/spec/requests/api/graphql/user/user_achievements_query_spec.rb b/spec/requests/api/graphql/user/user_achievements_query_spec.rb
index be67009784b..27d32d07372 100644
--- a/spec/requests/api/graphql/user/user_achievements_query_spec.rb
+++ b/spec/requests/api/graphql/user/user_achievements_query_spec.rb
@@ -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
diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb
index dff41c4c477..6414b1efe6a 100644
--- a/spec/requests/api/internal/base_spec.rb
+++ b/spec/requests/api/internal/base_spec.rb
@@ -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),
+ expires_at: max_pat_access_token_lifetime
+ },
+ 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(max_pat_access_token_lifetime.iso8601)
+ end
+ end
+
+ 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
- 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
+ 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(max_pat_access_token_lifetime.iso8601)
+ end
+ 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 false' do
+ before do
+ stub_feature_flags(default_pat_expiration: false)
+ end
- 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
+ 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 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 be_nil
+ end
end
end
diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb
index 15a89527677..af289352778 100644
--- a/spec/requests/api/issues/issues_spec.rb
+++ b/spec/requests/api/issues/issues_spec.rb
@@ -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
diff --git a/spec/requests/api/resource_access_tokens_spec.rb b/spec/requests/api/resource_access_tokens_spec.rb
index a13aa48a497..ce05fa2b383 100644
--- a/spec/requests/api/resource_access_tokens_spec.rb
+++ b/spec/requests/api/resource_access_tokens_spec.rb
@@ -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(expires_at.to_date.iso8601)
+ end
+ end
+ end
- 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)
+ 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
diff --git a/spec/serializers/access_token_entity_base_spec.rb b/spec/serializers/access_token_entity_base_spec.rb
index e14a07a346a..8a92a53d0c1 100644
--- a/spec/serializers/access_token_entity_base_spec.rb
+++ b/spec/serializers/access_token_entity_base_spec.rb
@@ -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
))
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index d58a52dd35b..548d9455ebf 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -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
diff --git a/spec/services/resource_access_tokens/create_service_spec.rb b/spec/services/resource_access_tokens/create_service_spec.rb
index 464fc18f1f0..59d582f038a 100644
--- a/spec/services/resource_access_tokens/create_service_spec.rb
+++ b/spec/services/resource_access_tokens/create_service_spec.rb
@@ -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(
+ max_pat_access_token_lifetime.to_date
+ )
+ end
+ end
- expect(access_token.expires_at).to eq(nil)
+ 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
diff --git a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
index 96e57980c68..7e0e235698e 100644
--- a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
@@ -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
diff --git a/spec/support/shared_examples/models/concerns/protected_ref_access_allowed_access_levels_examples.rb b/spec/support/shared_examples/models/concerns/protected_ref_access_allowed_access_levels_examples.rb
new file mode 100644
index 00000000000..8e15720c79a
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/protected_ref_access_allowed_access_levels_examples.rb
@@ -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
diff --git a/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb b/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb
index f6ca2b91616..4753d7a4556 100644
--- a/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb
+++ b/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb
@@ -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) }
diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
index 8774623d07e..bb8a4455775 100644
--- a/spec/views/projects/merge_requests/edit.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
@@ -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)
+ expect(rendered).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
+ context 'with the visible_label_selection_on_metadata feature flag enabled' do
+ before do
+ stub_feature_flags(visible_label_selection_on_metadata: true)
+ end
- 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)
+ 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
diff --git a/yarn.lock b/yarn.lock
index 9e80a92792b..9c9a98d4184 100644
--- a/yarn.lock
+++ b/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"