Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-09-06 18:12:21 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-09-06 18:12:21 +0300
commit74739c4b9e2ced14233eaeee221c5472589d26cd (patch)
tree4941af4c79d998d0f9b99de8e3b11c19765ee9a8
parente08e64feb78ad8a490769b2aa59a13166bbdc073 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/jobs/components/job/job_log_controllers.vue2
-rw-r--r--app/assets/javascripts/jobs/components/table/cells/job_cell.vue6
-rw-r--r--app/assets/javascripts/jobs/components/table/cells/pipeline_cell.vue10
-rw-r--r--app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue17
-rw-r--r--app/finders/work_items/namespace_work_items_finder.rb27
-rw-r--r--app/graphql/resolvers/ci/all_jobs_resolver.rb1
-rw-r--r--app/graphql/resolvers/ci/runner_jobs_resolver.rb1
-rw-r--r--app/graphql/resolvers/namespaces/work_items_resolver.rb38
-rw-r--r--app/graphql/resolvers/work_items_resolver.rb11
-rw-r--r--app/models/users/callout.rb3
-rw-r--r--app/services/issuable_base_service.rb13
-rw-r--r--app/services/labels/available_labels_service.rb15
-rw-r--r--app/services/merge_requests/base_service.rb10
-rw-r--r--app/views/admin/application_settings/_account_and_limit.html.haml2
-rw-r--r--app/views/devise/sessions/_new_base.html.haml2
-rw-r--r--app/views/projects/settings/merge_requests/show.html.haml1
-rw-r--r--config/feature_flags/development/command_palette.yml8
-rw-r--r--db/docs/audit_events_instance_google_cloud_logging_configurations.yml10
-rw-r--r--db/migrate/20230823132142_create_instance_google_cloud_logging_configurations.rb23
-rw-r--r--db/schema_migrations/202308231321421
-rw-r--r--db/structure.sql34
-rw-r--r--doc/api/graphql/reference/index.md31
-rw-r--r--doc/ci/pipelines/merge_request_pipelines.md9
-rw-r--r--doc/user/project/codeowners/index.md8
-rw-r--r--doc/user/project/issues/crosslinking_issues.md7
-rw-r--r--doc/user/project/repository/branches/index.md9
-rw-r--r--doc/user/search/command_palette.md8
-rw-r--r--lib/gitlab/database/load_balancing/service_discovery.rb4
-rw-r--r--lib/gitlab/gon_helper.rb1
-rw-r--r--locale/gitlab.pot56
-rw-r--r--package.json2
-rw-r--r--spec/features/invites_spec.rb4
-rw-r--r--spec/features/users/signup_spec.rb2
-rw-r--r--spec/frontend/super_sidebar/components/global_search/components/global_search_spec.js63
-rw-r--r--spec/graphql/resolvers/work_items_resolver_spec.rb5
-rw-r--r--spec/lib/gitlab/background_migration/remove_project_group_link_with_missing_groups_spec.rb3
-rw-r--r--spec/requests/api/graphql/ci/runner_spec.rb81
-rw-r--r--spec/requests/api/graphql/ci/runners_spec.rb10
-rw-r--r--spec/requests/api/graphql/group/work_items_spec.rb32
-rw-r--r--spec/requests/api/graphql/jobs_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/work_items_spec.rb38
-rw-r--r--spec/services/labels/available_labels_service_spec.rb24
-rw-r--r--spec/services/merge_requests/update_service_spec.rb52
-rw-r--r--spec/support/db_cleaner.rb3
-rw-r--r--spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/requests/api/graphql/work_item_list_shared_examples.rb98
-rw-r--r--spec/support/shared_examples/services/issuable/issuable_update_service_shared_examples.rb71
-rw-r--r--spec/views/devise/shared/_signin_box.html.haml_spec.rb2
-rw-r--r--yarn.lock8
50 files changed, 589 insertions, 284 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 68408a5f640..5780c96e363 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-bda5c66c7a4207b1bfae779f367eba0776873cd5
+f6193a424bcc235e906bc1ae82737b4ed58ec8fd
diff --git a/app/assets/javascripts/jobs/components/job/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job/job_log_controllers.vue
index ffa06892653..78204103feb 100644
--- a/app/assets/javascripts/jobs/components/job/job_log_controllers.vue
+++ b/app/assets/javascripts/jobs/components/job/job_log_controllers.vue
@@ -215,7 +215,7 @@ export default {
:aria-label="$options.i18n.showRawButtonLabel"
:href="rawPath"
data-testid="job-raw-link-controller"
- icon="doc-text"
+ icon="doc-code"
/>
<!-- eo links -->
diff --git a/app/assets/javascripts/jobs/components/table/cells/job_cell.vue b/app/assets/javascripts/jobs/components/table/cells/job_cell.vue
index 27d286fc766..b435eb283fd 100644
--- a/app/assets/javascripts/jobs/components/table/cells/job_cell.vue
+++ b/app/assets/javascripts/jobs/components/table/cells/job_cell.vue
@@ -71,7 +71,7 @@ export default {
<template>
<div>
- <div class="gl-text-truncate gl-mb-2">
+ <div class="gl-text-truncate gl-p-3 gl-mt-n3 gl-mx-n3 gl-mb-n2">
<gl-link
v-if="canReadJob"
class="gl-text-blue-600!"
@@ -96,7 +96,7 @@ export default {
>
<div
v-if="jobRef"
- class="gl-px-2 gl-rounded-base gl-bg-gray-50 gl-max-w-15 gl-text-truncate"
+ class="gl-p-2 gl-rounded-base gl-bg-gray-50 gl-max-w-15 gl-text-truncate"
>
<gl-icon
v-if="createdByTag"
@@ -114,7 +114,7 @@ export default {
</div>
<span v-else>{{ __('none') }}</span>
- <div class="gl-ml-2 gl-rounded-base gl-px-2 gl-bg-gray-50">
+ <div class="gl-ml-2 gl-p-2 gl-rounded-base gl-bg-gray-50">
<gl-icon class="gl-mx-2" name="commit" :size="$options.iconSize" />
<gl-link
class="gl-font-sm gl-font-monospace gl-text-gray-700 gl-hover-text-gray-900"
diff --git a/app/assets/javascripts/jobs/components/table/cells/pipeline_cell.vue b/app/assets/javascripts/jobs/components/table/cells/pipeline_cell.vue
index c8f0fdd4439..18d68ee8a29 100644
--- a/app/assets/javascripts/jobs/components/table/cells/pipeline_cell.vue
+++ b/app/assets/javascripts/jobs/components/table/cells/pipeline_cell.vue
@@ -36,12 +36,16 @@ export default {
<template>
<div>
- <div class="gl-text-truncate">
- <gl-link class="gl-text-gray-500!" :href="pipelinePath" data-testid="pipeline-id">
+ <div class="gl-p-3 gl-mt-n3">
+ <gl-link
+ class="gl-text-truncate gl-ml-n3 gl-text-gray-500!"
+ :href="pipelinePath"
+ data-testid="pipeline-id"
+ >
{{ pipelineId }}
</gl-link>
</div>
- <div class="gl-font-sm gl-text-secondary gl-mt-2">
+ <div class="gl-font-sm gl-text-secondary gl-mt-n2">
<span>{{ __('created by') }}</span>
<gl-link v-if="showAvatar" :href="userPath" data-testid="pipeline-user-link">
<gl-avatar :src="pipelineUserAvatar" :size="16" />
diff --git a/app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue b/app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue
index b64f3ac52b2..4cfc329f8b8 100644
--- a/app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue
+++ b/app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue
@@ -18,14 +18,12 @@ import { sprintf } from '~/locale';
import { ARROW_DOWN_KEY, ARROW_UP_KEY, END_KEY, HOME_KEY, ESC_KEY } from '~/lib/utils/keys';
import {
MIN_SEARCH_TERM,
- SEARCH_GITLAB,
SEARCH_DESCRIBED_BY_WITH_RESULTS,
SEARCH_DESCRIBED_BY_DEFAULT,
SEARCH_DESCRIBED_BY_UPDATED,
SEARCH_RESULTS_LOADING,
SEARCH_RESULTS_SCOPE,
} from '~/vue_shared/global_search/constants';
-import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { darkModeEnabled } from '~/lib/utils/color_utils';
import {
SEARCH_INPUT_DESCRIPTION,
@@ -52,10 +50,10 @@ export default {
name: 'GlobalSearchModal',
SEARCH_MODAL_ID,
i18n: {
- SEARCH_GITLAB,
SEARCH_DESCRIBED_BY_WITH_RESULTS,
SEARCH_DESCRIBED_BY_DEFAULT,
SEARCH_DESCRIBED_BY_UPDATED,
+ SEARCH_OR_COMMAND_MODE_PLACEHOLDER,
SEARCH_RESULTS_LOADING,
SEARCH_RESULTS_SCOPE,
MIN_SEARCH_TERM,
@@ -72,7 +70,6 @@ export default {
CommandPaletteItems,
FakeSearchInput,
},
- mixins: [glFeatureFlagMixin()],
data() {
return {
nextFocusedItemIndex: null,
@@ -89,9 +86,6 @@ export default {
this.setSearch(value);
},
},
- searchPlaceholder() {
- return this.glFeatures?.commandPalette ? SEARCH_OR_COMMAND_MODE_PLACEHOLDER : SEARCH_GITLAB;
- },
showDefaultItems() {
return !this.searchText;
},
@@ -146,9 +140,8 @@ export default {
},
isCommandMode() {
return (
- this.glFeatures?.commandPalette &&
- (COMMON_HANDLES.includes(this.searchTextFirstChar) ||
- (this.searchContext.project && this.searchTextFirstChar === PATH_HANDLE))
+ COMMON_HANDLES.includes(this.searchTextFirstChar) ||
+ (this.searchContext?.project && this.searchTextFirstChar === PATH_HANDLE)
);
},
commandPaletteQuery() {
@@ -294,7 +287,7 @@ export default {
>
<form
role="search"
- :aria-label="searchPlaceholder"
+ :aria-label="$options.i18n.SEARCH_OR_COMMAND_MODE_PLACEHOLDER"
class="gl-relative gl-rounded-base gl-w-full gl-pb-0"
>
<div class="gl-relative gl-bg-white gl-border-b gl-mb-n1 gl-p-3">
@@ -305,7 +298,7 @@ export default {
role="searchbox"
data-testid="global-search-input"
autocomplete="off"
- :placeholder="searchPlaceholder"
+ :placeholder="$options.i18n.SEARCH_OR_COMMAND_MODE_PLACEHOLDER"
:aria-describedby="$options.SEARCH_INPUT_DESCRIPTION"
borderless
@input="getAutocompleteOptions"
diff --git a/app/finders/work_items/namespace_work_items_finder.rb b/app/finders/work_items/namespace_work_items_finder.rb
index aad99d710b6..da6437e0907 100644
--- a/app/finders/work_items/namespace_work_items_finder.rb
+++ b/app/finders/work_items/namespace_work_items_finder.rb
@@ -2,19 +2,14 @@
module WorkItems
class NamespaceWorkItemsFinder < WorkItemsFinder
+ FilterNotAvailableError = Class.new(ArgumentError)
+
def initialize(...)
super
self.parent_param = namespace
end
- def execute
- items = init_collection
- items = by_namespace(items)
-
- sort(items)
- end
-
override :with_confidentiality_access_check
def with_confidentiality_access_check
return model_class.all if params.user_can_see_all_issuables?
@@ -31,6 +26,12 @@ module WorkItems
private
+ def filter_items(items)
+ items = super(items)
+
+ by_namespace(items)
+ end
+
def by_namespace(items)
if namespace.blank? || !Ability.allowed?(current_user, "read_#{namespace.to_ability_name}".to_sym, namespace)
return klass.none
@@ -39,11 +40,23 @@ module WorkItems
items.in_namespaces(namespace)
end
+ override :by_search
+ def by_search(items)
+ return items unless search
+
+ raise FilterNotAvailableError, 'Searching is not available for work items at the namespace level yet'
+ end
+
def namespace
return if params[:namespace_id].blank?
params[:namespace_id].is_a?(Namespace) ? params[:namespace_id] : Namespace.find_by_id(params[:namespace_id])
end
strong_memoize_attr :namespace
+
+ override :by_project
+ def by_project(items)
+ items
+ end
end
end
diff --git a/app/graphql/resolvers/ci/all_jobs_resolver.rb b/app/graphql/resolvers/ci/all_jobs_resolver.rb
index 54dd435d617..85b0f8da877 100644
--- a/app/graphql/resolvers/ci/all_jobs_resolver.rb
+++ b/app/graphql/resolvers/ci/all_jobs_resolver.rb
@@ -40,6 +40,7 @@ params: { scope: statuses, runner_type: runner_types }).execute
play_path: [{ project: { namespace: [:route] } }],
web_path: [{ project: { namespace: [:route] } }],
tags: [:tags],
+ ai_failure_analysis: [{ project: [:project_feature, :namespace] }],
trace: [{ project: [:namespace] }, :job_artifacts_trace]
}
end
diff --git a/app/graphql/resolvers/ci/runner_jobs_resolver.rb b/app/graphql/resolvers/ci/runner_jobs_resolver.rb
index c0a5057cddd..b005702a71d 100644
--- a/app/graphql/resolvers/ci/runner_jobs_resolver.rb
+++ b/app/graphql/resolvers/ci/runner_jobs_resolver.rb
@@ -45,6 +45,7 @@ module Resolvers
web_path: [{ project: { namespace: [:route] } }],
short_sha: [:pipeline],
tags: [:tags],
+ ai_failure_analysis: [{ project: [:project_feature, :namespace] }],
trace: [{ project: [:namespace] }, :job_artifacts_trace]
}
end
diff --git a/app/graphql/resolvers/namespaces/work_items_resolver.rb b/app/graphql/resolvers/namespaces/work_items_resolver.rb
index 54bb8392071..6985a7a898a 100644
--- a/app/graphql/resolvers/namespaces/work_items_resolver.rb
+++ b/app/graphql/resolvers/namespaces/work_items_resolver.rb
@@ -2,33 +2,31 @@
module Resolvers
module Namespaces
- class WorkItemsResolver < BaseResolver
- prepend ::WorkItems::LookAheadPreloads
+ # rubocop:disable Graphql/ResolverType (inherited from Resolvers::WorkItemsResolver)
+ class WorkItemsResolver < ::Resolvers::WorkItemsResolver
+ def ready?(**args)
+ return false if Feature.disabled?(:namespace_level_work_items, resource_parent)
- type Types::WorkItemType.connection_type, null: true
-
- def resolve_with_lookahead(**args)
- return unless Feature.enabled?(:namespace_level_work_items, resource_parent)
- return WorkItem.none if resource_parent.nil?
-
- finder = ::WorkItems::NamespaceWorkItemsFinder.new(current_user, args.merge(
- namespace_id: resource_parent
- ))
+ super
+ end
- Gitlab::Graphql::Loaders::IssuableLoader.new(resource_parent, finder).batching_find_all do |q|
- apply_lookahead(q)
- end
+ override :resolve_with_lookahead
+ def resolve_with_lookahead(...)
+ super
+ rescue ::WorkItems::NamespaceWorkItemsFinder::FilterNotAvailableError => e
+ raise Gitlab::Graphql::Errors::ArgumentError, e.message
end
private
- def resource_parent
- # The project could have been loaded in batch by `BatchLoader`.
- # At this point we need the `id` of the project to query for work items, so
- # make sure it's loaded and not `nil` before continuing.
- object.respond_to?(:sync) ? object.sync : object
+ override :finder
+ def finder(args)
+ ::WorkItems::NamespaceWorkItemsFinder.new(
+ current_user,
+ args.merge(namespace_id: resource_parent)
+ )
end
- strong_memoize_attr :resource_parent
end
+ # rubocop:enable Graphql/ResolverType
end
end
diff --git a/app/graphql/resolvers/work_items_resolver.rb b/app/graphql/resolvers/work_items_resolver.rb
index d4f73361e05..995f54f35d8 100644
--- a/app/graphql/resolvers/work_items_resolver.rb
+++ b/app/graphql/resolvers/work_items_resolver.rb
@@ -21,13 +21,18 @@ module Resolvers
def resolve_with_lookahead(**args)
return WorkItem.none if resource_parent.nil?
- finder = ::WorkItems::WorkItemsFinder.new(current_user, prepare_finder_params(args))
-
- Gitlab::Graphql::Loaders::IssuableLoader.new(resource_parent, finder).batching_find_all { |q| apply_lookahead(q) }
+ Gitlab::Graphql::Loaders::IssuableLoader.new(
+ resource_parent,
+ finder(prepare_finder_params(args))
+ ).batching_find_all { |q| apply_lookahead(q) }
end
private
+ def finder(args)
+ ::WorkItems::WorkItemsFinder.new(current_user, args)
+ end
+
def prepare_finder_params(args)
params = super(args)
params[:iids] ||= [params.delete(:iid)].compact if params[:iid]
diff --git a/app/models/users/callout.rb b/app/models/users/callout.rb
index cc7f9cd2a57..17dbf8acbc3 100644
--- a/app/models/users/callout.rb
+++ b/app/models/users/callout.rb
@@ -73,7 +73,8 @@ module Users
new_navigation_callout: 71,
# 72 removed in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129022
namespace_over_storage_users_combined_alert: 73, # EE-only
- rich_text_editor: 74
+ rich_text_editor: 74,
+ vsd_feedback_banner: 75 # EE-only
}
validates :feature_name,
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index f546bd5951a..27cfaef2db2 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -180,7 +180,18 @@ class IssuableBaseService < ::BaseContainerService
new_label_ids |= add_label_ids if add_label_ids
new_label_ids -= remove_label_ids if remove_label_ids
- new_label_ids.uniq
+ filter_locked_labels(issuable, new_label_ids.uniq, existing_label_ids)
+ end
+
+ # Filter out any locked labels that are attempting to be removed
+ def filter_locked_labels(issuable, ids, existing_label_ids)
+ return ids unless issuable.supports_lock_on_merge?
+ return ids unless existing_label_ids.present?
+
+ removed_label_ids = existing_label_ids - ids
+ removed_locked_label_ids = labels_service.filter_locked_label_ids(removed_label_ids)
+
+ ids + removed_locked_label_ids
end
def process_assignee_ids(attributes, existing_assignee_ids: nil, extra_assignee_ids: [])
diff --git a/app/services/labels/available_labels_service.rb b/app/services/labels/available_labels_service.rb
index 21f92eeaf09..7809f263eb6 100644
--- a/app/services/labels/available_labels_service.rb
+++ b/app/services/labels/available_labels_service.rb
@@ -40,19 +40,8 @@ module Labels
ids.map(&:to_i) & existing_ids
end
- def filter_locked_labels_ids_in_param(key)
- ids = Array.wrap(params[key])
- return [] if ids.empty?
-
- params = finder_params
- params[:locked_labels] = true
- existing_labels = LabelsFinder.new(current_user, params).execute
-
- # rubocop:disable CodeReuse/ActiveRecord
- existing_ids = existing_labels.id_in(ids).pluck(:id)
- # rubocop:enable CodeReuse/ActiveRecord
-
- ids.map(&:to_i) & existing_ids
+ def filter_locked_label_ids(ids)
+ available_labels.with_lock_on_merge.id_in(ids).pluck(:id) # rubocop:disable CodeReuse/ActiveRecord
end
def available_labels
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index b1894f7fded..8a2ed4d08e6 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -176,20 +176,10 @@ module MergeRequests
params.delete(:allow_collaboration)
end
- filter_locked_labels(merge_request)
filter_reviewer(merge_request)
filter_suggested_reviewers
end
- # Filter out any locked labels that are requested to be removed.
- # Only supported for merged MRs.
- def filter_locked_labels(merge_request)
- return unless params[:remove_label_ids].present?
- return unless merge_request.supports_lock_on_merge?
-
- params[:remove_label_ids] -= labels_service.filter_locked_labels_ids_in_param(:remove_label_ids)
- end
-
def filter_reviewer(merge_request)
return if params[:reviewer_ids].blank?
diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml
index 98bd9ad93ef..215fbc3b262 100644
--- a/app/views/admin/application_settings/_account_and_limit.html.haml
+++ b/app/views/admin/application_settings/_account_and_limit.html.haml
@@ -25,7 +25,7 @@
.form-group
= f.label :max_import_size, _('Maximum import size (MiB)'), class: 'label-light'
= f.number_field :max_import_size, class: 'form-control gl-form-input', title: _('Maximum size of import files.'), data: { toggle: 'tooltip', container: 'body' }
- %span.form-text.text-muted= _('Only effective when remote storage is enabled. Set to 0 for no size limit.')
+ %span.form-text.text-muted= _('Set to 0 for no size limit.')
.form-group
= f.label :max_import_remote_file_size, s_('Import|Maximum import remote file size (MB)'), class: 'label-light'
= f.number_field :max_import_remote_file_size, class: 'form-control gl-form-input', title: s_('Import|Maximum remote file size for imports from external object storages. For example, AWS S3.'), data: { toggle: 'tooltip', container: 'body' }
diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml
index 345a1cc0225..e6551adffde 100644
--- a/app/views/devise/sessions/_new_base.html.haml
+++ b/app/views/devise/sessions/_new_base.html.haml
@@ -1,6 +1,6 @@
= gitlab_ui_form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'gl-p-5 gl-show-field-errors js-arkose-labs-form', aria: { live: 'assertive' }, data: { testid: 'sign-in-form' }}) do |f|
.form-group
- = f.label :login, _('Username or email')
+ = f.label :login, _('Username or primary email')
= f.text_field :login, value: @invite_email, class: 'form-control gl-form-input js-username-field', autocomplete: 'username', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off', required: true, title: _('This field is required.'), data: { qa_selector: 'login_field', testid: 'username-field' }
.form-group
= f.label :password, _('Password')
diff --git a/app/views/projects/settings/merge_requests/show.html.haml b/app/views/projects/settings/merge_requests/show.html.haml
index f9f26d04e53..01af028f30c 100644
--- a/app/views/projects/settings/merge_requests/show.html.haml
+++ b/app/views/projects/settings/merge_requests/show.html.haml
@@ -17,3 +17,4 @@
= render_if_exists 'projects/settings/merge_requests/merge_request_approvals_settings', expanded: true
= render_if_exists 'projects/settings/merge_requests/suggested_reviewers_settings', expanded: true
+= render_if_exists 'projects/settings/merge_requests/target_branch_rules_settings'
diff --git a/config/feature_flags/development/command_palette.yml b/config/feature_flags/development/command_palette.yml
deleted file mode 100644
index 3a7935e6bf5..00000000000
--- a/config/feature_flags/development/command_palette.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: command_palette
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121932
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/413060
-milestone: '16.1'
-type: development
-group: group::foundations
-default_enabled: true
diff --git a/db/docs/audit_events_instance_google_cloud_logging_configurations.yml b/db/docs/audit_events_instance_google_cloud_logging_configurations.yml
new file mode 100644
index 00000000000..059ab59d860
--- /dev/null
+++ b/db/docs/audit_events_instance_google_cloud_logging_configurations.yml
@@ -0,0 +1,10 @@
+---
+table_name: audit_events_instance_google_cloud_logging_configurations
+classes:
+ - AuditEvents::Instance::GoogleCloudLoggingConfiguration
+feature_categories:
+ - audit_events
+description: Stores Instance level Google Cloud Logging configurations associated with IAM service accounts, used for generating access tokens.
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/423036
+milestone: '16.4'
+gitlab_schema: gitlab_main
diff --git a/db/migrate/20230823132142_create_instance_google_cloud_logging_configurations.rb b/db/migrate/20230823132142_create_instance_google_cloud_logging_configurations.rb
new file mode 100644
index 00000000000..c659264038d
--- /dev/null
+++ b/db/migrate/20230823132142_create_instance_google_cloud_logging_configurations.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class CreateInstanceGoogleCloudLoggingConfigurations < Gitlab::Database::Migration[2.1]
+ enable_lock_retries!
+
+ UNIQUE_INDEX_NAME = "unique_instance_google_cloud_logging_configurations"
+ UNIQUE_CONFIG_NAME_INDEX = "unique_instance_google_cloud_logging_configurations_name"
+
+ def change
+ create_table :audit_events_instance_google_cloud_logging_configurations do |t|
+ t.timestamps_with_timezone null: false
+ t.text :google_project_id_name, null: false, limit: 30
+ t.text :client_email, null: false, limit: 254
+ t.text :log_id_name, default: "audit_events", limit: 511
+ t.text :name, null: false, limit: 72
+ t.binary :encrypted_private_key, null: false
+ t.binary :encrypted_private_key_iv, null: false
+
+ t.index [:google_project_id_name, :log_id_name], unique: true, name: UNIQUE_INDEX_NAME
+ t.index :name, unique: true, name: UNIQUE_CONFIG_NAME_INDEX
+ end
+ end
+end
diff --git a/db/schema_migrations/20230823132142 b/db/schema_migrations/20230823132142
new file mode 100644
index 00000000000..2ea42285c0e
--- /dev/null
+++ b/db/schema_migrations/20230823132142
@@ -0,0 +1 @@
+438ce9b705e575c139724a2ad81e799bdda63b9428b4cf3bd21b9e47df36ece7 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index c68fc40f247..0ab32dec106 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -12379,6 +12379,31 @@ CREATE SEQUENCE audit_events_instance_external_audit_event_destinations_id_seq
ALTER SEQUENCE audit_events_instance_external_audit_event_destinations_id_seq OWNED BY audit_events_instance_external_audit_event_destinations.id;
+CREATE TABLE audit_events_instance_google_cloud_logging_configurations (
+ id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ google_project_id_name text NOT NULL,
+ client_email text NOT NULL,
+ log_id_name text DEFAULT 'audit_events'::text,
+ name text NOT NULL,
+ encrypted_private_key bytea NOT NULL,
+ encrypted_private_key_iv bytea NOT NULL,
+ CONSTRAINT check_0da5c76c49 CHECK ((char_length(client_email) <= 254)),
+ CONSTRAINT check_74fd943192 CHECK ((char_length(log_id_name) <= 511)),
+ CONSTRAINT check_ab65f57721 CHECK ((char_length(google_project_id_name) <= 30)),
+ CONSTRAINT check_ac42ad3ca2 CHECK ((char_length(name) <= 72))
+);
+
+CREATE SEQUENCE audit_events_instance_google_cloud_logging_configuration_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE audit_events_instance_google_cloud_logging_configuration_id_seq OWNED BY audit_events_instance_google_cloud_logging_configurations.id;
+
CREATE TABLE audit_events_streaming_event_type_filters (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
@@ -25422,6 +25447,8 @@ ALTER TABLE ONLY audit_events_google_cloud_logging_configurations ALTER COLUMN i
ALTER TABLE ONLY audit_events_instance_external_audit_event_destinations ALTER COLUMN id SET DEFAULT nextval('audit_events_instance_external_audit_event_destinations_id_seq'::regclass);
+ALTER TABLE ONLY audit_events_instance_google_cloud_logging_configurations ALTER COLUMN id SET DEFAULT nextval('audit_events_instance_google_cloud_logging_configuration_id_seq'::regclass);
+
ALTER TABLE ONLY audit_events_streaming_event_type_filters ALTER COLUMN id SET DEFAULT nextval('audit_events_streaming_event_type_filters_id_seq'::regclass);
ALTER TABLE ONLY audit_events_streaming_headers ALTER COLUMN id SET DEFAULT nextval('audit_events_streaming_headers_id_seq'::regclass);
@@ -27215,6 +27242,9 @@ ALTER TABLE ONLY audit_events_google_cloud_logging_configurations
ALTER TABLE ONLY audit_events_instance_external_audit_event_destinations
ADD CONSTRAINT audit_events_instance_external_audit_event_destinations_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY audit_events_instance_google_cloud_logging_configurations
+ ADD CONSTRAINT audit_events_instance_google_cloud_logging_configurations_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY audit_events
ADD CONSTRAINT audit_events_pkey PRIMARY KEY (id, created_at);
@@ -34328,6 +34358,10 @@ CREATE UNIQUE INDEX unique_index_sysaccess_ms_access_tokens_on_sysaccess_ms_app_
CREATE UNIQUE INDEX unique_instance_audit_event_destination_name ON audit_events_instance_external_audit_event_destinations USING btree (name);
+CREATE UNIQUE INDEX unique_instance_google_cloud_logging_configurations ON audit_events_instance_google_cloud_logging_configurations USING btree (google_project_id_name, log_id_name);
+
+CREATE UNIQUE INDEX unique_instance_google_cloud_logging_configurations_name ON audit_events_instance_google_cloud_logging_configurations USING btree (name);
+
CREATE UNIQUE INDEX unique_merge_request_diff_llm_summaries_on_mr_diff_id ON merge_request_diff_llm_summaries USING btree (merge_request_diff_id);
CREATE UNIQUE INDEX unique_merge_request_metrics_by_merge_request_id ON merge_request_metrics USING btree (merge_request_id);
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index f307792901b..ffeeae09611 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -16867,7 +16867,6 @@ GPG signature for a signed commit.
| <a id="groupvisibility"></a>`visibility` | [`String`](#string) | Visibility of the namespace. |
| <a id="groupvulnerabilityscanners"></a>`vulnerabilityScanners` | [`VulnerabilityScannerConnection`](#vulnerabilityscannerconnection) | Vulnerability scanners reported on the project vulnerabilities of the group and its subgroups. (see [Connections](#connections)) |
| <a id="groupweburl"></a>`webUrl` | [`String!`](#string) | Web URL of the group. |
-| <a id="groupworkitems"></a>`workItems` **{warning-solid}** | [`WorkItemConnection`](#workitemconnection) | **Introduced** in 16.3. This feature is an Experiment. It can be changed or removed at any time. Work items that belong to the namespace. |
#### Fields with arguments
@@ -17760,6 +17759,35 @@ four standard [pagination arguments](#connection-pagination-arguments):
| ---- | ---- | ----------- |
| <a id="groupworkitemtypestaskable"></a>`taskable` | [`Boolean`](#boolean) | If `true`, only taskable work item types will be returned. Argument is experimental and can be removed in the future without notice. |
+##### `Group.workItems`
+
+Work items that belong to the namespace.
+
+WARNING:
+**Introduced** in 16.3.
+This feature is an Experiment. It can be changed or removed at any time.
+
+Returns [`WorkItemConnection`](#workitemconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#connection-pagination-arguments):
+`before: String`, `after: String`, `first: Int`, `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="groupworkitemsauthorusername"></a>`authorUsername` **{warning-solid}** | [`String`](#string) | **Introduced** in 15.9. This feature is an Experiment. It can be changed or removed at any time. Filter work items by author username. |
+| <a id="groupworkitemsiid"></a>`iid` | [`String`](#string) | IID of the work item. For example, "1". |
+| <a id="groupworkitemsiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of work items. For example, `["1", "2"]`. |
+| <a id="groupworkitemsin"></a>`in` | [`[IssuableSearchableField!]`](#issuablesearchablefield) | Specify the fields to perform the search in. Defaults to `[TITLE, DESCRIPTION]`. Requires the `search` argument.'. |
+| <a id="groupworkitemsrequirementlegacywidget"></a>`requirementLegacyWidget` **{warning-solid}** | [`RequirementLegacyFilterInput`](#requirementlegacyfilterinput) | **Deprecated** in 15.9. Use work item IID filter instead. |
+| <a id="groupworkitemssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
+| <a id="groupworkitemssort"></a>`sort` | [`WorkItemSort`](#workitemsort) | Sort work items by criteria. |
+| <a id="groupworkitemsstate"></a>`state` | [`IssuableState`](#issuablestate) | Current state of the work item. |
+| <a id="groupworkitemsstatuswidget"></a>`statusWidget` | [`StatusFilterInput`](#statusfilterinput) | Input for status widget filter. Ignored if `work_items_mvc_2` is disabled. |
+| <a id="groupworkitemstypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter work items by the given work item types. |
+
### `GroupDataTransfer`
#### Fields
@@ -28195,6 +28223,7 @@ Name of the feature that the callout is for.
| <a id="usercalloutfeaturenameenumunfinished_tag_cleanup_callout"></a>`UNFINISHED_TAG_CLEANUP_CALLOUT` | Callout feature name for unfinished_tag_cleanup_callout. |
| <a id="usercalloutfeaturenameenumuser_reached_limit_free_plan_alert"></a>`USER_REACHED_LIMIT_FREE_PLAN_ALERT` | Callout feature name for user_reached_limit_free_plan_alert. |
| <a id="usercalloutfeaturenameenumverification_reminder"></a>`VERIFICATION_REMINDER` | Callout feature name for verification_reminder. |
+| <a id="usercalloutfeaturenameenumvsd_feedback_banner"></a>`VSD_FEEDBACK_BANNER` | Callout feature name for vsd_feedback_banner. |
| <a id="usercalloutfeaturenameenumweb_ide_alert_dismissed"></a>`WEB_IDE_ALERT_DISMISSED` | Callout feature name for web_ide_alert_dismissed. |
| <a id="usercalloutfeaturenameenumweb_ide_ci_environments_guidance"></a>`WEB_IDE_CI_ENVIRONMENTS_GUIDANCE` | Callout feature name for web_ide_ci_environments_guidance. |
diff --git a/doc/ci/pipelines/merge_request_pipelines.md b/doc/ci/pipelines/merge_request_pipelines.md
index 356b97aacc0..7b8a2a16734 100644
--- a/doc/ci/pipelines/merge_request_pipelines.md
+++ b/doc/ci/pipelines/merge_request_pipelines.md
@@ -169,8 +169,13 @@ To use the UI to run a pipeline in the parent project for a merge request from a
1. Select **Run pipeline**. You must read and accept the warning, or the pipeline does not run.
You can disable this feature by using [the projects API](../../api/projects.md#edit-project)
-to disable the `ci_allow_fork_pipelines_to_run_in_parent_project` setting.
-The setting is `enabled` by default.
+to disable the `ci_allow_fork_pipelines_to_run_in_parent_project` setting (enabled by default).
+When you disable this setting, new pipelines from forks in the parent project are prevented.
+
+WARNING:
+Older pipelines created before the setting was disabled are not affected and continue to run.
+If you rerun a job in an older pipeline, the job uses the same context as when the
+pipeline was originally created.
## Available predefined variables
diff --git a/doc/user/project/codeowners/index.md b/doc/user/project/codeowners/index.md
index 8a450fdb64f..c77efaab2ea 100644
--- a/doc/user/project/codeowners/index.md
+++ b/doc/user/project/codeowners/index.md
@@ -77,6 +77,14 @@ all others are ignored:
For more information, see [Code Owners syntax and error handling](reference.md).
+#### When user or group change names
+
+When a user or group change their names, the `CODEOWNERS` isn't automatically updated with the new names.
+To enter the new names, you must edit the file.
+
+Organizations using SAML SSO can [set usernames](../../../integration/saml.md#set-a-username) to
+prevent users from being able to change their usernames.
+
### Add a group as a Code Owner
To set the members of a group or subgroup as a Code Owner:
diff --git a/doc/user/project/issues/crosslinking_issues.md b/doc/user/project/issues/crosslinking_issues.md
index b3fe7da4280..882e4d97938 100644
--- a/doc/user/project/issues/crosslinking_issues.md
+++ b/doc/user/project/issues/crosslinking_issues.md
@@ -76,3 +76,10 @@ you can also [set an issue to close automatically](managing_issues.md#closing-is
as soon as the merge request is merged.
![issue mentioned in MR](img/mention_in_merge_request.png)
+
+## From branch names
+
+When you create a branch in the same project as an issue and start the branch name with the issue
+number, followed by a hyphen, the issue and MR you create are linked.
+For more information, see
+[Prefix branch names with issue numbers](../repository/branches/index.md#prefix-branch-names-with-issue-numbers).
diff --git a/doc/user/project/repository/branches/index.md b/doc/user/project/repository/branches/index.md
index 3d62d6614c7..f33d443cd7f 100644
--- a/doc/user/project/repository/branches/index.md
+++ b/doc/user/project/repository/branches/index.md
@@ -219,8 +219,13 @@ To change the default pattern for branches created from issues:
### Prefix branch names with issue numbers
-To streamline the creation of merge requests, start your branch name with an
-issue number. GitLab uses the issue number to import data into the merge request:
+To streamline the creation of merge requests, start your Git branch name with the
+issue number, followed by a hyphen.
+For example, to link a branch to issue `#123`, start the branch name with `123-`.
+
+The issue and the branch must be in the same project.
+
+GitLab uses the issue number to import data into the merge request:
- The issue is marked as related to the merge request. The issue and merge request
display links to each other.
diff --git a/doc/user/search/command_palette.md b/doc/user/search/command_palette.md
index 8876b61e4d2..0f42e727eda 100644
--- a/doc/user/search/command_palette.md
+++ b/doc/user/search/command_palette.md
@@ -7,16 +7,12 @@ type: reference
# Command palette **(FREE ALL)**
-> Introduced in GitLab 16.2 [with a flag](../../administration/feature_flags.md) named `command_palette`. Enabled by default.
+> - Introduced in GitLab 16.2 [with a flag](../../administration/feature_flags.md) named `command_palette`. Enabled by default.
+> - Feature flag `command_palette` removed in GitLab 16.4.
You can use command palette to narrow down the scope of your search or to
find an object more quickly.
-FLAG:
-On self-managed GitLab, by default this feature is available.
-To hide the feature, an administrator can [disable the feature flag](../../administration/feature_flags.md) named `command_palette`.
-On GitLab.com, this feature is available.
-
## Open the command palette
To open the command palette:
diff --git a/lib/gitlab/database/load_balancing/service_discovery.rb b/lib/gitlab/database/load_balancing/service_discovery.rb
index 61e44458613..bc38bbcbb51 100644
--- a/lib/gitlab/database/load_balancing/service_discovery.rb
+++ b/lib/gitlab/database/load_balancing/service_discovery.rb
@@ -118,7 +118,7 @@ module Gitlab
# The return value is the amount of time (in seconds) to wait before
# checking the DNS record for any changes.
def refresh_if_necessary
- interval, from_dns = addresses_from_dns
+ wait_time, from_dns = addresses_from_dns
current = addresses_from_load_balancer
@@ -132,7 +132,7 @@ module Gitlab
replace_hosts(from_dns)
end
- interval
+ wait_time
end
# Replaces all the hosts in the load balancer with the new ones,
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 2b24d1e5716..4e6e18825f5 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -74,7 +74,6 @@ module Gitlab
push_frontend_feature_flag(:source_editor_toolbar)
push_frontend_feature_flag(:vscode_web_ide, current_user)
push_frontend_feature_flag(:unbatch_graphql_queries, current_user)
- push_frontend_feature_flag(:command_palette, current_user)
# To be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/399248
push_frontend_feature_flag(:remove_monitor_metrics)
push_frontend_feature_flag(:gitlab_duo, current_user)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index e23b2dfb135..317d93371bb 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2968,6 +2968,9 @@ msgstr ""
msgid "Add tag"
msgstr ""
+msgid "Add target branch rule"
+msgstr ""
+
msgid "Add text to the sign-in page. Markdown enabled."
msgstr ""
@@ -13799,6 +13802,9 @@ msgstr ""
msgid "Create requirement"
msgstr ""
+msgid "Create rules for target branches in merge requests."
+msgstr ""
+
msgid "Create service account"
msgstr ""
@@ -14783,6 +14789,9 @@ msgstr ""
msgid "DORA4Metrics|To help us improve the Show forecast feature, please share feedback about your experience in %{linkStart}this issue%{linkEnd}."
msgstr ""
+msgid "DORA4Metrics|To help us improve the Value Stream Management Dashboard, please share feedback about your experience in this %{linkStart}survey%{linkEnd}."
+msgstr ""
+
msgid "DORA4Metrics|Took 1 day or less to restore service when a service incident or a defect that impacts users occurs."
msgstr ""
@@ -32831,9 +32840,6 @@ msgstr ""
msgid "Only active projects show up in the search and on the dashboard."
msgstr ""
-msgid "Only effective when remote storage is enabled. Set to 0 for no size limit."
-msgstr ""
-
msgid "Only include features new to your current subscription tier."
msgstr ""
@@ -40286,6 +40292,9 @@ msgstr ""
msgid "Ruby"
msgstr ""
+msgid "Rule name"
+msgstr ""
+
msgid "Rule name is already taken."
msgstr ""
@@ -41651,9 +41660,18 @@ msgstr ""
msgid "ScanResultPolicy|When %{scanType} in an open merge request targeting %{branches} %{branchExceptions} and the licenses match all of the following criteria:"
msgstr ""
+msgid "ScanResultPolicy|When %{scanType} in an open that targets %{branches} with %{commitType}"
+msgstr ""
+
msgid "ScanResultPolicy|When %{scanners} find scanner specified conditions in an open merge request targeting the %{branches} %{branchExceptions} and match %{boldDescription} of the following criteria"
msgstr ""
+msgid "ScanResultPolicy|any commits"
+msgstr ""
+
+msgid "ScanResultPolicy|any unsigned commits"
+msgstr ""
+
msgid "ScanResultPolicy|license status"
msgstr ""
@@ -42297,6 +42315,9 @@ msgstr ""
msgid "SecurityOrchestration|And scans to be performed:"
msgstr ""
+msgid "SecurityOrchestration|Any merge request"
+msgstr ""
+
msgid "SecurityOrchestration|Are you sure you want to delete this policy? This action cannot be undone."
msgstr ""
@@ -46662,6 +46683,9 @@ msgstr ""
msgid "TanukiBot|What is a fork?"
msgstr ""
+msgid "Targe branch"
+msgstr ""
+
msgid "Target"
msgstr ""
@@ -46674,6 +46698,12 @@ msgstr ""
msgid "Target branch"
msgstr ""
+msgid "Target branch rule"
+msgstr ""
+
+msgid "Target branch rules"
+msgstr ""
+
msgid "Target branch: %{target_branch}"
msgstr ""
@@ -47678,6 +47708,9 @@ msgstr ""
msgid "There are currently no mirrored repositories."
msgstr ""
+msgid "There are currently no target branch rules"
+msgstr ""
+
msgid "There are merge conflicts"
msgstr ""
@@ -51542,6 +51575,9 @@ msgstr ""
msgid "Username or email"
msgstr ""
+msgid "Username or primary email"
+msgstr ""
+
msgid "Username:"
msgstr ""
@@ -53795,7 +53831,7 @@ msgstr ""
msgid "Workspaces"
msgstr ""
-msgid "Workspaces|A workspace is a virtual sandbox environment for your code in GitLab. You can create a workspace for a public project."
+msgid "Workspaces|A workspace is a virtual sandbox environment for your code in GitLab."
msgstr ""
msgid "Workspaces|Cancel"
@@ -53894,9 +53930,6 @@ msgstr ""
msgid "Workspaces|Workspaces"
msgstr ""
-msgid "Workspaces|You can create a workspace for public projects only."
-msgstr ""
-
msgid "Workspaces|You can't create a workspace for this project"
msgstr ""
@@ -55881,6 +55914,9 @@ msgstr ""
msgid "eg party_tanuki"
msgstr ""
+msgid "eg. dev/*"
+msgstr ""
+
msgid "element is not a hierarchy"
msgstr ""
@@ -55967,9 +56003,6 @@ msgstr[1] ""
msgid "finding is not found or is already attached to a vulnerability"
msgstr ""
-msgid "for Workspace is required to be public"
-msgstr ""
-
msgid "for Workspace must have an associated RemoteDevelopmentAgentConfig"
msgstr ""
@@ -56753,6 +56786,9 @@ msgstr ""
msgid "must not contain commonly used combinations of words and letters"
msgstr ""
+msgid "must only contain letters, digits, forward-slash, underscore, hyphen or period"
+msgstr ""
+
msgid "my-awesome-group"
msgstr ""
diff --git a/package.json b/package.json
index 45244dc324a..b636f313db2 100644
--- a/package.json
+++ b/package.json
@@ -147,7 +147,7 @@
"gettext-parser": "^6.0.0",
"graphql": "^15.7.2",
"graphql-tag": "^2.11.0",
- "gridstack": "^8.4.0",
+ "gridstack": "^9.1.0",
"highlight.js": "^11.8.0",
"immer": "^9.0.15",
"ipaddr.js": "^1.9.1",
diff --git a/spec/features/invites_spec.rb b/spec/features/invites_spec.rb
index b3bd358170d..785a34e0b9b 100644
--- a/spec/features/invites_spec.rb
+++ b/spec/features/invites_spec.rb
@@ -64,10 +64,10 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures, feature_cate
expect(page).to have_content('To accept this invitation, create an account or sign in')
end
- it 'pre-fills the "Username or email" field on the sign in box with the invite_email from the invite' do
+ it 'pre-fills the "Username or primary email" field on the sign in box with the invite_email from the invite' do
click_link 'Sign in'
- expect(find_field('Username or email').value).to eq(group_invite.invite_email)
+ expect(find_field('Username or primary email').value).to eq(group_invite.invite_email)
end
it 'pre-fills the Email field on the sign up box with the invite_email from the invite' do
diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb
index d6b6806d63d..111c0cce1b1 100644
--- a/spec/features/users/signup_spec.rb
+++ b/spec/features/users/signup_spec.rb
@@ -234,7 +234,7 @@ RSpec.describe 'Signup', :js, feature_category: :user_profile do
confirm_email
- expect(find_field('Username or email').value).to eq(new_user.email)
+ expect(find_field('Username or primary email').value).to eq(new_user.email)
end
end
diff --git a/spec/frontend/super_sidebar/components/global_search/components/global_search_spec.js b/spec/frontend/super_sidebar/components/global_search/components/global_search_spec.js
index f9a6690a391..038c7a96adc 100644
--- a/spec/frontend/super_sidebar/components/global_search/components/global_search_spec.js
+++ b/spec/frontend/super_sidebar/components/global_search/components/global_search_spec.js
@@ -23,7 +23,6 @@ import {
ICON_SUBGROUP,
SCOPE_TOKEN_MAX_LENGTH,
} from '~/super_sidebar/components/global_search/constants';
-import { SEARCH_GITLAB } from '~/vue_shared/global_search/constants';
import { truncate } from '~/lib/utils/text_utility';
import { visitUrl } from '~/lib/utils/url_utility';
import { ENTER_KEY } from '~/lib/utils/keys';
@@ -52,7 +51,7 @@ describe('GlobalSearchModal', () => {
clearAutocomplete: jest.fn(),
};
- const deafaultMockState = {
+ const defaultMockState = {
searchContext: {
project: MOCK_PROJECT,
group: MOCK_GROUP,
@@ -66,15 +65,14 @@ describe('GlobalSearchModal', () => {
};
const createComponent = ({
- initialState = deafaultMockState,
+ initialState = defaultMockState,
mockGetters = defaultMockGetters,
stubs,
- glFeatures = { commandPalette: false },
...mountOptions
} = {}) => {
const store = new Vuex.Store({
state: {
- ...deafaultMockState,
+ ...defaultMockState,
...initialState,
},
actions: actionSpies,
@@ -89,7 +87,6 @@ describe('GlobalSearchModal', () => {
wrapper = shallowMountExtended(GlobalSearchModal, {
store,
stubs,
- provide: { glFeatures },
...mountOptions,
});
};
@@ -271,49 +268,28 @@ describe('GlobalSearchModal', () => {
});
describe('Command palette', () => {
- describe('when FF `command_palette` is disabled', () => {
+ describe.each([...COMMON_HANDLES, PATH_HANDLE])('when search handle is %s', (handle) => {
beforeEach(() => {
- createComponent();
+ createComponent({
+ initialState: { search: handle },
+ });
});
- it('should not render command mode components', () => {
- expect(findCommandPaletteItems().exists()).toBe(false);
- expect(findFakeSearchInput().exists()).toBe(false);
+ it('should render command mode components', () => {
+ expect(findCommandPaletteItems().exists()).toBe(true);
+ expect(findFakeSearchInput().exists()).toBe(true);
});
- it('should provide default placeholder to the search input', () => {
- expect(findGlobalSearchInput().attributes('placeholder')).toBe(SEARCH_GITLAB);
+ it('should provide an alternative placeholder to the search input', () => {
+ expect(findGlobalSearchInput().attributes('placeholder')).toBe(
+ SEARCH_OR_COMMAND_MODE_PLACEHOLDER,
+ );
});
- });
-
- describe.each([...COMMON_HANDLES, PATH_HANDLE])(
- 'when FF `command_palette` is enabled and search handle is %s',
- (handle) => {
- beforeEach(() => {
- createComponent({
- initialState: { search: handle },
- glFeatures: {
- commandPalette: true,
- },
- });
- });
- it('should render command mode components', () => {
- expect(findCommandPaletteItems().exists()).toBe(true);
- expect(findFakeSearchInput().exists()).toBe(true);
- });
-
- it('should provide an alternative placeholder to the search input', () => {
- expect(findGlobalSearchInput().attributes('placeholder')).toBe(
- SEARCH_OR_COMMAND_MODE_PLACEHOLDER,
- );
- });
-
- it('should not render the scope token', () => {
- expect(findScopeToken().exists()).toBe(false);
- });
- },
- );
+ it('should not render the scope token', () => {
+ expect(findScopeToken().exists()).toBe(false);
+ });
+ });
});
});
@@ -373,9 +349,6 @@ describe('GlobalSearchModal', () => {
beforeEach(() => {
createComponent({
initialState: { search: '>' },
- glFeatures: {
- commandPalette: true,
- },
});
submitSearch();
});
diff --git a/spec/graphql/resolvers/work_items_resolver_spec.rb b/spec/graphql/resolvers/work_items_resolver_spec.rb
index 6da62e3adb7..c856f990e7a 100644
--- a/spec/graphql/resolvers/work_items_resolver_spec.rb
+++ b/spec/graphql/resolvers/work_items_resolver_spec.rb
@@ -46,11 +46,6 @@ RSpec.describe Resolvers::WorkItemsResolver do
expect(resolve_items).to contain_exactly(item1, item2)
end
- it 'filters by state' do
- expect(resolve_items(state: 'opened')).to contain_exactly(item1)
- expect(resolve_items(state: 'closed')).to contain_exactly(item2)
- end
-
context 'when searching items' do
it_behaves_like 'graphql query for searching issuables' do
let_it_be(:parent) { project }
diff --git a/spec/lib/gitlab/background_migration/remove_project_group_link_with_missing_groups_spec.rb b/spec/lib/gitlab/background_migration/remove_project_group_link_with_missing_groups_spec.rb
index 64fba6e89a8..126e928fa77 100644
--- a/spec/lib/gitlab/background_migration/remove_project_group_link_with_missing_groups_spec.rb
+++ b/spec/lib/gitlab/background_migration/remove_project_group_link_with_missing_groups_spec.rb
@@ -104,8 +104,7 @@ RSpec.describe Gitlab::BackgroundMigration::RemoveProjectGroupLinkWithMissingGro
).perform
end
- it 'removes the `project_group_links` records whose associated group does not exist anymore',
- quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/424274' do
+ it 'removes the `project_group_links` records whose associated group does not exist anymore' do
group_2.delete
# Schema is fixed to `20230206172702` on this spec.
diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb
index 388c284d83c..6f1eb77fa9b 100644
--- a/spec/requests/api/graphql/ci/runner_spec.rb
+++ b/spec/requests/api/graphql/ci/runner_spec.rb
@@ -5,6 +5,8 @@ require 'spec_helper'
RSpec.describe 'Query.runner(id)', :freeze_time, feature_category: :runner_fleet do
include GraphqlHelpers
+ using RSpec::Parameterized::TableSyntax
+
let_it_be(:user) { create(:user, :admin) }
let_it_be(:another_admin) { create(:user, :admin) }
let_it_be_with_reload(:group) { create(:group) }
@@ -144,23 +146,6 @@ RSpec.describe 'Query.runner(id)', :freeze_time, feature_category: :runner_fleet
)
expect(runner_data['tagList']).to match_array runner.tag_list
end
-
- it 'does not execute more queries per runner', :use_sql_query_cache, :aggregate_failures do
- # warm-up license cache and so on:
- personal_access_token = create(:personal_access_token, user: user)
- args = { current_user: user, token: { personal_access_token: personal_access_token } }
- post_graphql(query, **args)
- expect(graphql_data_at(:runner)).not_to be_nil
-
- personal_access_token = create(:personal_access_token, user: another_admin)
- args = { current_user: another_admin, token: { personal_access_token: personal_access_token } }
- control = ActiveRecord::QueryRecorder.new(skip_cached: false) { post_graphql(query, **args) }
-
- create(:ci_runner, :instance, version: '14.0.0', tag_list: %w[tag5 tag6], creator: another_admin)
- create(:ci_runner, :project, version: '14.0.1', projects: [project1], tag_list: %w[tag3 tag8], creator: another_admin)
-
- expect { post_graphql(query, **args) }.not_to exceed_all_query_limit(control)
- end
end
shared_examples 'retrieval with no admin url' do
@@ -248,23 +233,25 @@ RSpec.describe 'Query.runner(id)', :freeze_time, feature_category: :runner_fleet
end
describe 'for project runner' do
- describe 'locked' do
- using RSpec::Parameterized::TableSyntax
+ let_it_be_with_refind(:project_runner) do
+ create(:ci_runner, :project,
+ description: 'Runner 3',
+ contacted_at: 1.day.ago,
+ active: false,
+ locked: false,
+ version: 'adfe157',
+ revision: 'b',
+ ip_address: '10.10.10.10',
+ access_level: 1,
+ run_untagged: true)
+ end
+ describe 'locked' do
where(is_locked: [true, false])
with_them do
- let(:project_runner) do
- create(:ci_runner, :project,
- description: 'Runner 3',
- contacted_at: 1.day.ago,
- active: false,
- locked: is_locked,
- version: 'adfe157',
- revision: 'b',
- ip_address: '10.10.10.10',
- access_level: 1,
- run_untagged: true)
+ before do
+ project_runner.update!(locked: is_locked)
end
let(:query) do
@@ -428,6 +415,38 @@ RSpec.describe 'Query.runner(id)', :freeze_time, feature_category: :runner_fleet
end
end
end
+
+ describe 'a query fetching all fields' do
+ let(:query) do
+ wrap_fields(query_graphql_path(query_path, all_graphql_fields_for('CiRunner')))
+ end
+
+ let(:query_path) do
+ [
+ [:runner, { id: project_runner.to_global_id.to_s }]
+ ]
+ end
+
+ it 'does not execute more queries per runner', :use_sql_query_cache, :aggregate_failures do
+ create(:ci_build, :failed, runner: project_runner)
+ create(:ci_runner_machine, runner: project_runner, version: '16.4.0')
+
+ # warm-up license cache and so on:
+ personal_access_token = create(:personal_access_token, user: user)
+ args = { current_user: user, token: { personal_access_token: personal_access_token } }
+ post_graphql(query, **args)
+ expect(graphql_data_at(:runner)).not_to be_nil
+
+ personal_access_token = create(:personal_access_token, user: another_admin)
+ args = { current_user: another_admin, token: { personal_access_token: personal_access_token } }
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) { post_graphql(query, **args) }
+
+ create(:ci_build, :failed, runner: project_runner)
+ create(:ci_runner_machine, runner: project_runner, version: '16.4.1')
+
+ expect { post_graphql(query, **args) }.not_to exceed_all_query_limit(control)
+ end
+ end
end
describe 'for inactive runner' do
@@ -995,8 +1014,6 @@ RSpec.describe 'Query.runner(id)', :freeze_time, feature_category: :runner_fleet
end
context 'when requesting individual fields' do
- using RSpec::Parameterized::TableSyntax
-
where(:field) do
[
'detailedStatus { id detailsPath group icon text }',
diff --git a/spec/requests/api/graphql/ci/runners_spec.rb b/spec/requests/api/graphql/ci/runners_spec.rb
index 3f6d39435fd..c5571086700 100644
--- a/spec/requests/api/graphql/ci/runners_spec.rb
+++ b/spec/requests/api/graphql/ci/runners_spec.rb
@@ -72,10 +72,16 @@ RSpec.describe 'Query.runners', feature_category: :runner_fleet do
args = { current_user: admin2, token: { personal_access_token: personal_access_token } }
control = ActiveRecord::QueryRecorder.new { post_graphql(query, **args) }
- create(:ci_runner, :instance, version: '14.0.0', tag_list: %w[tag5 tag6], creator: admin2)
- create(:ci_runner, :project, version: '14.0.1', projects: [project], tag_list: %w[tag3 tag8],
+ runner2 = create(:ci_runner, :instance, version: '14.0.0', tag_list: %w[tag5 tag6], creator: admin2)
+ runner3 = create(:ci_runner, :project, version: '14.0.1', projects: [project], tag_list: %w[tag3 tag8],
creator: current_user)
+ create(:ci_build, :failed, runner: runner2)
+ create(:ci_runner_machine, runner: runner2, version: '16.4.1')
+
+ create(:ci_build, :failed, runner: runner3)
+ create(:ci_runner_machine, runner: runner3, version: '16.4.0')
+
expect { post_graphql(query, **args) }.not_to exceed_query_limit(control)
end
end
diff --git a/spec/requests/api/graphql/group/work_items_spec.rb b/spec/requests/api/graphql/group/work_items_spec.rb
index f6dad577b5e..ef96ef4754a 100644
--- a/spec/requests/api/graphql/group/work_items_spec.rb
+++ b/spec/requests/api/graphql/group/work_items_spec.rb
@@ -35,7 +35,7 @@ RSpec.describe 'getting a work_item list for a group', feature_category: :team_p
let_it_be(:other_work_item) { create(:work_item) }
let(:work_items_data) { graphql_data['group']['workItems']['nodes'] }
- let(:work_item_filter_params) { {} }
+ let(:item_filter_params) { {} }
let(:current_user) { user }
let(:query_group) { group }
@@ -47,6 +47,28 @@ RSpec.describe 'getting a work_item list for a group', feature_category: :team_p
QUERY
end
+ it_behaves_like 'graphql work item list request spec' do
+ let_it_be(:container_build_params) { { namespace: group } }
+ let(:work_item_node_path) { %w[group workItems nodes] }
+
+ def post_query(request_user = current_user)
+ post_graphql(query, current_user: request_user)
+ end
+ end
+
+ context 'when filtering by search' do
+ let(:item_filter_params) { { search: 'search_term', in: [:DESCRIPTION] } }
+
+ # TODO: https://gitlab.com/gitlab-org/gitlab/-/work_items/393126
+ it 'returns an error since search is not implemented at the group level yet' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_errors).to contain_exactly(
+ hash_including('message' => 'Searching is not available for work items at the namespace level yet')
+ )
+ end
+ end
+
context 'when the user can not see confidential work_items' do
it_behaves_like 'a working graphql query' do
before do
@@ -66,12 +88,6 @@ RSpec.describe 'getting a work_item list for a group', feature_category: :team_p
context 'when the user can see confidential work_items' do
let(:current_user) { reporter }
- it_behaves_like 'a working graphql query' do
- before do
- post_graphql(query, current_user: current_user)
- end
- end
-
it 'returns also confidential work_items' do
post_graphql(query, current_user: current_user)
@@ -96,7 +112,7 @@ RSpec.describe 'getting a work_item list for a group', feature_category: :team_p
graphql_dig_at(work_items_data, :id)
end
- def query(params = work_item_filter_params)
+ def query(params = item_filter_params)
graphql_query_for(
'group',
{ 'fullPath' => query_group.full_path },
diff --git a/spec/requests/api/graphql/jobs_query_spec.rb b/spec/requests/api/graphql/jobs_query_spec.rb
index 4248a03fa74..7607aeac6e0 100644
--- a/spec/requests/api/graphql/jobs_query_spec.rb
+++ b/spec/requests/api/graphql/jobs_query_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe 'getting job information', feature_category: :continuous_integrat
:jobs, {}, %(
count
nodes {
- #{all_graphql_fields_for(::Types::Ci::JobType, max_depth: 1, excluded: %w[aiFailureAnalysis])}
+ #{all_graphql_fields_for(::Types::Ci::JobType, max_depth: 1)}
})
)
end
diff --git a/spec/requests/api/graphql/project/work_items_spec.rb b/spec/requests/api/graphql/project/work_items_spec.rb
index 4aba83dae92..d5d3d6c578f 100644
--- a/spec/requests/api/graphql/project/work_items_spec.rb
+++ b/spec/requests/api/graphql/project/work_items_spec.rb
@@ -30,16 +30,14 @@ RSpec.describe 'getting a work item list for a project', feature_category: :team
let_it_be(:confidential_item) { create(:work_item, confidential: true, project: project, title: 'item3') }
let_it_be(:other_item) { create(:work_item) }
- let(:items_data) { graphql_data['project']['workItems']['edges'] }
+ let(:items_data) { graphql_data['project']['workItems']['nodes'] }
let(:item_filter_params) { {} }
let(:fields) do
<<~QUERY
- edges {
- node {
+ nodes {
#{all_graphql_fields_for('workItems'.classify, max_depth: 2)}
}
- }
QUERY
end
@@ -69,6 +67,15 @@ RSpec.describe 'getting a work item list for a project', feature_category: :team
end
end
+ it_behaves_like 'graphql work item list request spec' do
+ let_it_be(:container_build_params) { { project: project } }
+ let(:work_item_node_path) { %w[project workItems nodes] }
+
+ def post_query(request_user = current_user)
+ post_graphql(query, current_user: request_user)
+ end
+ end
+
describe 'N + 1 queries' do
context 'when querying root fields' do
it_behaves_like 'work items resolver without N + 1 queries'
@@ -199,12 +206,6 @@ RSpec.describe 'getting a work item list for a project', feature_category: :team
end
end
- it_behaves_like 'a working graphql query' do
- before do
- post_graphql(query, current_user: current_user)
- end
- end
-
context 'when the user does not have access to the item' do
before do
project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
@@ -237,25 +238,12 @@ RSpec.describe 'getting a work item list for a project', feature_category: :team
context 'when filtering by search' do
it_behaves_like 'query with a search term' do
- let(:issuable_data) { items_data }
+ let(:ids) { item_ids }
let(:user) { current_user }
let_it_be(:issuable) { create(:work_item, project: project, description: 'bar') }
end
end
- context 'when filtering by author username' do
- let_it_be(:author) { create(:author) }
- let_it_be(:item_3) { create(:work_item, project: project, author: author) }
-
- let(:item_filter_params) { { author_username: item_3.author.username } }
-
- it 'returns correct results' do
- post_graphql(query, current_user: current_user)
-
- expect(item_ids).to match_array([item_3.to_global_id.to_s])
- end
- end
-
describe 'sorting and pagination' do
let(:data_path) { [:project, :work_items] }
@@ -415,7 +403,7 @@ RSpec.describe 'getting a work item list for a project', feature_category: :team
end
def item_ids
- graphql_dig_at(items_data, :node, :id)
+ graphql_dig_at(items_data, :id)
end
def query(params = item_filter_params)
diff --git a/spec/services/labels/available_labels_service_spec.rb b/spec/services/labels/available_labels_service_spec.rb
index c9f75283c75..2b398210034 100644
--- a/spec/services/labels/available_labels_service_spec.rb
+++ b/spec/services/labels/available_labels_service_spec.rb
@@ -108,36 +108,24 @@ RSpec.describe Labels::AvailableLabelsService, feature_category: :team_planning
end
end
- describe '#filter_locked_labels_ids_in_param' do
- let(:label_ids) { labels.map(&:id).push(non_existing_record_id) }
+ describe '#filter_locked_label_ids' do
+ let(:label_ids) { labels.map(&:id) }
context 'when parent is a project' do
- it 'returns only locked label ids' do
- result = described_class.new(user, project, ids: label_ids).filter_locked_labels_ids_in_param(:ids)
+ it 'returns only relevant label ids' do
+ result = described_class.new(user, project, ids: label_ids).filter_locked_label_ids(label_ids)
expect(result).to match_array([project_label_locked.id, group_label_locked.id])
end
-
- it 'returns labels in preserved order' do
- result = described_class.new(user, project, ids: label_ids.reverse).filter_locked_labels_ids_in_param(:ids)
-
- expect(result).to eq([group_label_locked.id, project_label_locked.id])
- end
end
context 'when parent is a group' do
- it 'returns only locked label ids' do
- result = described_class.new(user, group, ids: label_ids).filter_locked_labels_ids_in_param(:ids)
+ it 'returns only relevant label ids' do
+ result = described_class.new(user, group, ids: label_ids).filter_locked_label_ids(label_ids)
expect(result).to match_array([group_label_locked.id])
end
end
-
- it 'accepts a single id parameter' do
- result = described_class.new(user, project, label_id: project_label_locked.id).filter_locked_labels_ids_in_param(:label_id)
-
- expect(result).to match_array([project_label_locked.id])
- end
end
describe '#available_labels' do
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 2f6db13a041..72e41f7b814 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -1301,41 +1301,39 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re
end
context 'updating labels' do
- let(:label_a) { label }
- let(:label_b) { create(:label, title: 'b', project: project) }
- let(:label_c) { create(:label, title: 'c', project: project) }
- let(:label_locked) { create(:label, title: 'locked', project: project, lock_on_merge: true) }
- let(:issuable) { merge_request }
+ context 'when merge request is not merged' do
+ let(:label_a) { label }
+ let(:label_b) { create(:label, title: 'b', project: project) }
+ let(:label_c) { create(:label, title: 'c', project: project) }
+ let(:label_locked) { create(:label, title: 'locked', project: project, lock_on_merge: true) }
+ let(:issuable) { merge_request }
- it_behaves_like 'updating issuable labels'
- it_behaves_like 'keeps issuable labels sorted after update'
- it_behaves_like 'broadcasting issuable labels updates'
+ it_behaves_like 'updating issuable labels'
+ it_behaves_like 'keeps issuable labels sorted after update'
+ it_behaves_like 'broadcasting issuable labels updates'
+ end
context 'when merge request has been merged' do
- context 'when remove_label_ids contains a locked label' do
- let(:params) { { remove_label_ids: [label_locked.id] } }
+ let(:label_a) { create(:label, title: 'a', project: project, lock_on_merge: true) }
+ let(:label_b) { create(:label, title: 'b', project: project, lock_on_merge: true) }
+ let(:label_c) { create(:label, title: 'c', project: project, lock_on_merge: true) }
+ let(:label_unlocked) { create(:label, title: 'unlocked', project: project) }
+ let(:issuable) { merge_request }
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(enforce_locked_labels_on_merge: false)
- end
+ before do
+ merge_request.update!(state: 'merged')
+ end
- it 'removes locked labels' do
- merge_request.update!(state: 'merged', labels: [label_a, label_locked])
- update_issuable(params)
+ it_behaves_like 'updating merged MR with locked labels'
- expect(merge_request.label_ids).to contain_exactly(label_a.id)
- end
- end
-
- context 'when feature flag is enabled' do
- it 'does not remove locked labels' do
- merge_request.update!(state: 'merged', labels: [label_a, label_locked])
- update_issuable(params)
+ context 'when feature flag is disabled' do
+ let(:label_locked) { create(:label, title: 'locked', project: project, lock_on_merge: true) }
- expect(merge_request.label_ids).to contain_exactly(label_a.id, label_locked.id)
- end
+ before do
+ stub_feature_flags(enforce_locked_labels_on_merge: false)
end
+
+ it_behaves_like 'updating issuable labels'
end
end
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index 17b4270fa20..3131a22a20b 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -73,7 +73,10 @@ module DbCleaner
end
end
+ disable_ddl_was = Feature.enabled?(:disallow_database_ddl_feature_flags, type: :ops)
+ stub_feature_flags(disallow_database_ddl_feature_flags: false)
Gitlab::Database::Partitioning.sync_partitions_ignore_db_error
+ stub_feature_flags(disallow_database_ddl_feature_flags: disable_ddl_was)
puts "Databases re-creation done in #{Gitlab::Metrics::System.monotonic_time - start}"
end
diff --git a/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb b/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb
index d654a30d54a..c2d144bef3b 100644
--- a/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb
+++ b/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb
@@ -47,7 +47,8 @@ RSpec.shared_examples 'labels sidebar widget' do
end
end
- it 'adds first label by pressing enter when search' do
+ it 'adds first label by pressing enter when search',
+ quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/414877' do
within(labels_widget) do
page.within('[data-testid="value-wrapper"]') do
expect(page).not_to have_content(development.name)
diff --git a/spec/support/shared_examples/requests/api/graphql/work_item_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/work_item_list_shared_examples.rb
new file mode 100644
index 00000000000..a9c422c8f2d
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/graphql/work_item_list_shared_examples.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'graphql work item list request spec' do
+ let(:work_item_ids) { graphql_dig_at(work_item_data, :id) }
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_query
+ end
+ end
+
+ describe 'filters' do
+ before do
+ post_query
+ end
+
+ context 'when filtering by author username' do
+ let(:author) { create(:author) }
+ let(:authored_work_item) { create(:work_item, author: author, **container_build_params) }
+
+ let(:item_filter_params) { { author_username: authored_work_item.author.username } }
+
+ it 'returns correct results' do
+ expect(work_item_ids).to contain_exactly(authored_work_item.to_global_id.to_s)
+ end
+ end
+
+ context 'when filtering by state' do
+ let_it_be(:opened_work_item) { create(:work_item, :opened, **container_build_params) }
+ let_it_be(:closed_work_item) { create(:work_item, :closed, **container_build_params) }
+
+ context 'when filtering by state opened' do
+ let(:item_filter_params) { { state: :opened } }
+
+ it 'filters by state' do
+ expect(work_item_ids).to include(opened_work_item.to_global_id.to_s)
+ expect(work_item_ids).not_to include(closed_work_item.to_global_id.to_s)
+ end
+ end
+
+ context 'when filtering by state closed' do
+ let(:item_filter_params) { { state: :closed } }
+
+ it 'filters by state' do
+ expect(work_item_ids).not_to include(opened_work_item.to_global_id.to_s)
+ expect(work_item_ids).to include(closed_work_item.to_global_id.to_s)
+ end
+ end
+ end
+
+ context 'when filtering by type' do
+ let_it_be(:issue_work_item) { create(:work_item, :issue, **container_build_params) }
+ let_it_be(:task_work_item) { create(:work_item, :task, **container_build_params) }
+
+ context 'when filtering by issue type' do
+ let(:item_filter_params) { { types: [:ISSUE] } }
+
+ it 'filters by type' do
+ expect(work_item_ids).to include(issue_work_item.to_global_id.to_s)
+ expect(work_item_ids).not_to include(task_work_item.to_global_id.to_s)
+ end
+ end
+
+ context 'when filtering by task type' do
+ let(:item_filter_params) { { types: [:TASK] } }
+
+ it 'filters by type' do
+ expect(work_item_ids).not_to include(issue_work_item.to_global_id.to_s)
+ expect(work_item_ids).to include(task_work_item.to_global_id.to_s)
+ end
+ end
+ end
+
+ context 'when filtering by iid' do
+ let_it_be(:work_item_by_iid) { create(:work_item, **container_build_params) }
+
+ context 'when using the iid filter' do
+ let(:item_filter_params) { { iid: work_item_by_iid.iid.to_s } }
+
+ it 'returns only items by the given iid' do
+ expect(work_item_ids).to contain_exactly(work_item_by_iid.to_global_id.to_s)
+ end
+ end
+
+ context 'when using the iids filter' do
+ let(:item_filter_params) { { iids: [work_item_by_iid.iid.to_s] } }
+
+ it 'returns only items by the given iid' do
+ expect(work_item_ids).to contain_exactly(work_item_by_iid.to_global_id.to_s)
+ end
+ end
+ end
+ end
+
+ def work_item_data
+ graphql_data.dig(*work_item_node_path)
+ end
+end
diff --git a/spec/support/shared_examples/services/issuable/issuable_update_service_shared_examples.rb b/spec/support/shared_examples/services/issuable/issuable_update_service_shared_examples.rb
index 3f95d6060ea..9624f7a4450 100644
--- a/spec/support/shared_examples/services/issuable/issuable_update_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/issuable/issuable_update_service_shared_examples.rb
@@ -145,6 +145,77 @@ RSpec.shared_examples 'updating issuable labels' do
end
end
+RSpec.shared_examples 'updating merged MR with locked labels' do
+ context 'when add_label_ids and label_ids are passed' do
+ let(:params) { { label_ids: [label_a.id], add_label_ids: [label_c.id] } }
+
+ it 'replaces unlocked labels with the ones in label_ids and adds those in add_label_ids' do
+ issuable.update!(labels: [label_b, label_unlocked])
+ update_issuable(params)
+
+ expect(issuable.label_ids).to contain_exactly(label_a.id, label_b.id, label_c.id)
+ end
+ end
+
+ context 'when remove_label_ids and label_ids are passed' do
+ let(:params) { { label_ids: [label_a.id, label_b.id, label_c.id], remove_label_ids: [label_a.id] } }
+
+ it 'replaces unlocked labels with the ones in label_ids and does not remove locked label in remove_label_ids' do
+ issuable.update!(labels: [label_a, label_c, label_unlocked])
+ update_issuable(params)
+
+ expect(issuable.label_ids).to contain_exactly(label_a.id, label_b.id, label_c.id)
+ end
+ end
+
+ context 'when add_label_ids and remove_label_ids are passed' do
+ let(:params) { { add_label_ids: [label_c.id], remove_label_ids: [label_a.id, label_unlocked.id] } }
+
+ before do
+ issuable.update!(labels: [label_a, label_unlocked])
+ update_issuable(params)
+ end
+
+ it 'adds the passed labels' do
+ expect(issuable.label_ids).to include(label_c.id)
+ end
+
+ it 'removes the passed unlocked labels' do
+ expect(issuable.label_ids).to include(label_a.id)
+ expect(issuable.label_ids).not_to include(label_unlocked.id)
+ end
+ end
+
+ context 'when same id is passed as add_label_ids and remove_label_ids' do
+ let(:params) { { add_label_ids: [label_a.id], remove_label_ids: [label_a.id] } }
+
+ context 'for a label assigned to an issue' do
+ it 'does not remove the label' do
+ issuable.update!(labels: [label_a])
+ update_issuable(params)
+
+ expect(issuable.label_ids).to contain_exactly(label_a.id)
+ end
+ end
+
+ context 'for a label not assigned to an issue' do
+ it 'does not add the label' do
+ expect(issuable.label_ids).to be_empty
+ end
+ end
+ end
+
+ context 'when duplicate label titles are given' do
+ let(:params) { { labels: [label_c.title, label_c.title] } }
+
+ it 'assigns the label once' do
+ update_issuable(params)
+
+ expect(issuable.labels).to contain_exactly(label_c)
+ end
+ end
+end
+
RSpec.shared_examples 'keeps issuable labels sorted after update' do
before do
update_issuable(label_ids: [label_b.id])
diff --git a/spec/views/devise/shared/_signin_box.html.haml_spec.rb b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
index e2aa0bb9870..24937cfdd4a 100644
--- a/spec/views/devise/shared/_signin_box.html.haml_spec.rb
+++ b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe 'devise/shared/_signin_box' do
it 'renders user_login label' do
render
- expect(rendered).to have_content(_('Username or email'))
+ expect(rendered).to have_content(_('Username or primary email'))
end
end
diff --git a/yarn.lock b/yarn.lock
index 48001a5da3c..10c43095994 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7171,10 +7171,10 @@ graphql@^15.7.2:
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.7.2.tgz#85ab0eeb83722977151b3feb4d631b5f2ab287ef"
integrity sha512-AnnKk7hFQFmU/2I9YSQf3xw44ctnSFCfp3zE0N6W174gqe9fWG/2rKaKxROK7CcI3XtERpjEKFqts8o319Kf7A==
-gridstack@^8.4.0:
- version "8.4.0"
- resolved "https://registry.yarnpkg.com/gridstack/-/gridstack-8.4.0.tgz#7af49159f9dc144c89a2c56246e1710406f75fcf"
- integrity sha512-qLJuJrBy9bbG3hI+h2cEhiuZ51J3MyEMmv5AXg7MCFiBeG8A4HyIUytueqtD/oZcA3Pccq2Xoj7GrwpmKOS3ig==
+gridstack@^9.1.0:
+ version "9.1.0"
+ resolved "https://registry.yarnpkg.com/gridstack/-/gridstack-9.1.0.tgz#9c82808cc87ff4491f5bfcd517dc7ead922aa62e"
+ integrity sha512-ViUwP6Q9WzYUd/ulKZ7/bmX3JZeQciVgZa4P5ZChXhwMsNJp7ctb6RnxxatcAkG+X0oln7udPI8AsW0SMsPUYQ==
gzip-size@^6.0.0:
version "6.0.0"