diff options
46 files changed, 616 insertions, 84 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 8587312bb08..3199739a33f 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -1043,7 +1043,6 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /app/assets/javascripts/alerts_settings/graphql/mutations/reset_http_token.mutation.graphql @gitlab-org/manage/authentication-and-authorization/approvers /app/assets/javascripts/authentication/ @gitlab-org/manage/authentication-and-authorization/approvers /app/assets/javascripts/ide/components/shared/tokened_input.vue @gitlab-org/manage/authentication-and-authorization/approvers -/app/assets/javascripts/invite_members/components/members_token_select.vue @gitlab-org/manage/authentication-and-authorization/approvers /app/assets/javascripts/packages_and_registries/package_registry/components/list/tokens/ @gitlab-org/manage/authentication-and-authorization/approvers /app/assets/javascripts/pages/admin/impersonation_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers /app/assets/javascripts/pages/groups/settings/access_tokens/ @gitlab-org/manage/authentication-and-authorization/approvers diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 830fbf64d38..c40f5744686 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -1903,14 +1903,17 @@ - <<: *if-dot-com-gitlab-org-merge-request changes: *controllers-patterns variables: *review-change-pattern + when: manual allow_failure: true - <<: *if-dot-com-gitlab-org-merge-request changes: *models-patterns variables: *review-change-pattern + when: manual allow_failure: true - <<: *if-dot-com-gitlab-org-merge-request changes: *lib-gitlab-patterns variables: *review-change-pattern + when: manual allow_failure: true - <<: *if-dot-com-gitlab-org-merge-request changes: *qa-patterns diff --git a/app/assets/javascripts/issues/list/constants.js b/app/assets/javascripts/issues/list/constants.js index fd7d53e7a36..16d28e8404c 100644 --- a/app/assets/javascripts/issues/list/constants.js +++ b/app/assets/javascripts/issues/list/constants.js @@ -147,7 +147,7 @@ export const specialFilterValues = [ export const TYPE_TOKEN_TASK_OPTION = { icon: 'issue-type-task', title: 'task', value: 'task' }; export const TYPE_TOKEN_OBJECTIVE_OPTION = { - icon: 'issue-type-issue', + icon: 'issue-type-objective', title: 'objective', value: 'objective', }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue index a15bf6fadd8..6b9a8f59aa8 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue @@ -467,8 +467,9 @@ export default { <template> <div + :class="{ 'gl-bg-gray-10': mr.state !== 'closed' && mr.state !== 'merged' }" data-testid="ready_to_merge_state" - class="gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-bg-gray-10 gl-pl-7" + class="gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-pl-7" > <div v-if="loading" class="mr-widget-body"> <div class="gl-w-full mr-ready-to-merge-loader"> diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js index cdb69586969..aef488739ee 100644 --- a/app/assets/javascripts/work_items/constants.js +++ b/app/assets/javascripts/work_items/constants.js @@ -112,7 +112,7 @@ export const WORK_ITEMS_TYPE_MAP = { name: s__('WorkItem|Requirements'), }, [WORK_ITEM_TYPE_ENUM_OBJECTIVE]: { - icon: `issue-type-issue`, + icon: `issue-type-objective`, name: s__('WorkItem|Objective'), }, [WORK_ITEM_TYPE_ENUM_KEY_RESULT]: { diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb index 8acbba0621b..2ebc1d67ec7 100644 --- a/app/controllers/concerns/lfs_request.rb +++ b/app/controllers/concerns/lfs_request.rb @@ -94,10 +94,15 @@ module LfsRequest next false unless has_authentication_ability?(:push_code) next false if limit_exceeded? - lfs_deploy_token? || can?(user, :push_code, project) || can?(deploy_token, :push_code, project) + lfs_deploy_token? || can?(user, :push_code, +project) || can?(deploy_token, :push_code, project) || any_branch_allows_collaboration? end end + def any_branch_allows_collaboration? + project.merge_requests_allowing_push_to_user(user).any? + end + def lfs_deploy_token? authentication_result.lfs_deploy_token?(project) end diff --git a/app/finders/users_finder.rb b/app/finders/users_finder.rb index 9c2462b42a6..11e3c341c1f 100644 --- a/app/finders/users_finder.rb +++ b/app/finders/users_finder.rb @@ -55,7 +55,7 @@ class UsersFinder private def base_scope - scope = current_user&.admin? ? User.all : User.without_forbidden_states + scope = current_user&.can_admin_all_resources? ? User.all : User.without_forbidden_states scope.order_id_desc end @@ -80,7 +80,7 @@ class UsersFinder def by_search(users) return users unless params[:search].present? - users.search(params[:search], with_private_emails: current_user&.admin?) + users.search(params[:search], with_private_emails: current_user&.can_admin_all_resources?) end def by_blocked(users) @@ -97,7 +97,7 @@ class UsersFinder # rubocop: disable CodeReuse/ActiveRecord def by_external_identity(users) - return users unless current_user&.admin? && params[:extern_uid] && params[:provider] + return users unless current_user&.can_admin_all_resources? && params[:extern_uid] && params[:provider] users.joins(:identities).merge(Identity.with_extern_uid(params[:provider], params[:extern_uid])) end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index b8ac2afa7d6..54700e634b6 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -40,6 +40,7 @@ module SearchHelper [ groups_autocomplete(term), projects_autocomplete(term), + users_autocomplete(term), issue_autocomplete(term) ].flatten end @@ -351,6 +352,25 @@ module SearchHelper end end + def users_autocomplete(term, limit = 5) + return [] unless current_user && Ability.allowed?(current_user, :read_users_list) + + SearchService + .new(current_user, { scope: 'users', search: term }) + .search_objects + .limit(limit) + .map do |user| + { + category: "Users", + id: user.id, + value: "#{search_result_sanitize(user.name)}", + label: "#{search_result_sanitize(user.username)}", + url: user_path(user), + avatar_url: user.avatar_url || '' + } + end + end + def recent_merge_requests_autocomplete(term) return [] unless current_user diff --git a/app/models/work_items/type.rb b/app/models/work_items/type.rb index dc30899d24f..51ec5676ae6 100644 --- a/app/models/work_items/type.rb +++ b/app/models/work_items/type.rb @@ -10,17 +10,29 @@ module WorkItems include CacheMarkdownField + # type name is used in restrictions DB seeder to assure restrictions for + # default types are pre-filled + TYPE_NAMES = { + issue: 'Issue', + incident: 'Incident', + test_case: 'Test Case', + requirement: 'Requirement', + task: 'Task', + objective: 'Objective', + key_result: 'Key Result' + }.freeze + # Base types need to exist on the DB on app startup # This constant is used by the DB seeder # TODO - where to add new icon names created? BASE_TYPES = { - issue: { name: 'Issue', icon_name: 'issue-type-issue', enum_value: 0 }, - incident: { name: 'Incident', icon_name: 'issue-type-incident', enum_value: 1 }, - test_case: { name: 'Test Case', icon_name: 'issue-type-test-case', enum_value: 2 }, ## EE-only - requirement: { name: 'Requirement', icon_name: 'issue-type-requirements', enum_value: 3 }, ## EE-only - task: { name: 'Task', icon_name: 'issue-type-task', enum_value: 4 }, - objective: { name: 'Objective', icon_name: 'issue-type-objective', enum_value: 5 }, ## EE-only - key_result: { name: 'Key Result', icon_name: 'issue-type-keyresult', enum_value: 6 } ## EE-only + issue: { name: TYPE_NAMES[:issue], icon_name: 'issue-type-issue', enum_value: 0 }, + incident: { name: TYPE_NAMES[:incident], icon_name: 'issue-type-incident', enum_value: 1 }, + test_case: { name: TYPE_NAMES[:test_case], icon_name: 'issue-type-test-case', enum_value: 2 }, ## EE-only + requirement: { name: TYPE_NAMES[:requirement], icon_name: 'issue-type-requirements', enum_value: 3 }, ## EE-only + task: { name: TYPE_NAMES[:task], icon_name: 'issue-type-task', enum_value: 4 }, + objective: { name: TYPE_NAMES[:objective], icon_name: 'issue-type-objective', enum_value: 5 }, ## EE-only + key_result: { name: TYPE_NAMES[:key_result], icon_name: 'issue-type-keyresult', enum_value: 6 } ## EE-only }.freeze WIDGETS_FOR_TYPE = { @@ -66,6 +78,7 @@ module WorkItems return found_type if found_type Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter.upsert_types + Gitlab::DatabaseImporters::WorkItems::HierarchyRestrictionsImporter.upsert_restrictions find_by(namespace_id: nil, base_type: type) end diff --git a/db/fixtures/development/50_create_work_item_hierarchy_restrictions.rb b/db/fixtures/development/50_create_work_item_hierarchy_restrictions.rb new file mode 100644 index 00000000000..b5c5d0cacdd --- /dev/null +++ b/db/fixtures/development/50_create_work_item_hierarchy_restrictions.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +Gitlab::Seeder.quiet do + Gitlab::DatabaseImporters::WorkItems::HierarchyRestrictionsImporter.upsert_restrictions +end diff --git a/db/fixtures/production/020_create_work_item_hierarchy_restrictions.rb b/db/fixtures/production/020_create_work_item_hierarchy_restrictions.rb new file mode 100644 index 00000000000..b5c5d0cacdd --- /dev/null +++ b/db/fixtures/production/020_create_work_item_hierarchy_restrictions.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +Gitlab::Seeder.quiet do + Gitlab::DatabaseImporters::WorkItems::HierarchyRestrictionsImporter.upsert_restrictions +end diff --git a/db/post_migrate/20221116143854_add_okr_hierarchy_restrictions.rb b/db/post_migrate/20221116143854_add_okr_hierarchy_restrictions.rb new file mode 100644 index 00000000000..658ce0287f8 --- /dev/null +++ b/db/post_migrate/20221116143854_add_okr_hierarchy_restrictions.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +class AddOkrHierarchyRestrictions < Gitlab::Database::Migration[2.0] + class WorkItemType < MigrationRecord + self.table_name = 'work_item_types' + end + + class HierarchyRestriction < MigrationRecord + self.table_name = 'work_item_hierarchy_restrictions' + end + + restrict_gitlab_migration gitlab_schema: :gitlab_main + disable_ddl_transaction! + + def up + objective = WorkItemType.find_by_name_and_namespace_id('Objective', nil) + key_result = WorkItemType.find_by_name_and_namespace_id('Key Result', nil) + issue = WorkItemType.find_by_name_and_namespace_id('Issue', nil) + task = WorkItemType.find_by_name_and_namespace_id('Task', nil) + incident = WorkItemType.find_by_name_and_namespace_id('Incident', nil) + + # work item default types should be filled, if this is not the case + # then restrictions will be created together with work item types + unless objective && key_result && issue && task && incident + Gitlab::AppLogger.warn('default types are missing, not adding restrictions') + + return + end + + restrictions = [ + { parent_type_id: objective.id, child_type_id: objective.id, maximum_depth: 9 }, + { parent_type_id: objective.id, child_type_id: key_result.id, maximum_depth: 1 }, + { parent_type_id: issue.id, child_type_id: task.id, maximum_depth: 1 }, + { parent_type_id: incident.id, child_type_id: task.id, maximum_depth: 1 } + ] + + HierarchyRestriction.upsert_all( + restrictions, + unique_by: :index_work_item_hierarchy_restrictions_on_parent_and_child + ) + end + + def down + # so far restrictions table was empty so we can delete all records when + # migrating down + HierarchyRestriction.delete_all + end +end diff --git a/db/schema_migrations/20221116143854 b/db/schema_migrations/20221116143854 new file mode 100644 index 00000000000..9f0b0815c79 --- /dev/null +++ b/db/schema_migrations/20221116143854 @@ -0,0 +1 @@ +a6caf06dd18f096219d5ce0752c956ef099a92df71899c1b9164d3a16f6ef0ba
\ No newline at end of file diff --git a/doc/administration/feature_flags.md b/doc/administration/feature_flags.md index f2a40b60536..f7237b167e5 100644 --- a/doc/administration/feature_flags.md +++ b/doc/administration/feature_flags.md @@ -45,8 +45,7 @@ Features that are disabled by default may change or be removed without notice in Data corruption, stability degradation, performance degradation, or security issues might occur if you enable a feature that's disabled by default. Problems caused by using a default -disabled feature aren't covered by GitLab support, unless you were directed by GitLab -to enable the feature. +disabled feature aren't covered by GitLab Support. Security issues found in features that are disabled by default are patched in regular releases and do not follow our regular [maintenance policy](../policy/maintenance.md#security-releases) diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index e1fe6a0c1bf..1b606cc50bf 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -5795,7 +5795,7 @@ Input type: `VulnerabilityDismissInput` | Name | Type | Description | | ---- | ---- | ----------- | | <a id="mutationvulnerabilitydismissclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | -| <a id="mutationvulnerabilitydismisscomment"></a>`comment` | [`String`](#string) | Comment why vulnerability should be dismissed. | +| <a id="mutationvulnerabilitydismisscomment"></a>`comment` | [`String`](#string) | Comment why vulnerability should be dismissed (max. 50 000 characters). | | <a id="mutationvulnerabilitydismissdismissalreason"></a>`dismissalReason` | [`VulnerabilityDismissalReason`](#vulnerabilitydismissalreason) | Reason why vulnerability should be dismissed. | | <a id="mutationvulnerabilitydismissid"></a>`id` | [`VulnerabilityID!`](#vulnerabilityid) | ID of the vulnerability to be dismissed. | diff --git a/doc/api/merge_request_approvals.md b/doc/api/merge_request_approvals.md index 0476035784a..4d52bf36f20 100644 --- a/doc/api/merge_request_approvals.md +++ b/doc/api/merge_request_approvals.md @@ -433,6 +433,7 @@ Supported attributes: | `applies_to_all_protected_branches` | boolean | **{dotted-circle}** No | Whether the rule is applied to all protected branches. If set to `true`, the value of `protected_branch_ids` is ignored. Default is `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/335316) in GitLab 15.3. | | `group_ids` | Array | **{dotted-circle}** No | The IDs of groups as approvers. | | `protected_branch_ids` | Array | **{dotted-circle}** No | The IDs of protected branches to scope the rule by. To identify the ID, [use the API](protected_branches.md#list-protected-branches). | +| `remove_hidden_groups` | boolean | **{dotted-circle}** No | Whether hidden groups should be removed. | | `user_ids` | Array | **{dotted-circle}** No | The IDs of users as approvers. | ```json @@ -964,6 +965,7 @@ Supported attributes: | `merge_request_iid` | integer | **{check-circle}** Yes | The IID of a merge request. | | `name` | string | **{check-circle}** Yes | The name of the approval rule. | | `group_ids` | Array | **{dotted-circle}** No | The IDs of groups as approvers. | +| `remove_hidden_groups` | boolean | **{dotted-circle}** No | Whether hidden groups should be removed. | | `user_ids` | Array | **{dotted-circle}** No | The IDs of users as approvers. | ```json diff --git a/doc/api/packages/pypi.md b/doc/api/packages/pypi.md index 4e4c060d99b..e9546f50e36 100644 --- a/doc/api/packages/pypi.md +++ b/doc/api/packages/pypi.md @@ -32,7 +32,7 @@ Download a PyPI package file. The [simple API](#group-level-simple-api-entry-poi normally supplies this URL. ```plaintext -GET groups/:id/packages/pypi/files/:sha256/:file_identifier +GET groups/:id/-/packages/pypi/files/:sha256/:file_identifier ``` | Attribute | Type | Required | Description | @@ -42,13 +42,13 @@ GET groups/:id/packages/pypi/files/:sha256/:file_identifier | `file_identifier` | string | yes | The PyPI package file's name. | ```shell -curl --user <username>:<personal_access_token> "https://gitlab.example.com/api/v4/groups/1/packages/pypi/files/5y57017232013c8ac80647f4ca153k3726f6cba62d055cd747844ed95b3c65ff/my.pypi.package-0.0.1.tar.gz" +curl --user <username>:<personal_access_token> "https://gitlab.example.com/api/v4/groups/1/-/packages/pypi/files/5y57017232013c8ac80647f4ca153k3726f6cba62d055cd747844ed95b3c65ff/my.pypi.package-0.0.1.tar.gz" ``` To write the output to a file: ```shell -curl --user <username>:<personal_access_token> "https://gitlab.example.com/api/v4/groups/1/packages/pypi/files/5y57017232013c8ac80647f4ca153k3726f6cba62d055cd747844ed95b3c65ff/my.pypi.package-0.0.1.tar.gz" >> my.pypi.package-0.0.1.tar.gz +curl --user <username>:<personal_access_token> "https://gitlab.example.com/api/v4/groups/1/-/packages/pypi/files/5y57017232013c8ac80647f4ca153k3726f6cba62d055cd747844ed95b3c65ff/my.pypi.package-0.0.1.tar.gz" >> my.pypi.package-0.0.1.tar.gz ``` This writes the downloaded file to `my.pypi.package-0.0.1.tar.gz` in the current diff --git a/doc/user/search/index.md b/doc/user/search/index.md index 9bff2a91ec8..e27ce075e0a 100644 --- a/doc/user/search/index.md +++ b/doc/user/search/index.md @@ -150,6 +150,7 @@ To delete filter tokens one at a time, the <kbd>⌥</kbd> (Mac) / <kbd>Control</ In the search bar, you can view autocomplete suggestions for: - Projects and groups +- Users - Various help pages (try and type **API help**) - Project feature pages (try and type **milestones**) - Various settings pages (try and type **user settings**) diff --git a/lib/gitlab/ci/config/entry/default.rb b/lib/gitlab/ci/config/entry/default.rb index 12d68b755b3..e996b6b1312 100644 --- a/lib/gitlab/ci/config/entry/default.rb +++ b/lib/gitlab/ci/config/entry/default.rb @@ -13,9 +13,8 @@ module Gitlab include ::Gitlab::Config::Entry::Configurable include ::Gitlab::Config::Entry::Inheritable - ALLOWED_KEYS = %i[before_script image services - after_script cache interruptible - timeout retry tags artifacts].freeze + ALLOWED_KEYS = %i[before_script after_script hooks cache image services + interruptible timeout retry tags artifacts].freeze validations do validates :config, allowed_keys: ALLOWED_KEYS @@ -25,22 +24,27 @@ module Gitlab description: 'Script that will be executed before each job.', inherit: true - entry :image, Entry::Image, - description: 'Docker image that will be used to execute jobs.', - inherit: true - - entry :services, Entry::Services, - description: 'Docker images that will be linked to the container.', - inherit: true - entry :after_script, Entry::Commands, description: 'Script that will be executed after each job.', inherit: true + entry :hooks, Entry::Hooks, + description: 'Commands that will be executed on Runner before/after some events ' \ + 'such as `clone` and `build-script`.', + inherit: false + entry :cache, Entry::Caches, description: 'Configure caching between build jobs.', inherit: true + entry :image, Entry::Image, + description: 'Docker image that will be used to execute jobs.', + inherit: true + + entry :services, Entry::Services, + description: 'Docker images that will be linked to the container.', + inherit: true + entry :interruptible, ::Gitlab::Config::Entry::Boolean, description: 'Set jobs interruptible default value.', inherit: false diff --git a/lib/gitlab/ci/config/entry/hooks.rb b/lib/gitlab/ci/config/entry/hooks.rb index d979dd497b2..28bc2e4e7ce 100644 --- a/lib/gitlab/ci/config/entry/hooks.rb +++ b/lib/gitlab/ci/config/entry/hooks.rb @@ -8,6 +8,8 @@ module Gitlab # `Configurable` alreadys adds `Validatable` include ::Gitlab::Config::Entry::Configurable + # NOTE: If a new hook is added, inheriting should be changed because a `job:hooks` overrides all + # `default:hooks` now. We should implement merging; each hook must be overridden individually. ALLOWED_HOOKS = %i[pre_get_sources_script].freeze validations do diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb index 29335c4679c..7c49b59a7f0 100644 --- a/lib/gitlab/ci/config/entry/job.rb +++ b/lib/gitlab/ci/config/entry/job.rb @@ -61,7 +61,7 @@ module Gitlab entry :hooks, Entry::Hooks, description: 'Commands that will be executed on Runner before/after some events; clone, build-script.', - inherit: false # This will be true in next iterations + inherit: true entry :cache, Entry::Caches, description: 'Cache definition for this job.', diff --git a/lib/gitlab/ci/pipeline/logger.rb b/lib/gitlab/ci/pipeline/logger.rb index 9659cec4889..08e731148c6 100644 --- a/lib/gitlab/ci/pipeline/logger.rb +++ b/lib/gitlab/ci/pipeline/logger.rb @@ -53,6 +53,7 @@ module Gitlab if once observations[operation.to_s] = value else + observations[operation.to_s] ||= [] observations[operation.to_s].push(value) end end @@ -116,13 +117,12 @@ module Gitlab end def enabled? - strong_memoize(:enabled) do - ::Feature.enabled?(:ci_pipeline_creation_logger, project, type: :ops) - end + ::Feature.enabled?(:ci_pipeline_creation_logger, project, type: :ops) end + strong_memoize_attr :enabled?, :enabled def observations - @observations ||= Hash.new { |hash, key| hash[key] = [] } + @observations ||= {} end def observe_sql_counters(operation, start_db_counters, end_db_counters, once: false) diff --git a/lib/gitlab/database_importers/work_items/hierarchy_restrictions_importer.rb b/lib/gitlab/database_importers/work_items/hierarchy_restrictions_importer.rb new file mode 100644 index 00000000000..1181c259a5c --- /dev/null +++ b/lib/gitlab/database_importers/work_items/hierarchy_restrictions_importer.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Gitlab + module DatabaseImporters + module WorkItems + module HierarchyRestrictionsImporter + def self.upsert_restrictions + objective = find_or_create_type(::WorkItems::Type::TYPE_NAMES[:objective]) + key_result = find_or_create_type(::WorkItems::Type::TYPE_NAMES[:key_result]) + issue = find_or_create_type(::WorkItems::Type::TYPE_NAMES[:issue]) + task = find_or_create_type(::WorkItems::Type::TYPE_NAMES[:task]) + incident = find_or_create_type(::WorkItems::Type::TYPE_NAMES[:incident]) + + restrictions = [ + { parent_type_id: objective.id, child_type_id: objective.id, maximum_depth: 9 }, + { parent_type_id: objective.id, child_type_id: key_result.id, maximum_depth: 1 }, + { parent_type_id: issue.id, child_type_id: task.id, maximum_depth: 1 }, + { parent_type_id: incident.id, child_type_id: task.id, maximum_depth: 1 } + ] + + ::WorkItems::HierarchyRestriction.upsert_all( + restrictions, + unique_by: :index_work_item_hierarchy_restrictions_on_parent_and_child + ) + end + + def self.find_or_create_type(name) + type = ::WorkItems::Type.find_by_name_and_namespace_id(name, nil) + return type if type + + Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter.upsert_types + ::WorkItems::Type.find_by_name_and_namespace_id(name, nil) + end + end + end + end +end diff --git a/lib/gitlab/slash_commands/application_help.rb b/lib/gitlab/slash_commands/application_help.rb index bfdb65a816d..94abc8b4508 100644 --- a/lib/gitlab/slash_commands/application_help.rb +++ b/lib/gitlab/slash_commands/application_help.rb @@ -3,6 +3,11 @@ module Gitlab module SlashCommands class ApplicationHelp < BaseCommand + def initialize(project, params) + @project = project + @params = params + end + def execute Gitlab::SlashCommands::Presenters::Help .new(project, commands, params) @@ -16,11 +21,7 @@ module Gitlab end def commands - Gitlab::SlashCommands::Command.new( - project, - chat_name, - params - ).commands + Gitlab::SlashCommands::Command.commands end end end diff --git a/lib/gitlab/slash_commands/command.rb b/lib/gitlab/slash_commands/command.rb index 265eda46489..f8b55f1a91d 100644 --- a/lib/gitlab/slash_commands/command.rb +++ b/lib/gitlab/slash_commands/command.rb @@ -3,7 +3,7 @@ module Gitlab module SlashCommands class Command < BaseCommand - def commands + def self.commands commands = [ Gitlab::SlashCommands::IssueShow, Gitlab::SlashCommands::IssueNew, @@ -15,7 +15,7 @@ module Gitlab Gitlab::SlashCommands::Run ] - if Feature.enabled?(:incident_declare_slash_command, current_user) + if Feature.enabled?(:incident_declare_slash_command) commands << Gitlab::SlashCommands::IncidentManagement::IncidentNew end @@ -50,7 +50,7 @@ module Gitlab private def available_commands - commands.keep_if do |klass| + self.class.commands.keep_if do |klass| klass.available?(project) end end diff --git a/scripts/api/create_issue_discussion.rb b/scripts/api/create_issue_discussion.rb new file mode 100644 index 00000000000..74a9f3ae378 --- /dev/null +++ b/scripts/api/create_issue_discussion.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'gitlab' +require_relative 'default_options' + +class CreateIssueDiscussion + def initialize(options) + @project = options.fetch(:project) + + # Force the token to be a string so that if api_token is nil, it's set to '', + # allowing unauthenticated requests (for forks). + api_token = options.delete(:api_token).to_s + + warn "No API token given." if api_token.empty? + + @client = Gitlab.client( + endpoint: options.delete(:endpoint) || API::DEFAULT_OPTIONS[:endpoint], + private_token: api_token + ) + end + + def execute(discussion_data) + client.post( + "/projects/#{client.url_encode project}/issues/#{discussion_data.delete(:issue_iid)}/discussions", + body: discussion_data + ) + end + + private + + attr_reader :project, :client +end diff --git a/scripts/create-pipeline-failure-incident.rb b/scripts/create-pipeline-failure-incident.rb index c38f80699e6..bbabfb7dfce 100755 --- a/scripts/create-pipeline-failure-incident.rb +++ b/scripts/create-pipeline-failure-incident.rb @@ -7,6 +7,7 @@ require 'json' require_relative 'api/pipeline_failed_jobs' require_relative 'api/create_issue' +require_relative 'api/create_issue_discussion' class CreatePipelineFailureIncident DEFAULT_OPTIONS = { @@ -28,7 +29,12 @@ class CreatePipelineFailureIncident labels: incident_labels } - CreateIssue.new(project: project, api_token: api_token).execute(payload) + CreateIssue.new(project: project, api_token: api_token).execute(payload).tap do |incident| + CreateIssueDiscussion.new(project: project, api_token: api_token) + .execute(issue_iid: incident.iid, body: "## Root Cause Analysis") + CreateIssueDiscussion.new(project: project, api_token: api_token) + .execute(issue_iid: incident.iid, body: "## Investigation Steps") + end end private @@ -44,8 +50,16 @@ class CreatePipelineFailureIncident end def title - "#{now.strftime('%A %F %R UTC')} - `#{ENV['CI_PROJECT_PATH']}` broken `#{ENV['CI_COMMIT_REF_NAME']}` " \ - "with #{failed_jobs.size} failed jobs" + @title ||= begin + full_title = "#{now.strftime('%A %F %R UTC')} - `#{ENV['CI_PROJECT_PATH']}` " \ + "broken `#{ENV['CI_COMMIT_REF_NAME']}` with #{failed_jobs.map(&:name).join(', ')}" + + if full_title.size >= 255 + "#{full_title[...252]}..." # max title length is 255, and we add an elipsis + else + full_title + end + end end def description diff --git a/scripts/review_apps/k8s-resources-count-checks.sh b/scripts/review_apps/k8s-resources-count-checks.sh index ae4c8e163e5..b63fa043065 100755 --- a/scripts/review_apps/k8s-resources-count-checks.sh +++ b/scripts/review_apps/k8s-resources-count-checks.sh @@ -56,8 +56,6 @@ cat > k8s-resources-count.out <<COMMANDS $(k8s_resource_count daemonsets.apps) daemonsets.apps $(k8s_resource_count deployments.apps) deployments.apps $(k8s_resource_count endpoints) endpoints - $(k8s_resource_count endpointslices.discovery.k8s.io) endpointslices.discovery.k8s.io - $(k8s_resource_count events) events $(k8s_resource_count frontendconfigs.networking.gke.io) frontendconfigs.networking.gke.io $(k8s_resource_count horizontalpodautoscalers.autoscaling) horizontalpodautoscalers.autoscaling $(k8s_resource_count ingressclasses) ingressclasses @@ -71,7 +69,6 @@ cat > k8s-resources-count.out <<COMMANDS $(k8s_resource_count orders.acme.cert-manager.io) orders.acme.cert-manager.io $(k8s_resource_count persistentvolumeclaims) persistentvolumeclaims $(k8s_resource_count poddisruptionbudgets.policy) poddisruptionbudgets.policy - $(k8s_resource_count pods.metrics.k8s.io) pods.metrics.k8s.io $(k8s_resource_count pods) pods $(k8s_resource_count podtemplates) podtemplates $(k8s_resource_count replicasets.apps) replicasets.apps diff --git a/scripts/used-feature-flags b/scripts/used-feature-flags index eb7e85be229..74180d02a91 100755 --- a/scripts/used-feature-flags +++ b/scripts/used-feature-flags @@ -114,6 +114,9 @@ if unused_flags.count > 0 puts puts "If they are really no longer needed REMOVE their .yml definition".red puts "If they are needed you need to ENSURE that their usage is covered with specs to continue.".red + puts "Feature flag usage is detected via Rubocop, which is unable to resolve dynamic feature flag usage,".red.bold + puts "interpolated strings however are optimistically matched. For more details consult test suite:".red + puts "https://gitlab.com/gitlab-org/gitlab/-/blob/69cb5d36db95881b495966c95655672cfb816f62/spec/rubocop/cop/gitlab/mark_used_feature_flags_spec.rb".red puts unused_flags.keys.sort.each do |name| puts "- #{name}".yellow diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index 21df53fb074..37fc5a033ba 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -421,6 +421,12 @@ RSpec.describe SearchController do expect(json_response.count).to eq(1) expect(json_response.first['label']).to match(/User settings/) end + + it 'makes a call to search_autocomplete_opts' do + expect(controller).to receive(:search_autocomplete_opts).once + + get :autocomplete, params: { term: 'setting', filter: 'generic' } + end end describe '#append_info_to_payload' do diff --git a/spec/db/development/create_work_item_hierarchy_restrictions_spec.rb b/spec/db/development/create_work_item_hierarchy_restrictions_spec.rb new file mode 100644 index 00000000000..0e60ecd08c0 --- /dev/null +++ b/spec/db/development/create_work_item_hierarchy_restrictions_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Create work item hierarchy restrictions in development', feature_category: :portfolio_management do + subject { load Rails.root.join('db/fixtures/development/50_create_work_item_hierarchy_restrictions.rb') } + + it_behaves_like 'work item hierarchy restrictions importer' +end diff --git a/spec/db/production/create_work_item_hierarchy_restrictions_spec.rb b/spec/db/production/create_work_item_hierarchy_restrictions_spec.rb new file mode 100644 index 00000000000..5b47d88d71a --- /dev/null +++ b/spec/db/production/create_work_item_hierarchy_restrictions_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Create work item hierarchy restrictions in production', feature_category: :portfolio_management do + subject { load Rails.root.join('db/fixtures/production/020_create_work_item_hierarchy_restrictions.rb') } + + it_behaves_like 'work item hierarchy restrictions importer' +end diff --git a/spec/finders/users_finder_spec.rb b/spec/finders/users_finder_spec.rb index 271dce44db7..5cf845a87b2 100644 --- a/spec/finders/users_finder_spec.rb +++ b/spec/finders/users_finder_spec.rb @@ -8,9 +8,7 @@ RSpec.describe UsersFinder do let_it_be(:project_bot) { create(:user, :project_bot) } - context 'with a normal user' do - let_it_be(:user) { create(:user) } - + shared_examples 'executes users finder as normal user' do it 'returns searchable users' do users = described_class.new(user).execute @@ -97,37 +95,35 @@ RSpec.describe UsersFinder do end end - context 'with an admin user', :enable_admin_mode do - let_it_be(:admin) { create(:admin) } - + shared_examples 'executes users finder as admin' do it 'filters by external users' do - users = described_class.new(admin, external: true).execute + users = described_class.new(user, external: true).execute expect(users).to contain_exactly(external_user) end it 'returns all users' do - users = described_class.new(admin).execute + users = described_class.new(user).execute - expect(users).to contain_exactly(admin, normal_user, blocked_user, unconfirmed_user, banned_user, external_user, omniauth_user, internal_user, admin_user, project_bot) + expect(users).to contain_exactly(user, normal_user, blocked_user, unconfirmed_user, banned_user, external_user, omniauth_user, internal_user, admin_user, project_bot) end it 'filters by blocked users' do - users = described_class.new(admin, blocked: true).execute + users = described_class.new(user, blocked: true).execute expect(users).to contain_exactly(blocked_user) end it 'filters by active users' do - users = described_class.new(admin, active: true).execute + users = described_class.new(user, active: true).execute - expect(users).to contain_exactly(admin, normal_user, unconfirmed_user, external_user, omniauth_user, admin_user, project_bot) + expect(users).to contain_exactly(user, normal_user, unconfirmed_user, external_user, omniauth_user, admin_user, project_bot) end it 'returns only admins' do - users = described_class.new(admin, admins: true).execute + users = described_class.new(user, admins: true).execute - expect(users).to contain_exactly(admin, admin_user) + expect(users).to contain_exactly(user, admin_user) end it 'filters by custom attributes' do @@ -137,7 +133,7 @@ RSpec.describe UsersFinder do create :user_custom_attribute, user: internal_user, key: 'foo', value: 'foo' users = described_class.new( - admin, + user, custom_attributes: { foo: 'foo', bar: 'bar' } ).execute @@ -145,10 +141,34 @@ RSpec.describe UsersFinder do end it 'filters by private emails search' do - users = described_class.new(admin, search: normal_user.email).execute + users = described_class.new(user, search: normal_user.email).execute expect(users).to contain_exactly(normal_user) end end + + context 'with a normal user' do + let_it_be(:user) { create(:user) } + + it_behaves_like 'executes users finder as normal user' + end + + context 'with an admin user' do + let_it_be(:user) { create(:admin) } + + context 'when admin mode setting is disabled', :do_not_mock_admin_mode_setting do + it_behaves_like 'executes users finder as admin' + end + + context 'when admin mode setting is enabled' do + context 'when in admin mode', :enable_admin_mode do + it_behaves_like 'executes users finder as admin' + end + + context 'when not in admin mode' do + it_behaves_like 'executes users finder as normal user' + end + end + end end end diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index 192dfaa9caf..74a59aa37ce 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -60,6 +60,34 @@ RSpec.describe SearchHelper do expect(search_autocomplete_opts(project.name).size).to eq(1) end + context 'for users' do + let_it_be(:another_user) { create(:user, name: 'Jane Doe') } + let(:term) { 'jane' } + + it 'makes a call to SearchService' do + expect(SearchService).to receive(:new).with(current_user, { search: term, scope: 'users' }).and_call_original + + search_autocomplete_opts(term) + end + + it 'returns users matching the term' do + result = search_autocomplete_opts(term) + expect(result.size).to eq(1) + expect(result.first[:id]).to eq(another_user.id) + end + + context 'when current_user cannot read_users_list' do + before do + allow(Ability).to receive(:allowed?).and_return(true) + allow(Ability).to receive(:allowed?).with(current_user, :read_users_list).and_return(false) + end + + it 'returns an empty array' do + expect(search_autocomplete_opts(term)).to eq([]) + end + end + end + it "includes the required project attrs" do project = create(:project, namespace: create(:namespace, owner: user)) result = search_autocomplete_opts(project.name).first diff --git a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb index 8da46561b73..736c184a289 100644 --- a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb @@ -13,7 +13,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do # that we know that we don't want to inherit # as they do not have sense in context of Bridge let(:ignored_inheritable_columns) do - %i[before_script after_script image services cache interruptible timeout + %i[before_script after_script hooks image services cache interruptible timeout retry tags artifacts] end end diff --git a/spec/lib/gitlab/ci/config/entry/default_spec.rb b/spec/lib/gitlab/ci/config/entry/default_spec.rb index 5613b0f09d1..46e96843ee3 100644 --- a/spec/lib/gitlab/ci/config/entry/default_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/default_spec.rb @@ -26,9 +26,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Default do context 'when filtering all the entry/node names' do it 'contains the expected node names' do expect(described_class.nodes.keys) - .to match_array(%i[before_script image services - after_script cache interruptible - timeout retry tags artifacts]) + .to match_array(%i[before_script after_script hooks cache image services + interruptible timeout retry tags artifacts]) end end end diff --git a/spec/lib/gitlab/ci/pipeline/logger_spec.rb b/spec/lib/gitlab/ci/pipeline/logger_spec.rb index 4492ca716ae..60cc3486414 100644 --- a/spec/lib/gitlab/ci/pipeline/logger_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/logger_spec.rb @@ -213,6 +213,21 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do expect(commit).to be_truthy end + + context 'with unexistent observations in condition' do + it 'does not commit the log' do + logger.log_when do |observations| + value = observations['non_existent_value'] + next false unless value + + value > 0 + end + + expect(Gitlab::AppJsonLogger).not_to receive(:info) + + expect(commit).to be_falsey + end + end end context 'when project is not passed and pipeline is not persisted' do diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index fee3731c662..0d1deb863b1 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -901,6 +901,37 @@ module Gitlab ) end end + + context 'when receiving from the default' do + let(:config) do + { + default: { hooks: { pre_get_sources_script: ["echo 1", "echo 2", "pwd"] } }, + test: { script: ["script"] } + } + end + + it "inherits hooks" do + expect(subject[:options][:hooks]).to eq( + { pre_get_sources_script: ["echo 1", "echo 2", "pwd"] } + ) + end + end + + context 'when overriding the default' do + let(:config) do + { + default: { hooks: { pre_get_sources_script: ["echo 1", "echo 2", "pwd"] } }, + test: { script: ["script"], + hooks: { pre_get_sources_script: ["echo 3", "echo 4", "pwd"] } } + } + end + + it "overrides hooks" do + expect(subject[:options][:hooks]).to eq( + { pre_get_sources_script: ["echo 3", "echo 4", "pwd"] } + ) + end + end end end diff --git a/spec/lib/gitlab/database_importers/work_items/hierarchy_restrictions_importer_spec.rb b/spec/lib/gitlab/database_importers/work_items/hierarchy_restrictions_importer_spec.rb new file mode 100644 index 00000000000..d8173794b3f --- /dev/null +++ b/spec/lib/gitlab/database_importers/work_items/hierarchy_restrictions_importer_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::DatabaseImporters::WorkItems::HierarchyRestrictionsImporter, + feature_category: :portfolio_management do + subject { described_class.upsert_restrictions } + + it_behaves_like 'work item hierarchy restrictions importer' +end diff --git a/spec/lib/gitlab/slash_commands/application_help_spec.rb b/spec/lib/gitlab/slash_commands/application_help_spec.rb index b182c0e5cc6..d0cefdf4895 100644 --- a/spec/lib/gitlab/slash_commands/application_help_spec.rb +++ b/spec/lib/gitlab/slash_commands/application_help_spec.rb @@ -4,13 +4,11 @@ require 'spec_helper' RSpec.describe Gitlab::SlashCommands::ApplicationHelp do let(:params) { { command: '/gitlab', text: 'help' } } - let_it_be(:user) { create(:user) } - let_it_be(:chat_user) { create(:chat_name, user: user) } let(:project) { build(:project) } describe '#execute' do subject do - described_class.new(project, chat_user, params).execute + described_class.new(project, params).execute end it 'displays the help section' do diff --git a/spec/migrations/add_okr_hierarchy_restrictions_spec.rb b/spec/migrations/add_okr_hierarchy_restrictions_spec.rb new file mode 100644 index 00000000000..9923795925d --- /dev/null +++ b/spec/migrations/add_okr_hierarchy_restrictions_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe AddOkrHierarchyRestrictions, :migration, feature_category: :portfolio_management do + include MigrationHelpers::WorkItemTypesHelper + + let_it_be(:restrictions) { table(:work_item_hierarchy_restrictions) } + let_it_be(:work_item_types) { table(:work_item_types) } + + it 'creates default restrictions' do + restrictions.delete_all + + reversible_migration do |migration| + migration.before -> { + expect(restrictions.count).to eq(0) + } + + migration.after -> { + expect(restrictions.count).to eq(4) + } + end + end + + context 'when work items are missing' do + before do + work_item_types.delete_all + end + + it 'does nothing' do + expect { migrate! }.not_to change { restrictions.count } + end + end +end diff --git a/spec/models/work_items/type_spec.rb b/spec/models/work_items/type_spec.rb index 6685720778a..1d8c5e79bf2 100644 --- a/spec/models/work_items/type_spec.rb +++ b/spec/models/work_items/type_spec.rb @@ -75,6 +75,32 @@ RSpec.describe WorkItems::Type do end end + describe '.default_by_type' do + let(:default_issue_type) { described_class.find_by(namespace_id: nil, base_type: :issue) } + + subject { described_class.default_by_type(:issue) } + + it 'returns default work item type by base type without calling importer' do + expect(Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter).not_to receive(:upsert_types) + expect(Gitlab::DatabaseImporters::WorkItems::HierarchyRestrictionsImporter).not_to receive(:upsert_restrictions) + + expect(subject).to eq(default_issue_type) + end + + context 'when default types are missing' do + before do + described_class.delete_all + end + + it 'creates types and restrictions and returns default work item type by base type' do + expect(Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter).to receive(:upsert_types) + expect(Gitlab::DatabaseImporters::WorkItems::HierarchyRestrictionsImporter).to receive(:upsert_restrictions) + + expect(subject).to eq(default_issue_type) + end + end + end + describe '#default?' do subject { build(:work_item_type, namespace: namespace).default? } diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index c072e6d48db..a3fa85dee73 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -1031,7 +1031,7 @@ RSpec.describe 'Git LFS API and storage' do end describe 'to a forked project' do - let_it_be(:upstream_project) { create(:project, :public) } + let_it_be_with_reload(:upstream_project) { create(:project, :public) } let_it_be(:project_owner) { create(:user) } let(:project) { fork_project(upstream_project, project_owner) } @@ -1069,6 +1069,56 @@ RSpec.describe 'Git LFS API and storage' do end end + describe 'when user has push access to upstream project' do + before do + upstream_project.add_maintainer(user) + end + + context 'an MR exists on target forked project' do + let(:allow_collaboration) { true } + let(:merge_request) do + create(:merge_request, + target_project: upstream_project, + source_project: project, + allow_collaboration: allow_collaboration) + end + + before do + merge_request + end + + context 'with allow_collaboration option set to true' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + put_authorize + end + + it_behaves_like 'LFS http 200 workhorse response' + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + put_finalize + end + + it_behaves_like 'LFS http 200 response' + end + end + + context 'with allow_collaboration option set to false' do + context 'request is sent by gitlab-workhorse to authorize the request' do + let(:allow_collaboration) { false } + + before do + put_authorize + end + + it_behaves_like 'forbidden' + end + end + end + end + describe 'and user does not have push access' do it_behaves_like 'forbidden' end diff --git a/spec/services/ci/create_pipeline_service/scripts_spec.rb b/spec/services/ci/create_pipeline_service/scripts_spec.rb index 493e341395b..50b558e505a 100644 --- a/spec/services/ci/create_pipeline_service/scripts_spec.rb +++ b/spec/services/ci/create_pipeline_service/scripts_spec.rb @@ -39,24 +39,74 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes end end - context 'when job has hooks' do + context 'when job has hooks and default hooks' do let(:config) do <<-CI_CONFIG - job: + default: hooks: - pre_get_sources_script: echo 'hello job pre_get_sources_script' - script: echo 'hello job script' + pre_get_sources_script: + - echo 'hello default pre_get_sources_script' + + job1: + hooks: + pre_get_sources_script: + - echo 'hello job1 pre_get_sources_script' + script: echo 'hello job1 script' + + job2: + script: echo 'hello job2 script' + + job3: + inherit: + default: false + script: echo 'hello job3 script' CI_CONFIG end - it 'creates a job with script data' do + it 'creates jobs with hook data' do expect(pipeline).to be_created_successfully - expect(pipeline.builds.first).to have_attributes( - name: 'job', + expect(pipeline.builds.find_by(name: 'job1')).to have_attributes( + name: 'job1', stage: 'test', - options: { script: ["echo 'hello job script'"], - hooks: { pre_get_sources_script: ["echo 'hello job pre_get_sources_script'"] } } + options: { script: ["echo 'hello job1 script'"], + hooks: { pre_get_sources_script: ["echo 'hello job1 pre_get_sources_script'"] } } ) + expect(pipeline.builds.find_by(name: 'job2')).to have_attributes( + name: 'job2', + stage: 'test', + options: { script: ["echo 'hello job2 script'"], + hooks: { pre_get_sources_script: ["echo 'hello default pre_get_sources_script'"] } } + ) + expect(pipeline.builds.find_by(name: 'job3')).to have_attributes( + name: 'job3', + stage: 'test', + options: { script: ["echo 'hello job3 script'"] } + ) + end + + context 'when the FF ci_hooks_pre_get_sources_script is disabled' do + before do + stub_feature_flags(ci_hooks_pre_get_sources_script: false) + end + + it 'creates jobs without hook data' do + expect(pipeline).to be_created_successfully + expect(pipeline.builds.find_by(name: 'job1')).to have_attributes( + name: 'job1', + stage: 'test', + options: { script: ["echo 'hello job1 script'"] } + ) + expect(pipeline.builds.find_by(name: 'job2')).to have_attributes( + name: 'job2', + stage: 'test', + options: { script: ["echo 'hello job2 script'"] } + ) + expect(pipeline.builds.find_by(name: 'job3')).to have_attributes( + name: 'job3', + stage: 'test', + options: { script: ["echo 'hello job3 script'"] } + ) + end end end end diff --git a/spec/support/shared_examples/work_item_hierarchy_restrictions_importer.rb b/spec/support/shared_examples/work_item_hierarchy_restrictions_importer.rb new file mode 100644 index 00000000000..a1bccb7b7a3 --- /dev/null +++ b/spec/support/shared_examples/work_item_hierarchy_restrictions_importer.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'work item hierarchy restrictions importer' do + shared_examples_for 'adds restrictions' do + it "adds all restrictions if they don't exist" do + expect { subject }.to change { WorkItems::HierarchyRestriction.count }.from(0).to(4) + end + end + + it_behaves_like 'adds restrictions' + + context 'when base types are missing' do + before do + WorkItems::Type.delete_all + end + + it_behaves_like 'adds restrictions' + end + + context 'when restrictions already exist' do + before do + Gitlab::DatabaseImporters::WorkItems::HierarchyRestrictionsImporter.upsert_restrictions + end + + it 'upserts restrictions' do + restriction = WorkItems::HierarchyRestriction.first + depth = restriction.maximum_depth + + restriction.update!(maximum_depth: depth + 1) + + expect do + subject + restriction.reload + end.to not_change { WorkItems::HierarchyRestriction.count }.and( + change { restriction.maximum_depth }.from(depth + 1).to(depth) + ) + end + end + + context 'when some restrictions are missing' do + before do + Gitlab::DatabaseImporters::WorkItems::HierarchyRestrictionsImporter.upsert_restrictions + WorkItems::HierarchyRestriction.limit(1).delete_all + end + + it 'inserts missing restrictions and does nothing if some already existed' do + expect { subject }.to make_queries_matching(/INSERT/, 1).and( + change { WorkItems::HierarchyRestriction.count }.by(1) + ) + expect(WorkItems::HierarchyRestriction.count).to eq(4) + end + end +end diff --git a/tooling/config/CODEOWNERS.yml b/tooling/config/CODEOWNERS.yml index d729ae5d532..07fddde056c 100644 --- a/tooling/config/CODEOWNERS.yml +++ b/tooling/config/CODEOWNERS.yml @@ -60,6 +60,7 @@ - 'config/audit_events/' - 'runner_token_expiration/' - '*metadata_id_tokens*' + - '/app/assets/javascripts/invite_members/' patterns: - '%{keyword}' |