diff --git a/.eslint_todo/vue-no-unused-properties.mjs b/.eslint_todo/vue-no-unused-properties.mjs index 7419aafde70..6a0bf5f0f06 100644 --- a/.eslint_todo/vue-no-unused-properties.mjs +++ b/.eslint_todo/vue-no-unused-properties.mjs @@ -61,18 +61,6 @@ export default { 'app/assets/javascripts/ci/pipelines_page/components/pipelines_artifacts.vue', 'app/assets/javascripts/ci/pipelines_page/pipelines.vue', 'app/assets/javascripts/ci/reports/components/report_section.vue', - 'app/assets/javascripts/ci/runner/admin_runner_show/admin_runner_show_app.vue', - 'app/assets/javascripts/ci/runner/components/cells/runner_summary_cell.vue', - 'app/assets/javascripts/ci/runner/components/registration/registration_dropdown.vue', - 'app/assets/javascripts/ci/runner/components/registration/runner_instructions/runner_instructions_modal.vue', - 'app/assets/javascripts/ci/runner/components/runner_delete_button.vue', - 'app/assets/javascripts/ci/runner/components/runner_delete_modal.vue', - 'app/assets/javascripts/ci/runner/components/runner_pause_action.vue', - 'app/assets/javascripts/ci/runner/components/runner_type_tabs.vue', - 'app/assets/javascripts/ci/runner/components/stat/runner_count.vue', - 'app/assets/javascripts/ci/runner/group_new_runner/group_new_runner_app.vue', - 'app/assets/javascripts/ci/runner/group_runner_show/group_runner_show_app.vue', - 'app/assets/javascripts/ci/runner/project_new_runner/project_new_runner_app.vue', 'app/assets/javascripts/clusters_list/components/agents.vue', 'app/assets/javascripts/clusters_list/components/delete_agent_button.vue', 'app/assets/javascripts/clusters_list/components/install_agent_modal.vue', diff --git a/.rubocop_todo/gitlab/bounded_contexts.yml b/.rubocop_todo/gitlab/bounded_contexts.yml index e0bd78dca46..215141b8d5b 100644 --- a/.rubocop_todo/gitlab/bounded_contexts.yml +++ b/.rubocop_todo/gitlab/bounded_contexts.yml @@ -3240,7 +3240,6 @@ Gitlab/BoundedContexts: - 'ee/app/services/groups/enterprise_users/base_service.rb' - 'ee/app/services/groups/enterprise_users/disassociate_service.rb' - 'ee/app/services/groups/epics_count_service.rb' - - 'ee/app/services/groups/mark_for_deletion_service.rb' - 'ee/app/services/groups/restore_service.rb' - 'ee/app/services/groups/schedule_bulk_repository_shard_moves_service.rb' - 'ee/app/services/groups/seat_usage_export_service.rb' diff --git a/.rubocop_todo/gitlab/feature_available_usage.yml b/.rubocop_todo/gitlab/feature_available_usage.yml index 38c54bf4193..b860dfe440f 100644 --- a/.rubocop_todo/gitlab/feature_available_usage.yml +++ b/.rubocop_todo/gitlab/feature_available_usage.yml @@ -82,7 +82,6 @@ Gitlab/FeatureAvailableUsage: - 'ee/app/services/iterations/create_service.rb' - 'ee/app/services/iterations/update_service.rb' - 'ee/app/services/merge_requests/update_blocks_service.rb' - - 'ee/app/services/projects/mark_for_deletion_service.rb' - 'ee/app/services/requirements_management/process_test_reports_service.rb' - 'ee/app/services/security/store_scans_service.rb' - 'ee/app/workers/analytics/code_review_metrics_worker.rb' diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml index 9d3605f51a2..65b8dbb2be1 100644 --- a/.rubocop_todo/layout/line_length.yml +++ b/.rubocop_todo/layout/line_length.yml @@ -14,7 +14,6 @@ Layout/LineLength: - 'app/controllers/projects/settings/ci_cd_controller.rb' - 'app/controllers/projects/settings/repository_controller.rb' - 'app/controllers/projects/templates_controller.rb' - - 'app/controllers/projects/triggers_controller.rb' - 'app/controllers/projects/web_ide_schemas_controller.rb' - 'app/controllers/projects_controller.rb' - 'app/controllers/registrations_controller.rb' @@ -718,7 +717,6 @@ Layout/LineLength: - 'ee/app/services/jira/requests/issues/list_service.rb' - 'ee/app/services/merge_requests/create_from_vulnerability_data_service.rb' - 'ee/app/services/merge_trains/refresh_merge_request_service.rb' - - 'ee/app/services/projects/mark_for_deletion_service.rb' - 'ee/app/services/projects/update_mirror_service.rb' - 'ee/app/services/security/ingestion/finding_map.rb' - 'ee/app/services/security/ingestion/tasks/ingest_remediations.rb' diff --git a/.rubocop_todo/style/if_unless_modifier.yml b/.rubocop_todo/style/if_unless_modifier.yml index 17ea871c0fa..58bc09eaa36 100644 --- a/.rubocop_todo/style/if_unless_modifier.yml +++ b/.rubocop_todo/style/if_unless_modifier.yml @@ -313,7 +313,6 @@ Style/IfUnlessModifier: - 'ee/app/services/iterations/delete_service.rb' - 'ee/app/services/merge_requests/update_blocks_service.rb' - 'ee/app/services/merge_trains/refresh_merge_request_service.rb' - - 'ee/app/services/projects/mark_for_deletion_service.rb' - 'ee/app/services/projects/update_mirror_service.rb' - 'ee/app/services/security/security_orchestration_policies/policy_configuration_validation_service.rb' - 'ee/app/services/security/security_orchestration_policies/process_policy_service.rb' diff --git a/app/assets/javascripts/ci/runner/admin_runner_show/admin_runner_show_app.vue b/app/assets/javascripts/ci/runner/admin_runner_show/admin_runner_show_app.vue index d63d1fcf7aa..b8eaca6d8b0 100644 --- a/app/assets/javascripts/ci/runner/admin_runner_show/admin_runner_show_app.vue +++ b/app/assets/javascripts/ci/runner/admin_runner_show/admin_runner_show_app.vue @@ -50,14 +50,6 @@ export default { }, }, }, - computed: { - canUpdate() { - return this.runner.userPermissions?.updateRunner; - }, - canDelete() { - return this.runner.userPermissions?.deleteRunner; - }, - }, methods: { reportToSentry(error) { captureException({ error, component: this.$options.name }); diff --git a/app/assets/javascripts/ci/runner/components/cells/runner_summary_cell.vue b/app/assets/javascripts/ci/runner/components/cells/runner_summary_cell.vue index a13f7919a33..c42e04f89e3 100644 --- a/app/assets/javascripts/ci/runner/components/cells/runner_summary_cell.vue +++ b/app/assets/javascripts/ci/runner/components/cells/runner_summary_cell.vue @@ -1,6 +1,6 @@ diff --git a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue index 09ae9a38cdf..aeeb2654836 100644 --- a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue +++ b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue @@ -7,6 +7,7 @@ import { GlButton } from '@gitlab/ui'; import { createAlert } from '~/alert'; import { TYPE_ISSUE } from '~/issues/constants'; import { __ } from '~/locale'; +import { InternalEvents } from '~/tracking'; import { isGid, getIdFromGraphQLId } from '~/graphql_shared/utils'; import { fetchUserCounts } from '~/super_sidebar/user_counts_fetch'; import ReviewerDrawer from '~/merge_requests/components/reviewers/reviewer_drawer.vue'; @@ -35,6 +36,7 @@ export default { ApprovalSummary: () => import('ee_component/merge_requests/components/reviewers/approval_summary.vue'), }, + mixins: [InternalEvents.mixin()], props: { mediator: { type: Object, @@ -191,6 +193,10 @@ export default { }, toggleDrawerOpen(drawerOpen = !this.drawerOpen) { this.drawerOpen = drawerOpen; + + if (drawerOpen) { + this.trackEvent('open_reviewer_sidebar_panel_in_mr'); + } }, }, }; @@ -213,7 +219,7 @@ export default { category="tertiary" variant="confirm" class="gl-ml-2 !gl-text-sm" - data-testid="sidebar-reviewers-assign-buton" + data-testid="sidebar-reviewers-assign-button" @click="toggleDrawerOpen()" > {{ __('Assign') }} diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index 6a984542355..c2c2c2c9dc2 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -117,10 +117,10 @@ export default { return { tag: '> ', suggestPopoverVisible: false, - shouldShowFindAndReplaceBar: false, findAndReplace: { find: '', replace: '', + shouldShowBar: false, }, modifierKey, shiftKey: modifierKey === '⌘' ? '⇧' : 'Shift+', @@ -181,14 +181,14 @@ export default { mounted() { $(document).on('markdown-preview:show.vue', this.showMarkdownPreview); $(document).on('markdown-preview:hide.vue', this.hideMarkdownPreview); - $(document).on('markdown-editor:find-and-replace:show', this.showFindAndReplaceBar); + $(document).on('markdown-editor:find-and-replace:show', this.findAndReplace_show); this.updateSuggestPopoverVisibility(); }, beforeDestroy() { $(document).off('markdown-preview:show.vue', this.showMarkdownPreview); $(document).off('markdown-preview:hide.vue', this.hideMarkdownPreview); - $(document).off('markdown-editor:find-and-replace:show', this.showFindAndReplaceBar); + $(document).off('markdown-editor:find-and-replace:show', this.findAndReplace_show); }, methods: { async updateSuggestPopoverVisibility() { @@ -233,8 +233,12 @@ export default { }) .catch(() => {}); }, + getCurrentTextArea() { + return this.$el.closest('.md-area')?.querySelector('textarea'); + }, insertIntoTextarea(text) { - const textArea = this.$el.closest('.md-area')?.querySelector('textarea'); + const textArea = this.getCurrentTextArea(); + if (textArea) { updateText({ textArea, @@ -289,20 +293,112 @@ export default { this.$el.closest('.md-area')?.querySelector('textarea')?.focus(); }, 500); }, - showFindAndReplaceBar(_, form) { + findAndReplace_show(_, form) { if (!this.isValid(form)) return; - this.shouldShowFindAndReplaceBar = true; + this.findAndReplace.shouldShowBar = true; }, - handleKeyDown(e) { + findAndReplace_handleKeyDown(e) { if (e.key === 'Enter') { e.preventDefault(); + } else if (e.key === 'Escape') { + this.findAndReplace.shouldShowBar = false; + this.getCurrentTextArea()?.removeEventListener('scroll', this.findAndReplace_syncScroll); + this.cloneDiv?.parentElement.removeChild(this.cloneDiv); + this.cloneDiv = undefined; + } + }, + findAndReplace_handleKeyUp(e) { + this.findAndReplace_highlightMatchingText(e.target.value); + }, + findAndReplace_syncScroll() { + const textArea = this.getCurrentTextArea(); + this.cloneDiv.scrollTop = textArea.scrollTop; + }, + findAndReplace_safeReplace(textArea, textToFind) { + const regex = new RegExp(`(${textToFind})`, 'g'); + const segments = textArea.value.split(regex); + + // Clear previous contents + this.cloneDiv.innerHTML = ''; + + segments.forEach((segment) => { + // If the segment matches the text we're highlighting + if (segment === textToFind) { + const span = document.createElement('span'); + span.classList.add('js-highlight'); + span.style.backgroundColor = 'orange'; + span.style.display = 'inline-block'; + span.textContent = segment; // Use textContent for safe text insertion + this.cloneDiv.appendChild(span); + } else { + // Otherwise, just append the plain text + const textNode = document.createTextNode(segment); + this.cloneDiv.appendChild(textNode); + } + }); + }, + async findAndReplace_highlightMatchingText(text) { + const textArea = this.getCurrentTextArea(); + + if (!textArea) { return; } - if (e.key === 'Escape') { - this.shouldShowFindAndReplaceBar = false; + // Make sure we got the right zIndex + textArea.style.position = 'relative'; + textArea.style.zIndex = 2; + + await this.findAndReplace_attachCloneDivIfNotExists(textArea); + + this.findAndReplace_safeReplace(textArea, text); + }, + async findAndReplace_attachCloneDivIfNotExists(textArea) { + if (this.cloneDiv) { + return; } + + this.cloneDiv = document.createElement('div'); + this.cloneDiv.dataset.testid = 'find-and-replace-clone'; + this.cloneDiv.textContent = textArea.value; + + const computedStyle = window.getComputedStyle(textArea); + const propsToCopy = [ + 'width', + 'height', + 'padding', + 'border', + 'font-family', + 'font-size', + 'line-height', + 'background-color', + 'color', + 'overflow', + 'white-space', + 'word-wrap', + 'resize', + 'margin', + ]; + + propsToCopy.forEach((prop) => { + this.cloneDiv.style[prop] = computedStyle[prop]; + }); + + // Additional required styles for div + this.cloneDiv.style.whiteSpace = 'pre-wrap'; + this.cloneDiv.style.overflowY = 'auto'; + this.cloneDiv.style.position = 'absolute'; + this.cloneDiv.style.zIndex = 1; + this.cloneDiv.style.color = 'transparent'; + + textArea.addEventListener('scroll', this.findAndReplace_syncScroll); + + textArea.parentElement.insertBefore(this.cloneDiv, textArea); + + await this.$nextTick(); + + // Required to align the clone div + this.cloneDiv.scrollTop = textArea.scrollTop; }, }, shortcuts: { @@ -620,8 +716,8 @@ export default {
diff --git a/app/controllers/admin/impersonation_tokens_controller.rb b/app/controllers/admin/impersonation_tokens_controller.rb index 0ba04a3416a..4e22f0d6cce 100644 --- a/app/controllers/admin/impersonation_tokens_controller.rb +++ b/app/controllers/admin/impersonation_tokens_controller.rb @@ -73,7 +73,7 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController end def active_impersonation_tokens - tokens = finder(state: 'active', sort: 'expires_at_asc_id_desc').execute + tokens = finder(state: 'active', sort: 'expires_asc').execute ::ImpersonationAccessTokenSerializer.new.represent(tokens) end diff --git a/app/controllers/concerns/render_access_tokens.rb b/app/controllers/concerns/render_access_tokens.rb index 056e81c7f99..e82ec877035 100644 --- a/app/controllers/concerns/render_access_tokens.rb +++ b/app/controllers/concerns/render_access_tokens.rb @@ -4,7 +4,7 @@ module RenderAccessTokens extend ActiveSupport::Concern def active_access_tokens - tokens = finder(state: 'active', sort: 'expires_at_asc_id_desc').execute.preload_users + tokens = finder(state: 'active', sort: 'expires_asc').execute.preload_users size = tokens.size tokens = tokens.page(page) diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb index 5ec0db457f9..277c260cb19 100644 --- a/app/controllers/projects/triggers_controller.rb +++ b/app/controllers/projects/triggers_controller.rb @@ -36,10 +36,12 @@ class Projects::TriggersController < Projects::ApplicationController end def update - response = ::Ci::PipelineTriggers::UpdateService.new(user: current_user, trigger: trigger, description: trigger_params[:description]).execute + response = ::Ci::PipelineTriggers::UpdateService.new(user: current_user, trigger: trigger, + description: trigger_params[:description]).execute if response.success? - redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers'), notice: _('Trigger token was successfully updated.') + redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers'), + notice: _('Trigger token was successfully updated.') else render action: "edit" end diff --git a/app/models/concerns/reactive_caching.rb b/app/models/concerns/reactive_caching.rb index 62300f95be4..82c6653b12b 100644 --- a/app/models/concerns/reactive_caching.rb +++ b/app/models/concerns/reactive_caching.rb @@ -151,7 +151,7 @@ module ReactiveCaching def enqueuing_update(*args) yield - worker_class.perform_in(self.class.reactive_cache_refresh_interval, self.class, id, *args) + worker_class.perform_in(self.class.reactive_cache_refresh_interval, self.class.name, id, *args) end def worker_class diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb index 8043f79dad1..9f8b25f6998 100644 --- a/app/models/personal_access_token.rb +++ b/app/models/personal_access_token.rb @@ -65,6 +65,9 @@ class PersonalAccessToken < ApplicationRecord scope :for_organization, ->(organization) { where(organization_id: organization) } scope :preload_users, -> { preload(:user) } scope :order_expires_at_asc_id_desc, -> { reorder(expires_at: :asc, id: :desc) } + scope :order_expires_at_desc_id_desc, -> { reorder(expires_at: :desc, id: :desc) } + scope :order_last_used_at_asc_id_desc, -> { reorder(last_used_at: :asc, id: :desc) } + scope :order_last_used_at_desc_id_desc, -> { reorder(last_used_at: :desc, id: :desc) } scope :project_access_token, -> { includes(:user).references(:user).merge(User.project_bot) } scope :owner_is_human, -> { includes(:user).references(:user).merge(User.human) } scope :last_used_before, ->(date) { where("last_used_at <= ?", date) } @@ -95,7 +98,11 @@ class PersonalAccessToken < ApplicationRecord def self.simple_sorts super.merge( { - 'expires_at_asc_id_desc' => -> { order_expires_at_asc_id_desc } + 'expires_asc' => -> { order_expires_at_asc_id_desc }, + 'expires_at_asc_id_desc' => -> { order_expires_at_asc_id_desc }, # Keep for backward compatibility + 'expires_desc' => -> { order_expires_at_desc_id_desc }, + 'last_used_asc' => -> { order_last_used_at_asc_id_desc }, + 'last_used_desc' => -> { order_last_used_at_desc_id_desc } } ) end diff --git a/app/services/groups/mark_for_deletion_service.rb b/app/services/groups/mark_for_deletion_service.rb new file mode 100644 index 00000000000..9c77774c262 --- /dev/null +++ b/app/services/groups/mark_for_deletion_service.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Groups # rubocop:disable Gitlab/BoundedContexts -- existing top-level module + class MarkForDeletionService < Groups::BaseService + # rubocop:disable Gitlab/NoCodeCoverageComment -- Tested in FOSS and fully overridden and tested in EE + # :nocov + def execute(async: true) + service = ::Groups::DestroyService.new(group, current_user, params) + + async ? service.async_execute : service.execute + end + # :nocov: + # rubocop:enable Gitlab/NoCodeCoverageComment + end +end + +Groups::MarkForDeletionService.prepend_mod diff --git a/app/services/projects/mark_for_deletion_service.rb b/app/services/projects/mark_for_deletion_service.rb new file mode 100644 index 00000000000..e304096b3c9 --- /dev/null +++ b/app/services/projects/mark_for_deletion_service.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Projects + class MarkForDeletionService < BaseService + # rubocop:disable Gitlab/NoCodeCoverageComment -- Tested in FOSS and fully overridden and tested in EE + # :nocov: + def execute(async: true) + service = ::Projects::DestroyService.new(project, current_user, params) + + async ? service.async_execute : service.execute + end + # :nocov: + # rubocop:enable Gitlab/NoCodeCoverageComment + end +end + +Projects::MarkForDeletionService.prepend_mod diff --git a/app/views/projects/merge_requests/creations/new.html.haml b/app/views/projects/merge_requests/creations/new.html.haml index 4f722ba901d..facb6727946 100644 --- a/app/views/projects/merge_requests/creations/new.html.haml +++ b/app/views/projects/merge_requests/creations/new.html.haml @@ -1,5 +1,5 @@ - add_to_breadcrumbs _("Merge requests"), project_merge_requests_path(@project) -- breadcrumb_title _("New") +- breadcrumb_title _("New merge request") - page_title _("New merge request") - add_page_specific_style 'page_bundles/pipelines' - add_page_specific_style 'page_bundles/ci_status' diff --git a/config/initializers/irb.rb b/config/initializers/irb.rb index 89c1bedfcc8..1e12d53429b 100644 --- a/config/initializers/irb.rb +++ b/config/initializers/irb.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true if Gitlab::Runtime.console? + require "irb" + # Stop irb from writing a history file by default. module IrbNoHistory def init_config(*) diff --git a/db/post_migrate/20250314113948_remove_replicas_with_evicted_indices.rb b/db/post_migrate/20250314113948_remove_replicas_with_evicted_indices.rb new file mode 100644 index 00000000000..cb994840069 --- /dev/null +++ b/db/post_migrate/20250314113948_remove_replicas_with_evicted_indices.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class RemoveReplicasWithEvictedIndices < Gitlab::Database::Migration[2.2] + disable_ddl_transaction! + restrict_gitlab_migration gitlab_schema: :gitlab_main + milestone '17.10' + BATCH_SIZE = 500 + EVICTED_STATE = 225 + SCOPE_SQL = ['state = ? AND zoekt_replica_id IS NOT NULL', EVICTED_STATE] + + def up + each_batch_range('zoekt_indices', scope: ->(table) { table.where(SCOPE_SQL) }, of: BATCH_SIZE) do |min, max| + execute <<~SQL + DELETE FROM zoekt_replicas + USING zoekt_indices + WHERE zoekt_indices.zoekt_replica_id = zoekt_replicas.id + AND zoekt_indices.id BETWEEN #{min} AND #{max} + SQL + end + end + + def down + # no-op + end +end diff --git a/db/schema_migrations/20250314113948 b/db/schema_migrations/20250314113948 new file mode 100644 index 00000000000..a4a0e521dc7 --- /dev/null +++ b/db/schema_migrations/20250314113948 @@ -0,0 +1 @@ +15688b6b3dd8adc421d7d41159ef6b448dc9948cf922e2deed022ab46c622657 \ No newline at end of file diff --git a/doc/administration/cells.md b/doc/administration/cells.md index f7d42448fd7..3a42fc0370b 100644 --- a/doc/administration/cells.md +++ b/doc/administration/cells.md @@ -30,6 +30,12 @@ This page explains how to configure the GitLab Rails console for cell functional ## Configuration +To configure your GitLab instance as a Cell instance: + +{{< tabs >}} + +{{< tab title="Self-compiled (source)" >}} + The cells related configuration in `config/gitlab.yml` is in this format: ```yaml @@ -45,6 +51,67 @@ The cells related configuration in `config/gitlab.yml` is in this format: private_key_file: /home/git/gitlab/config/topology-service-key.pem ``` +{{< /tab >}} + +{{< tab title="Linux Package (Omnibus)" >}} + +1. Edit `/etc/gitlab/gitlab.rb` and add the following lines: + + ```ruby + gitlab_rails['cell'] = { + enabled: true, + id: 1, + database: { + skip_sequence_alteration: false + }, + topology_service_client: { + enabled: true, + address: 'topology-service.gitlab.example.com:443', + ca_file: 'path/to/your/ca/.pem', + certificate_file: 'path/to/your/cert/.pem', + private_key_file: 'path/to/your/key/.pem' + } + } + ``` + +1. Reconfigure and restart GitLab: + + ```shell + sudo gitlab-ctl reconfigure + sudo gitlab-ctl restart + ``` + +{{< /tab >}} + +{{< tab title="Helm chart" >}} + +1. Edit `gitlab_values.yaml`: + + ```yaml + global: + appConfig: + cell: + enabled: true + id: 1 + database: + skipSequenceAlteration: false + topologyServiceClient: + address: "topology-service.gitlab.example.com:443" + caFile: "path/to/your/ca/.pem" + privateKeyFile: "path/to/your/key/.pem" + certificateFile: "path/to/your/certificate/.pem" + ``` + +1. Save the file and apply the new values: + + ```shell + helm upgrade -f gitlab_values.yaml gitlab gitlab/gitlab + ``` + +{{< /tab >}} + +{{< /tabs >}} + | Configuration | Default value | Description | |--------------------------------------------|-------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `cell.enabled` | `false` | To configure whether the instance is a Cell or not. `false` means all Cell features are disabled. `session_cookie_prefix_token` is not affected, and can be set separately. | diff --git a/doc/administration/credentials_inventory.md b/doc/administration/credentials_inventory.md index 9650a402767..8ae4994a443 100644 --- a/doc/administration/credentials_inventory.md +++ b/doc/administration/credentials_inventory.md @@ -28,7 +28,7 @@ Use the credentials inventory to monitor and control access to your GitLab self- As an administrator, you can: -- Revoke personal or project access tokens. +- Revoke personal, project, or group access tokens. - Delete SSH keys. - Review credential details including: - Ownership. @@ -48,21 +48,17 @@ To revoke a personal access token in your instance: The access token is revoked and the user is notified by email. -![The credentials inventory page listing personal access tokens.](img/credentials_inventory_personal_access_tokens_v14_9.png) - -## Revoke project access tokens +## Revoke project or group access tokens To revoke a project access token in your instance: 1. On the left sidebar, at the bottom, select **Admin**. 1. Select **Credentials**. -1. Select the **Project access tokens** tab. +1. Select the **Project and group access tokens** tab. 1. Next to the project access token, select **Revoke**. The access token is revoked and a background process begins to delete the associated project bot user. -![The credentials inventory page listing project access tokens.](img/credentials_inventory_project_access_tokens_v14_9.png) - ## Delete SSH keys To delete an SSH key in your instance: @@ -74,8 +70,6 @@ To delete an SSH key in your instance: The SSH key is deleted and the user is notified. -![The credentials inventory page listing SSH keys.](img/credentials_inventory_ssh_keys_v14_9.png) - ## View GPG keys You can see details for each GPG key including the owner, ID, and [verification status](../user/project/repository/signed_commits/gpg.md). @@ -85,5 +79,3 @@ To view information about GPG keys in your instance: 1. On the left sidebar, at the bottom, select **Admin**. 1. Select **Credentials**. 1. Select the **GPG Keys** tab. - -![The credentials inventory page listing GPG keys.](img/credentials_inventory_gpg_keys_v14_9.png) diff --git a/doc/administration/gitlab_duo_self_hosted/troubleshooting.md b/doc/administration/gitlab_duo_self_hosted/troubleshooting.md index e6ea15e9d49..035d9cade19 100644 --- a/doc/administration/gitlab_duo_self_hosted/troubleshooting.md +++ b/doc/administration/gitlab_duo_self_hosted/troubleshooting.md @@ -40,6 +40,12 @@ Before you begin troubleshooting, you should: Now, requests and responses from GitLab to the AI gateway are logged to [`llm.log`](../logs/_index.md#llmlog) +For more information on troubleshooting GitLab Duo, see: + +- [Troubleshooting GitLab Duo](../../user/gitlab_duo/troubleshooting.md). +- [Troubleshooting Code Suggestions](../../user/project/repository/code_suggestions/troubleshooting.md). +- [GitLab Duo Chat troubleshooting](../../user/gitlab_duo_chat/troubleshooting.md). + ## Use debugging scripts We provide two debugging scripts to help administrators verify their self-hosted model configuration. diff --git a/doc/administration/img/credentials_inventory_gpg_keys_v14_9.png b/doc/administration/img/credentials_inventory_gpg_keys_v14_9.png deleted file mode 100644 index 6c4c8f30df6..00000000000 Binary files a/doc/administration/img/credentials_inventory_gpg_keys_v14_9.png and /dev/null differ diff --git a/doc/administration/img/credentials_inventory_personal_access_tokens_v14_9.png b/doc/administration/img/credentials_inventory_personal_access_tokens_v14_9.png deleted file mode 100644 index 254d520d538..00000000000 Binary files a/doc/administration/img/credentials_inventory_personal_access_tokens_v14_9.png and /dev/null differ diff --git a/doc/administration/img/credentials_inventory_project_access_tokens_v14_9.png b/doc/administration/img/credentials_inventory_project_access_tokens_v14_9.png deleted file mode 100644 index ae204ac09ef..00000000000 Binary files a/doc/administration/img/credentials_inventory_project_access_tokens_v14_9.png and /dev/null differ diff --git a/doc/administration/img/credentials_inventory_ssh_keys_v14_9.png b/doc/administration/img/credentials_inventory_ssh_keys_v14_9.png deleted file mode 100644 index 8f2f11515eb..00000000000 Binary files a/doc/administration/img/credentials_inventory_ssh_keys_v14_9.png and /dev/null differ diff --git a/doc/api/group_access_tokens.md b/doc/api/group_access_tokens.md index eedf01f2f14..0a0c75c4a2e 100644 --- a/doc/api/group_access_tokens.md +++ b/doc/api/group_access_tokens.md @@ -40,7 +40,7 @@ GET /groups/:id/access_tokens?state=inactive | `last_used_before` | datetime (ISO 8601) | No | If defined, returns tokens last used before the specified time. | | `revoked` | boolean | No | If `true`, only returns revoked tokens. | | `search` | string | No | If defined, returns tokens that include the specified value in the name. | -| `sort` | string | No | If defined, sorts the results by the specified value. Possible values: `created_asc`, `created_desc`, `name_asc`, `name_desc`. | +| `sort` | string | No | If defined, sorts the results by the specified value. Possible values: `created_asc`, `created_desc`, `expires_asc`, `expires_desc`, `last_used_asc`, `last_used_desc`, `name_asc`, `name_desc`.| | `state` | string | No | If defined, returns tokens with the specified state. Possible values: `active` and `inactive`. | ```shell diff --git a/doc/api/group_service_accounts.md b/doc/api/group_service_accounts.md index d90ed60efe4..e565856b682 100644 --- a/doc/api/group_service_accounts.md +++ b/doc/api/group_service_accounts.md @@ -223,6 +223,7 @@ Parameters: | `id` | integer/string | yes | ID or [URL-encoded path](rest/_index.md#namespaced-paths) of a top-level group. | | `user_id` | integer | yes | ID of service account user. | | `name` | string | yes | Name of personal access token. | +| `description` | string | no | Description of personal access token. | | `scopes` | array | yes | Array of approved scopes. For a list of possible values, see [Personal access token scopes](../user/profile/personal_access_tokens.md#personal-access-token-scopes). | | `expires_at` | date | no | Expiration date of the access token in ISO format (`YYYY-MM-DD`). If not specified, the date is set to the [maximum allowable lifetime limit](../user/profile/personal_access_tokens.md#access-token-expiration). | diff --git a/doc/api/personal_access_tokens.md b/doc/api/personal_access_tokens.md index f1443c65613..01bdac88521 100644 --- a/doc/api/personal_access_tokens.md +++ b/doc/api/personal_access_tokens.md @@ -50,7 +50,7 @@ Supported attributes: | `last_used_before` | datetime (ISO 8601) | No | If defined, returns tokens last used before the specified time. | | `revoked` | boolean | No | If `true`, only returns revoked tokens. | | `search` | string | No | If defined, returns tokens that include the specified value in the name. | -| `sort` | string | No | If defined, sorts the results by the specified value. Possible values: `created_asc`, `created_desc`, `name_asc`, `name_desc`. | +| `sort` | string | No | If defined, sorts the results by the specified value. Possible values: `created_asc`, `created_desc`, `expires_asc`, `expires_desc`, `last_used_asc`, `last_used_desc`, `name_asc`, `name_desc`. | | `state` | string | No | If defined, returns tokens with the specified state. Possible values: `active` and `inactive`. | | `user_id` | integer or string | No | If defined, returns tokens owned by the specified user. Non-administrators can only filter their own tokens. | diff --git a/doc/api/project_access_tokens.md b/doc/api/project_access_tokens.md index ca3cff3c284..411a51e014f 100644 --- a/doc/api/project_access_tokens.md +++ b/doc/api/project_access_tokens.md @@ -40,7 +40,7 @@ GET projects/:id/access_tokens?state=inactive | `last_used_before` | datetime (ISO 8601) | No | If defined, returns tokens last used before the specified time. | | `revoked` | boolean | No | If `true`, only returns revoked tokens. | | `search` | string | No | If defined, returns tokens that include the specified value in the name. | -| `sort` | string | No | If defined, sorts the results by the specified value. Possible values: `created_asc`, `created_desc`, `name_asc`, `name_desc`. | +| `sort` | string | No | If defined, sorts the results by the specified value. Possible values: `created_asc`, `created_desc`, `expires_asc`, `expires_desc`, `last_used_asc`, `last_used_desc`, `name_asc`, `name_desc`.| | `state` | string | No | If defined, returns tokens with the specified state. Possible values: `active` and `inactive`. | ```shell diff --git a/doc/policy/development_stages_support.md b/doc/policy/development_stages_support.md index 0da9db6275b..e6f24a67901 100644 --- a/doc/policy/development_stages_support.md +++ b/doc/policy/development_stages_support.md @@ -61,6 +61,7 @@ Both types are production-ready, but have different scopes. Limited availability features: - Are ready for production use at a reduced scale. +- Can be initially available on one or more GitLab platforms (GitLab.com, GitLab Self-Managed, GitLab Dedicated). - Might initially be free, then become paid when generally available. - Might be offered at a discount before becoming generally available. - Might have commercial terms that change for new contracts when generally available. @@ -74,6 +75,7 @@ Generally available features: - Are ready for production use at any scale. - Are [fully supported](https://about.gitlab.com/support/statement-of-support/) and documented. - Have a complete user experience aligned with GitLab design standards. +- Must be available on all GitLab platforms (GitLab.com, GitLab Self-Managed, GitLab Dedicated). ## Development guidelines diff --git a/doc/user/duo_amazon_q/_index.md b/doc/user/duo_amazon_q/_index.md index e520e511069..a835980c16b 100644 --- a/doc/user/duo_amazon_q/_index.md +++ b/doc/user/duo_amazon_q/_index.md @@ -125,12 +125,24 @@ Amazon Q can reply to these comments with suggested fixes. Amazon Q proposes fixes for the issue in the comment. -### Create test coverage +### Generate unit tests Generate new unit tests while you're having your merge request reviewed. Amazon Q surfaces any missing unit test coverage in the proposed code changes. -To create test coverage: +To generate unit tests for all code changes: + +1. Open your merge request. +1. On the **Overview** tab, in a comment, type `/q test`. +1. Select **Comment**. + +Amazon Q populates a comment with the suggested tests. + +### Create test coverage for selected lines + +Generate new unit tests for specific lines of code in your merge request. + +To create test coverage for selected lines: 1. Open your merge request. 1. On the **Changes** tab, select the lines you want to test. diff --git a/doc/user/gitlab_duo/troubleshooting.md b/doc/user/gitlab_duo/troubleshooting.md index 2e32bf6d969..7c373cae7ca 100644 --- a/doc/user/gitlab_duo/troubleshooting.md +++ b/doc/user/gitlab_duo/troubleshooting.md @@ -10,6 +10,12 @@ When working with GitLab Duo, you might encounter issues. Start by [running a health check](setup.md#run-a-health-check-for-gitlab-duo) to determine if your instance meets the requirements to use GitLab Duo. +For more information on troubleshooting GitLab Duo, see: + +- [Troubleshooting Code Suggestions](../project/repository/code_suggestions/troubleshooting.md). +- [GitLab Duo Chat troubleshooting](../gitlab_duo_chat/troubleshooting.md). +- [Troubleshooting GitLab Duo Self-Hosted](../../administration/gitlab_duo_self_hosted/troubleshooting.md). + If the health check does not resolve your problem, review these troubleshooting steps. ## GitLab Duo features do not work on self-managed diff --git a/doc/user/gitlab_duo_chat/troubleshooting.md b/doc/user/gitlab_duo_chat/troubleshooting.md index 0b2f9e9f296..de5b1a6eaf7 100644 --- a/doc/user/gitlab_duo_chat/troubleshooting.md +++ b/doc/user/gitlab_duo_chat/troubleshooting.md @@ -21,11 +21,13 @@ button to appear. If this does not work, you can also check the following troubleshooting documentation: -- [GitLab Duo Code Suggestions](../project/repository/code_suggestions/troubleshooting.md) -- [VS Code](../../editor_extensions/visual_studio_code/troubleshooting.md) -- [Microsoft Visual Studio](../../editor_extensions/visual_studio/visual_studio_troubleshooting.md) -- [JetBrains IDEs](../../editor_extensions/jetbrains_ide/jetbrains_troubleshooting.md) -- [Neovim](../../editor_extensions/neovim/neovim_troubleshooting.md) +- [GitLab Duo Code Suggestions](../project/repository/code_suggestions/troubleshooting.md). +- [VS Code](../../editor_extensions/visual_studio_code/troubleshooting.md). +- [Microsoft Visual Studio](../../editor_extensions/visual_studio/visual_studio_troubleshooting.md). +- [JetBrains IDEs](../../editor_extensions/jetbrains_ide/jetbrains_troubleshooting.md). +- [Neovim](../../editor_extensions/neovim/neovim_troubleshooting.md). +- [Troubleshooting GitLab Duo](../gitlab_duo/troubleshooting.md). +- [Troubleshooting GitLab Duo Self-Hosted](../../administration/gitlab_duo_self_hosted/troubleshooting.md). ## `Error M2000` diff --git a/doc/user/group/credentials_inventory.md b/doc/user/group/credentials_inventory.md index 25367596a3f..5fac4fbf077 100644 --- a/doc/user/group/credentials_inventory.md +++ b/doc/user/group/credentials_inventory.md @@ -48,8 +48,6 @@ To revoke personal access tokens for enterprise users in your group: The access token is revoked and the user is notified by email. -![The credentials inventory page listing personal access tokens.](img/group_credentials_inventory_personal_access_tokens_v17_5.png) - ## Delete SSH keys To delete SSH keys for enterprise users in your group: @@ -61,8 +59,6 @@ To delete SSH keys for enterprise users in your group: The SSH key is deleted and the user is notified. -![The credentials inventory page listing SSH keys.](img/group_credentials_inventory_ssh_keys_v17_5.png) - ## Revoke project or group access tokens You cannot view or revoke project or group access tokens using the credentials inventory on GitLab.com. diff --git a/doc/user/group/img/group_credentials_inventory_personal_access_tokens_v17_5.png b/doc/user/group/img/group_credentials_inventory_personal_access_tokens_v17_5.png deleted file mode 100644 index 0eba417cb0e..00000000000 Binary files a/doc/user/group/img/group_credentials_inventory_personal_access_tokens_v17_5.png and /dev/null differ diff --git a/doc/user/group/img/group_credentials_inventory_ssh_keys_v17_5.png b/doc/user/group/img/group_credentials_inventory_ssh_keys_v17_5.png deleted file mode 100644 index c2f33bac7ad..00000000000 Binary files a/doc/user/group/img/group_credentials_inventory_ssh_keys_v17_5.png and /dev/null differ diff --git a/doc/user/project/file_lock.md b/doc/user/project/file_lock.md index 7b6077c3a30..8a822fe0e02 100644 --- a/doc/user/project/file_lock.md +++ b/doc/user/project/file_lock.md @@ -2,7 +2,7 @@ stage: Create group: Source Code info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments -title: File Locking +title: File locking --- {{< details >}} @@ -12,39 +12,23 @@ title: File Locking {{< /details >}} -Preventing wasted work caused by unresolvable merge conflicts requires -a different way of working. This means explicitly requesting write permissions, -and verifying no one else is editing the same file before you start. +File locking prevents multiple people from editing the same file simultaneously, which helps avoid +merge conflicts. File locking is especially valuable for binary files that cannot be merged like +design files, videos, and other non-text content. -Although branching strategies typically work well enough for source code and -plain text because different versions can be merged together, they do not work -for binary files. +GitLab supports two different types of file locking: -When file locking is setup, lockable files are **read-only** by default. - -When a file is locked, only the user who locked the file may modify it. This -user is said to "hold the lock" or have "taken the lock", because only one user -can lock a file at a time. When a file or directory is unlocked, the user is -said to have "released the lock". - -GitLab supports two different modes of file locking: - -- [Exclusive file locks](../../topics/git/file_management.md#file-locks) for binary files: done - **through the command line** with Git LFS and `.gitattributes`, it prevents locked - files from being modified on any branch. -- [Default branch locks](#default-branch-file-and-directory-locks): done - **through the GitLab UI**, it prevents locked files and directories being - modified on the default branch. +- [Exclusive file locks](../../topics/git/file_management.md#file-locks): Applied through the + command line with Git LFS and `.gitattributes`. + These locks prevent modifications to locked files on any branch. +- [Default branch file and directory locks](#default-branch-file-and-directory-locks): Applied + through the GitLab UI. These locks prevent modifications to files and directories on the + default branch only. ## Permissions -Locks can be created by any person who has at least -Developer role for the repository. - -Only the user who locked the file or directory can edit locked files. Other -users are prevented from modifying locked files by pushing, merging, -or any other means, and are shown an error like: -`'.gitignore' is locked by @Administrator`. +You can create file locks if you have at least the Developer role for the project. +For more information, see [Roles and permissions](../../user/permissions.md). ## Default branch file and directory locks @@ -55,40 +39,52 @@ or any other means, and are shown an error like: {{< /details >}} -This process allows you to lock one file at a time through the GitLab UI and -requires access to the [GitLab Premium or Ultimate tier](https://about.gitlab.com/pricing/). +Default branch locks apply only to the [default branch](repository/branches/default.md) set in your +project's settings. These locks help maintain stability in your default branch without blocking +collaborator workflows in other branches. -Default branch file and directory locks only apply to the -[default branch](repository/branches/default.md) set in the project's settings. +When a file or directory is locked by a user: -Changes to locked files on the default branch are blocked, including merge -requests that modify locked files. Unlock the file to allow changes. +- Only the user who created the lock can modify the file or directory on the default branch. +- For other users, the locked file or directory is **read-only** on the default branch. +- Direct changes to locked files or directories on the default branch are blocked. +- Merge requests that modify locked files or directories cannot be merged to the default branch. -### Lock a file or a directory +{{< alert type="note" >}} -To lock a file: +On non-default branches, all users can still modify locked files and directories. +A **Lock** status is visible on these files and directories. This helps team members +to be aware of in-flight work without restricting their workflow on other branches. -1. Open the file or directory in GitLab. -1. In the upper-right corner, above the file, select **Lock**. +{{< /alert >}} + +## Lock a file or directory + +To lock a file or directory: + +1. On the left sidebar, select **Search or go to** and find your project. +1. Go to the file or directory you want to lock. +1. In the upper-right corner, select **Lock**. 1. On the confirmation dialog, select **OK**. -If you do not have permission to lock the file, the button is not enabled. +If **Lock** is not enabled, you don't have the required permissions to lock the file. -To view the user who locked a directory (if it was not you), hover over the button. Reinstatement of -similar functionality for locked files is discussed in -[issue 376222](https://gitlab.com/gitlab-org/gitlab/-/issues/376222). +To see who locked a directory, if it wasn't you, hover over the **Lock**. For a similar function +for locked files, see [issue 4623](https://gitlab.com/gitlab-org/gitlab/-/issues/4623). -### View and remove existing locks +## View and remove locks -To view and remove file locks: +Locks can be removed by: + +- The user who created the lock. +- Any user with at least the Maintainer role for the project. + +To view and manage file locks: 1. On the left sidebar, select **Search or go to** and find your project. 1. Select **Code > Locked files**. -This list shows all the files locked either through LFS or GitLab UI. - -Locks can be removed by their author, or any user -with at least the Maintainer role. +This list displays all files locked either through Git LFS exclusive locks or the GitLab UI. ## Related topics diff --git a/doc/user/project/repository/code_suggestions/troubleshooting.md b/doc/user/project/repository/code_suggestions/troubleshooting.md index be9fbe8c95b..e7823ef41a1 100644 --- a/doc/user/project/repository/code_suggestions/troubleshooting.md +++ b/doc/user/project/repository/code_suggestions/troubleshooting.md @@ -23,6 +23,12 @@ When working with GitLab Duo Code Suggestions, you might encounter the following You can run a [health check](../../../gitlab_duo/turn_on_off.md) to test if your instance meets the requirements to run Code Suggestions. +For more information on troubleshooting GitLab Duo, see: + +- [Troubleshooting GitLab Duo](../../../../user/gitlab_duo/troubleshooting.md). +- [GitLab Duo Chat troubleshooting](../../../gitlab_duo_chat/troubleshooting.md). +- [Troubleshooting GitLab Duo Self-Hosted](../../../../administration/gitlab_duo_self_hosted/troubleshooting.md). + ## Suggestions are not displayed If suggestions are not displayed, ensure that you: diff --git a/doc/user/project/repository/web_editor.md b/doc/user/project/repository/web_editor.md index c2aa0f87b2b..55dff8869aa 100644 --- a/doc/user/project/repository/web_editor.md +++ b/doc/user/project/repository/web_editor.md @@ -20,7 +20,7 @@ local setup. You can: - Create new files and directories. - Upload and replace files. - Create branches and tags for version control. -- [Lock files](../file_lock.md#lock-a-file-or-a-directory) to prevent concurrent editing conflicts. +- [Lock files](../file_lock.md#lock-a-file-or-directory) to prevent concurrent editing conflicts. - Contribute to projects without setting up Git locally. GitLab uses your [primary email address](../../profile/_index.md#change-the-email-displayed-on-your-commits) diff --git a/lib/web_ide/extension_marketplace_preset.rb b/lib/web_ide/extension_marketplace_preset.rb index 0a42371669a..09b452a983a 100644 --- a/lib/web_ide/extension_marketplace_preset.rb +++ b/lib/web_ide/extension_marketplace_preset.rb @@ -16,7 +16,7 @@ module WebIde # See https://open-vsx.org/swagger-ui/index.html?urls.primaryName=VSCode%20Adapter for OpenVSX Swagger API service_url: "https://open-vsx.org/vscode/gallery", item_url: "https://open-vsx.org/vscode/item", - resource_url_template: "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}" + resource_url_template: "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{versionRaw}/{path}" ) end diff --git a/lib/web_ide/settings_sync.rb b/lib/web_ide/settings_sync.rb index 829c36c5320..e81d92cd29c 100644 --- a/lib/web_ide/settings_sync.rb +++ b/lib/web_ide/settings_sync.rb @@ -11,8 +11,10 @@ module WebIde # 2e0d3e8c1107f9ccc5ea is the hash of "web_ide_https://open-vsx.org/vscode/gallery_https://open-vsx.org/vscode/item_" # e36c431c0e2e1ee82c86 is the hash of "web_ide_https://open-vsx.org/vscode/gallery_https://open-vsx.org/vscode/item_https://open-vsx.org/vscode/asset/{publisher}/{name}/{version}/Microsoft.VisualStudio.Code.WebResources/{path}" # 55b10685e181429abe78 is the hash of "web_ide_https://open-vsx.org/vscode/gallery_https://open-vsx.org/vscode/item_https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}" + # 372ad9a36594931b612d is the hash of "web_ide_https://open-vsx.org/vscode/gallery_https://open-vsx.org/vscode/item_https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{versionRaw}/{path}" "e36c431c0e2e1ee82c86" => "2e0d3e8c1107f9ccc5ea", - "55b10685e181429abe78" => "2e0d3e8c1107f9ccc5ea" + "55b10685e181429abe78" => "2e0d3e8c1107f9ccc5ea", + "372ad9a36594931b612d" => "2e0d3e8c1107f9ccc5ea" }.freeze def settings_context_hash(extension_marketplace_settings:) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e91c65ff1ba..29f5655025e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -6263,6 +6263,9 @@ msgstr "" msgid "AmazonQ|Create unit tests for selected lines of code in Java or Python files (Beta)" msgstr "" +msgid "AmazonQ|Create unit tests for this merge request (Beta)" +msgstr "" + msgid "AmazonQ|Delete the IAM identity provider created for AI gateway." msgstr "" diff --git a/scripts/frontend/quarantined_vue3_specs.txt b/scripts/frontend/quarantined_vue3_specs.txt index 138e6cc47d2..eba4a77239c 100644 --- a/scripts/frontend/quarantined_vue3_specs.txt +++ b/scripts/frontend/quarantined_vue3_specs.txt @@ -74,7 +74,6 @@ ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulne ee/spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js ee/spec/frontend/status_checks/components/modal_create_spec.js ee/spec/frontend/status_checks/mount_spec.js -ee/spec/frontend/tracing/details/tracing_details_spec.js ee/spec/frontend/tracing/details/tracing_header_spec.js ee/spec/frontend/usage_quotas/transfer/components/usage_by_month_spec.js ee/spec/frontend/users/identity_verification/components/international_phone_input_spec.js diff --git a/spec/factories/ci/bridge.rb b/spec/factories/ci/bridge.rb index e5a03d98e8e..fca3724764c 100644 --- a/spec/factories/ci/bridge.rb +++ b/spec/factories/ci/bridge.rb @@ -25,13 +25,13 @@ FactoryBot.define do bridge.project ||= bridge.pipeline.project if evaluator.downstream.present? - bridge.options = bridge.options.to_h.merge( + bridge.options = bridge.options.to_h.deep_merge( trigger: { project: evaluator.downstream.full_path } ) end if evaluator.upstream.present? - bridge.options = bridge.options.to_h.merge( + bridge.options = bridge.options.to_h.deep_merge( bridge_needs: { pipeline: evaluator.upstream.full_path } ) end diff --git a/spec/features/issues/user_creates_confidential_merge_request_spec.rb b/spec/features/issues/user_creates_confidential_merge_request_spec.rb index 23fef5fa46e..12eff36a3af 100644 --- a/spec/features/issues/user_creates_confidential_merge_request_spec.rb +++ b/spec/features/issues/user_creates_confidential_merge_request_spec.rb @@ -50,6 +50,8 @@ RSpec.describe 'User creates confidential merge request on issue page', :js, fea click_button 'Create confidential merge request' end + click_button 'Show more breadcrumbs' + expect(page).to have_content(forked_project.namespace.name) end end diff --git a/spec/features/work_items/work_items_list_filters_spec.rb b/spec/features/work_items/work_items_list_filters_spec.rb index 383556b6518..854bfa264bc 100644 --- a/spec/features/work_items/work_items_list_filters_spec.rb +++ b/spec/features/work_items/work_items_list_filters_spec.rb @@ -3,7 +3,6 @@ require 'spec_helper' RSpec.describe 'Work items list filters', :js, feature_category: :team_planning do - include WorkItemFeedbackHelpers include FilteredSearchHelpers let_it_be(:user1) { create(:user) } @@ -48,8 +47,6 @@ RSpec.describe 'Work items list filters', :js, feature_category: :team_planning before do sign_in(user1) visit group_work_items_path(group) - - close_work_item_feedback_popover_if_present end describe 'assignee' do diff --git a/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js b/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js index 2e9126da815..c541f0357bc 100644 --- a/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js +++ b/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js @@ -1,9 +1,9 @@ -import { shallowMount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import axios from 'axios'; import AxiosMockAdapter from 'axios-mock-adapter'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import SidebarReviewers from '~/sidebar/components/reviewers/sidebar_reviewers.vue'; import SidebarService from '~/sidebar/services/sidebar_service'; import SidebarMediator from '~/sidebar/sidebar_mediator'; @@ -21,8 +21,10 @@ describe('sidebar reviewers', () => { let mediator; let axiosMock; + const findAssignButton = () => wrapper.findByTestId('sidebar-reviewers-assign-button'); + const createComponent = (props) => { - wrapper = shallowMount(SidebarReviewers, { + wrapper = shallowMountExtended(SidebarReviewers, { apolloProvider: apolloMock, propsData: { issuableIid: '1', @@ -67,7 +69,7 @@ describe('sidebar reviewers', () => { ${'shows'} | ${true} | ${true} ${'does not show'} | ${false} | ${false} `('$copy Assign button when canUpdate is $canUpdate', ({ canUpdate, expected }) => { - wrapper = shallowMount(SidebarReviewers, { + wrapper = shallowMountExtended(SidebarReviewers, { apolloProvider: apolloMock, propsData: { issuableIid: '1', @@ -93,7 +95,7 @@ describe('sidebar reviewers', () => { }, }); - expect(wrapper.find('[data-testid="sidebar-reviewers-assign-buton"]').exists()).toBe(expected); + expect(findAssignButton().exists()).toBe(expected); }); it('calls the mediator when it saves the reviewers', () => { diff --git a/spec/frontend/vue_shared/components/markdown/header_spec.js b/spec/frontend/vue_shared/components/markdown/header_spec.js index 4fb5cbc46b5..d8054af925f 100644 --- a/spec/frontend/vue_shared/components/markdown/header_spec.js +++ b/spec/frontend/vue_shared/components/markdown/header_spec.js @@ -1,13 +1,18 @@ import $ from 'jquery'; import { nextTick } from 'vue'; import { GlToggle, GlButton } from '@gitlab/ui'; +import { createWrapper as createVueTestWrapper } from '@vue/test-utils'; import HeaderComponent from '~/vue_shared/components/markdown/header.vue'; import HeaderDividerComponent from '~/vue_shared/components/markdown/header_divider.vue'; import CommentTemplatesModal from '~/vue_shared/components/markdown/comment_templates_modal.vue'; import ToolbarButton from '~/vue_shared/components/markdown/toolbar_button.vue'; import ToolbarTableButton from '~/content_editor/components/toolbar_table_button.vue'; import DrawioToolbarButton from '~/vue_shared/components/markdown/drawio_toolbar_button.vue'; -import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { + mountExtended, + shallowMountExtended, + extendedWrapper, +} from 'helpers/vue_test_utils_helper'; import { updateText } from '~/lib/utils/text_markdown'; import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; @@ -352,24 +357,38 @@ describe('Markdown field header component', () => { describe('find and replace', () => { let form; + let formWrapper; const createParentForm = () => { form = document.createElement('form'); const field = document.createElement('div'); const root = document.createElement('div'); + const textarea = document.createElement('textarea'); + textarea.value = 'lorem ipsum dolor sit amet '; field.classList = 'js-vue-markdown-field'; + form.classList = 'md-area'; + form.appendChild(textarea); form.appendChild(field); field.appendChild(root); document.body.appendChild(form); + formWrapper = extendedWrapper(createVueTestWrapper(form)); return root; }; + const findFindInput = () => wrapper.findByTestId('find-btn'); + const findCloneDiv = () => formWrapper.findByTestId('find-and-replace-clone'); + const showFindAndReplace = async () => { $(document).triggerHandler('markdown-editor:find-and-replace:show', [$('form')]); await nextTick(); }; - const findFindInput = () => wrapper.findByTestId('find-btn'); + const closeFindAndReplace = async () => { + const preventDefault = jest.fn(); + findFindInput().vm.$emit('keydown', { preventDefault, key: 'Escape' }); + await nextTick(); + expect(preventDefault).not.toHaveBeenCalled(); + }; beforeEach(() => { createWrapper({ attachTo: createParentForm() }); @@ -403,11 +422,49 @@ describe('Markdown field header component', () => { it('closes the find-and-replace bar when Escape key is pressed', async () => { await showFindAndReplace(); - const preventDefault = jest.fn(); - findFindInput().vm.$emit('keydown', { preventDefault, key: 'Escape' }); - await nextTick(); - expect(preventDefault).not.toHaveBeenCalled(); + expect(wrapper.findByTestId('find-and-replace').exists()).toBe(true); + await closeFindAndReplace(); expect(wrapper.findByTestId('find-and-replace').exists()).toBe(false); }); + + it('embeds a clone to div to color highlighted text', async () => { + await showFindAndReplace(); + + findFindInput().vm.$emit('keyup', { target: { value: 'my-text' } }); + + await nextTick(); + expect(findCloneDiv().exists()).toBe(true); + + // Wait till the highlighting is complete + await nextTick(); + + // Check that closing the find and replace removes the clone div + await closeFindAndReplace(); + expect(findCloneDiv().exists()).toBe(false); + }); + + it('highlights text when text matches', async () => { + await showFindAndReplace(); + + // Text that does not match + await findFindInput().vm.$emit('keyup', { target: { value: 'my-text' } }); + + expect(formWrapper.element.querySelector('.js-highlight')).toBe(null); + + // Text that matches + await findFindInput().vm.$emit('keyup', { target: { value: 'lorem' } }); + + expect(formWrapper.element.querySelector('.js-highlight').innerHTML).toBe('lorem'); + }); + + it('is not vulnerable to XSS', async () => { + await showFindAndReplace(); + await findFindInput().vm.$emit('keyup', { target: { value: 'prompt' } }); + await nextTick(); + + expect(findCloneDiv().element.innerHTML).toBe( + 'lorem ipsum dolor sit amet <img src="prompt">', + ); + }); }); }); diff --git a/spec/lib/web_ide/extension_marketplace_preset_spec.rb b/spec/lib/web_ide/extension_marketplace_preset_spec.rb index a1d85b86f82..8eb7b4eb23b 100644 --- a/spec/lib/web_ide/extension_marketplace_preset_spec.rb +++ b/spec/lib/web_ide/extension_marketplace_preset_spec.rb @@ -19,7 +19,7 @@ RSpec.describe WebIde::ExtensionMarketplacePreset, feature_category: :web_ide do values: { service_url: "https://open-vsx.org/vscode/gallery", item_url: "https://open-vsx.org/vscode/item", - resource_url_template: "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}" + resource_url_template: "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{versionRaw}/{path}" } ) end diff --git a/spec/lib/web_ide/settings/extension_marketplace_validator_spec.rb b/spec/lib/web_ide/settings/extension_marketplace_validator_spec.rb index 8fa1e69b001..b07102aac0c 100644 --- a/spec/lib/web_ide/settings/extension_marketplace_validator_spec.rb +++ b/spec/lib/web_ide/settings/extension_marketplace_validator_spec.rb @@ -7,7 +7,7 @@ RSpec.describe WebIde::Settings::ExtensionMarketplaceValidator, feature_category let(:service_url) { "https://open-vsx.org/vscode/gallery" } let(:item_url) { "https://open-vsx.org/vscode/item" } - let(:resource_url_template) { "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}" } + let(:resource_url_template) { "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{versionRaw}/{path}" } let(:vscode_extension_marketplace) do { service_url: service_url, diff --git a/spec/lib/web_ide/settings/settings_integration_spec.rb b/spec/lib/web_ide/settings/settings_integration_spec.rb index ddf8c8f526f..93846ba9ba1 100644 --- a/spec/lib/web_ide/settings/settings_integration_spec.rb +++ b/spec/lib/web_ide/settings/settings_integration_spec.rb @@ -9,7 +9,7 @@ RSpec.describe ::WebIde::Settings, feature_category: :web_ide do # rubocop:disab { service_url: "https://open-vsx.org/vscode/gallery", item_url: "https://open-vsx.org/vscode/item", - resource_url_template: 'https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}' + resource_url_template: 'https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{versionRaw}/{path}' } end @@ -69,7 +69,7 @@ RSpec.describe ::WebIde::Settings, feature_category: :web_ide do # rubocop:disab stub_env("GITLAB_WEB_IDE_VSCODE_EXTENSION_MARKETPLACE", '{"service_url":"https://OVERRIDE.org/vscode/gallery",' \ '"item_url":"https://OVERRIDE.org/vscode/item",' \ - '"resource_url_template":"https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}"}' + '"resource_url_template":"https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{versionRaw}/{path}"}' ) end @@ -78,7 +78,7 @@ RSpec.describe ::WebIde::Settings, feature_category: :web_ide do # rubocop:disab { service_url: "https://OVERRIDE.org/vscode/gallery", item_url: "https://OVERRIDE.org/vscode/item", - resource_url_template: "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}" + resource_url_template: "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{versionRaw}/{path}" } ) end @@ -99,7 +99,7 @@ RSpec.describe ::WebIde::Settings, feature_category: :web_ide do # rubocop:disab it "uses default value" do expected_value = { item_url: "https://open-vsx.org/vscode/item", - resource_url_template: "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}", + resource_url_template: "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{versionRaw}/{path}", service_url: "https://open-vsx.org/vscode/gallery" } diff --git a/spec/lib/web_ide/settings_sync_spec.rb b/spec/lib/web_ide/settings_sync_spec.rb index 71f8da8e551..5b316d16e75 100644 --- a/spec/lib/web_ide/settings_sync_spec.rb +++ b/spec/lib/web_ide/settings_sync_spec.rb @@ -24,7 +24,12 @@ RSpec.describe WebIde::SettingsSync, feature_category: :web_ide do }, expectation: 'c6620244fe72864fa8d8' }, - "default vscode settings" => { + "default vscode settings (openvsx)" => { + enabled: true, + vscode_settings: ::WebIde::ExtensionMarketplacePreset.open_vsx.values, + expectation: '2e0d3e8c1107f9ccc5ea' + }, + "default vscode settings (openvsx compat without versionRaw)" => { enabled: true, vscode_settings: { service_url: 'https://open-vsx.org/vscode/gallery', @@ -33,7 +38,7 @@ RSpec.describe WebIde::SettingsSync, feature_category: :web_ide do }, expectation: '2e0d3e8c1107f9ccc5ea' }, - "default vscode settings (compatability)" => { + "default vscode settings (openvsx compat with vscode/asset)" => { enabled: true, vscode_settings: { service_url: 'https://open-vsx.org/vscode/gallery', @@ -42,7 +47,7 @@ RSpec.describe WebIde::SettingsSync, feature_category: :web_ide do }, expectation: '2e0d3e8c1107f9ccc5ea' }, - "default vscode settings (compatability after bug fix)" => { + "default vscode settings (openvsx compat without resource_url_template)" => { # This is the default vscode settings that creates the same # hash as default_vscode_settings to avoid breaking-changes. # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/178491 diff --git a/spec/migrations/db/post_migrate/20250314113948_remove_replicas_with_evicted_indices_spec.rb b/spec/migrations/db/post_migrate/20250314113948_remove_replicas_with_evicted_indices_spec.rb new file mode 100644 index 00000000000..b2df5dc50c4 --- /dev/null +++ b/spec/migrations/db/post_migrate/20250314113948_remove_replicas_with_evicted_indices_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe RemoveReplicasWithEvictedIndices, migration: :gitlab_main, feature_category: :global_search do + let(:zoekt_nodes) { table(:zoekt_nodes) } + let(:zoekt_node) { zoekt_nodes.create!(uuid: SecureRandom.uuid, index_base_url: 'i_url', search_base_url: 's_url') } + let(:organizations) { table(:organizations) } + let(:organization) { organizations.create!(path: 'path') } + let(:namespaces) { table(:namespaces) } + let(:namespace) { namespaces.create!(name: 'name', path: 'path', type: 'Group', organization_id: organization.id) } + let(:zoekt_indices) { table(:zoekt_indices) } + let!(:evicted_index_without_zoekt_replica) do + zoekt_indices.create!( + state: described_class::EVICTED_STATE, + zoekt_node_id: zoekt_node.id, + namespace_id: namespace.id + ) + end + + let(:zoekt_replicas) { table(:zoekt_replicas) } + let(:zoekt_enabled_namespaces) { table(:zoekt_enabled_namespaces) } + let(:zoekt_enabled_namespace) { zoekt_enabled_namespaces.create!(root_namespace_id: namespace.id) } + let(:zoekt_replica) do + zoekt_replicas.create!(zoekt_enabled_namespace_id: zoekt_enabled_namespace.id, namespace_id: namespace.id) + end + + let!(:evicted_index_with_zoekt_replica) do + zoekt_indices.create!( + state: described_class::EVICTED_STATE, + zoekt_node_id: zoekt_node.id, + namespace_id: namespace.id, + zoekt_replica_id: zoekt_replica.id + ) + end + + let(:zoekt_replica2) do + zoekt_replicas.create!(zoekt_enabled_namespace_id: zoekt_enabled_namespace.id, namespace_id: namespace.id) + end + + let!(:pending_eviction_index_with_zoekt_replica) do + zoekt_indices.create!( + state: 220, + zoekt_node_id: zoekt_node.id, + namespace_id: namespace.id, + zoekt_replica_id: zoekt_replica2.id + ) + end + + describe '#up' do + it 'deletes the zoekt_replicas of the evicted indices' do + expect(zoekt_replicas.exists?(id: evicted_index_with_zoekt_replica.zoekt_replica_id)).to be true + migrate! + expect(zoekt_replicas.exists?(id: evicted_index_with_zoekt_replica.zoekt_replica_id)).to be false + expect(pending_eviction_index_with_zoekt_replica.reload.zoekt_replica_id).not_to be_nil + end + end +end diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb index cff97ac9318..dbd334e6068 100644 --- a/spec/models/ci/bridge_spec.rb +++ b/spec/models/ci/bridge_spec.rb @@ -82,6 +82,68 @@ RSpec.describe Ci::Bridge, feature_category: :continuous_integration do end end + describe '#downstream_pipeline_params' do + it 'returns an empty hash' do + expect(bridge.downstream_pipeline_params).to eq({}) + end + + context 'when there is a downstream project present' do + it 'returns cross project params' do + downstream_project = create(:project) + options = { trigger: { project: 'our/project', inputs: {} } } + bridge = create(:ci_bridge, status: :created, options: options, downstream: downstream_project) + + cross_project_params = { + project: downstream_project, + source: :pipeline, + target_revision: { + ref: downstream_project.default_branch, + variables_attributes: [] + }, + execute_params: { + ignore_skip_ci: true, + bridge: bridge, + inputs: {} + } + } + + expect(bridge.downstream_pipeline_params).to eq(cross_project_params) + end + end + + context 'when a child pipeline is triggered' do + let(:options) do + { + trigger: { + include: 'path/to/child.yml' + } + } + end + + it 'returns child params' do + child_params = { + project: project, + source: :parent_pipeline, + target_revision: { + ref: pipeline.ref, + checkout_sha: pipeline.sha, + before: pipeline.before_sha, + source_sha: pipeline.source_sha, + target_sha: pipeline.target_sha, + variables_attributes: bridge.downstream_variables + }, + execute_params: { + ignore_skip_ci: true, + bridge: bridge, + merge_request: pipeline.merge_request + } + } + + expect(bridge.downstream_pipeline_params).to eq(child_params) + end + end + end + describe '#tags' do it 'only has a bridge tag' do expect(bridge.tags).to eq [:bridge] diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb index 326b5d0bb39..899b219ec8c 100644 --- a/spec/models/personal_access_token_spec.rb +++ b/spec/models/personal_access_token_spec.rb @@ -648,7 +648,13 @@ RSpec.describe PersonalAccessToken, feature_category: :system_access do describe '.simple_sorts' do it 'includes overridden keys' do - expect(described_class.simple_sorts.keys).to include(*%w[expires_at_asc_id_desc]) + expect(described_class.simple_sorts.keys).to include(*%w[expires_asc expires_at_asc_id_desc expires_desc last_used_asc last_used_desc]) + end + + it 'returns a valid ActiveRecord::Relation for each sort' do + described_class.simple_sorts.each_value do |blk| + expect(blk.call).to be_a(ActiveRecord::Relation) + end end end @@ -679,6 +685,36 @@ RSpec.describe PersonalAccessToken, feature_category: :system_access do expect(described_class.order_expires_at_asc_id_desc).to eq [earlier_token_2, earlier_token, later_token] end end + + describe '.order_expires_at_desc_id_desc' do + let_it_be(:earlier_token_2) { create(:personal_access_token, expires_at: 2.days.ago) } + + it 'returns ordered list in combination of expires_at descending and id descending' do + expect(described_class.order_expires_at_desc_id_desc).to eq [later_token, earlier_token_2, earlier_token] + end + end + end + + describe 'ordering by last_used_at' do + let_it_be(:two_days_ago) { 2.days.ago } + let_it_be(:earlier_token) { create(:personal_access_token, last_used_at: two_days_ago) } + let_it_be(:later_token) { create(:personal_access_token, last_used_at: 1.day.ago) } + + describe '.order_last_used_at_asc_id_desc' do + let_it_be(:earlier_token_2) { create(:personal_access_token, last_used_at: two_days_ago) } + + it 'returns ordered list in combination of last_used_at ascending and id descending' do + expect(described_class.order_last_used_at_asc_id_desc).to eq [earlier_token_2, earlier_token, later_token] + end + end + + describe '.order_last_used_at_desc_id_desc' do + let_it_be(:earlier_token_2) { create(:personal_access_token, last_used_at: 2.days.ago) } + + it 'returns ordered list in combination of expires_at descending and id descending' do + expect(described_class.order_last_used_at_desc_id_desc).to eq [later_token, earlier_token_2, earlier_token] + end + end end it_behaves_like 'TokenAuthenticatable' do diff --git a/spec/requests/organizations/organizations_controller_spec.rb b/spec/requests/organizations/organizations_controller_spec.rb index bc2b26cd44c..3a49ea78853 100644 --- a/spec/requests/organizations/organizations_controller_spec.rb +++ b/spec/requests/organizations/organizations_controller_spec.rb @@ -208,30 +208,30 @@ RSpec.describe Organizations::OrganizationsController, feature_category: :cell d end end - context 'when organization has multiple projects', - quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/524222' do - let_it_be(:stale_project) do - create(:project, :public, organization: organization, last_activity_at: 3.days.ago) - .tap { |project| create(:project_member, :developer, user:, project:) } - end - - let_it_be(:recently_updated_project) do - create(:project, :public, organization: organization, last_activity_at: Date.current) - .tap { |project| create(:project_member, :developer, user:, project:) } - end + context 'when organization has multiple projects' do + let_it_be(:stale_project) { create(:project, organization: organization, developers: [user]) } + let_it_be(:recently_updated_project) { create(:project, organization: organization, developers: [user]) } before_all do + stale_project.update!(last_activity_at: 3.days.ago) + sign_in(user) end - it 'returns events from the projects with the most recent activities' do + it 'returns events from the projects with the most recent activities', :aggregate_failures do stub_const("#{described_class}::DEFAULT_RESOURCE_LIMIT", 1) get activity_organization_path(organization, format: :json) + resource_parent_path = json_response['events'].first["resource_parent"]["full_path"] + expect(json_response['events'].size).to eq(1) - expect(json_response['events'].first["resource_parent"]["full_path"]) - .to eq(recently_updated_project.full_path) + expect(resource_parent_path).to eq(recently_updated_project.full_path), <<~ERROR.squish + Expected project with path #{recently_updated_project.full_path} + (last_activity_at: #{recently_updated_project.last_activity_at}), + but got #{resource_parent_path || 'nil'}. + Stale project: #{stale_project.full_path} last_activity_at: #{stale_project.last_activity_at}). + ERROR end end diff --git a/spec/services/groups/mark_for_deletion_service_spec.rb b/spec/services/groups/mark_for_deletion_service_spec.rb new file mode 100644 index 00000000000..1e6ae15bd26 --- /dev/null +++ b/spec/services/groups/mark_for_deletion_service_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Groups::MarkForDeletionService, feature_category: :groups_and_projects do + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group) } + let(:params) { { param: true } } + let(:service) { described_class.new(group, user, params) } + + context 'when in FOSS', unless: Gitlab.ee? do + describe '#execute' do + context 'when async is true' do + it 'executes the Groups::DestroyService with the same parameters asychronously' do + expect_next_instance_of(Groups::DestroyService, group, user, params) do |destroy_service| + expect(destroy_service).to receive(:async_execute) + end + + service.execute + end + end + + context 'when async is false' do + it 'executes the Groups::DestroyService with the same parameters sychronously' do + expect_next_instance_of(Groups::DestroyService, group, user, params) do |destroy_service| + expect(destroy_service).to receive(:execute) + end + + service.execute(async: false) + end + end + end + end +end diff --git a/spec/services/projects/mark_for_deletion_service_spec.rb b/spec/services/projects/mark_for_deletion_service_spec.rb new file mode 100644 index 00000000000..74957284909 --- /dev/null +++ b/spec/services/projects/mark_for_deletion_service_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::MarkForDeletionService, feature_category: :groups_and_projects do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let(:params) { { param: true } } + let(:service) { described_class.new(project, user, params) } + + context 'when in FOSS', unless: Gitlab.ee? do + describe '#execute' do + context 'when async is true' do + it 'executes the Projects::DestroyService with the same parameters asychronously' do + expect_next_instance_of(Projects::DestroyService, project, user, params) do |destroy_service| + expect(destroy_service).to receive(:async_execute) + end + + service.execute + end + end + + context 'when async is false' do + it 'executes the Projects::DestroyService with the same parameters sychronously' do + expect_next_instance_of(Projects::DestroyService, project, user, params) do |destroy_service| + expect(destroy_service).to receive(:execute) + end + + service.execute(async: false) + end + end + end + end +end diff --git a/spec/support/helpers/reactive_caching_helpers.rb b/spec/support/helpers/reactive_caching_helpers.rb index 0b0b0622696..00192a5d587 100644 --- a/spec/support/helpers/reactive_caching_helpers.rb +++ b/spec/support/helpers/reactive_caching_helpers.rb @@ -48,6 +48,6 @@ module ReactiveCachingHelpers def expect_reactive_cache_update_queued(subject, worker_klass: ReactiveCachingWorker) expect(worker_klass) .to receive(:perform_in) - .with(subject.class.reactive_cache_refresh_interval, subject.class, subject.id) + .with(subject.class.reactive_cache_refresh_interval, subject.class.name, subject.id) end end diff --git a/spec/support/helpers/work_item_feedback_helpers.rb b/spec/support/helpers/work_item_feedback_helpers.rb deleted file mode 100644 index 3934e51d879..00000000000 --- a/spec/support/helpers/work_item_feedback_helpers.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module WorkItemFeedbackHelpers - def close_work_item_feedback_popover_if_present - return unless page.has_css?("[data-testid='work-item-feedback-popover']") - - page.within("[data-testid='work-item-feedback-popover']") do - page.find("[data-testid='close-button']").click - end - end -end diff --git a/spec/support/shared_examples/features/work_items/rolled_up_dates_drawer_shared_examples.rb b/spec/support/shared_examples/features/work_items/rolled_up_dates_drawer_shared_examples.rb index 59d63c41224..1083ad41560 100644 --- a/spec/support/shared_examples/features/work_items/rolled_up_dates_drawer_shared_examples.rb +++ b/spec/support/shared_examples/features/work_items/rolled_up_dates_drawer_shared_examples.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true RSpec.shared_examples 'work items due dates in drawer' do - include WorkItemFeedbackHelpers - let(:work_item_due_dates_selector) { '[data-testid="work-item-due-dates"]' } let(:work_item_start_due_dates_selector) { '[data-testid="work-item-start-due-dates"]' } let(:work_item_milestone_selector) { '[data-testid="work-item-milestone"]' } @@ -13,8 +11,6 @@ RSpec.shared_examples 'work items due dates in drawer' do page.refresh wait_for_all_requests - - close_work_item_feedback_popover_if_present end it 'passes axe automated accessibility testing in closed state' do diff --git a/spec/support/shared_examples/features/work_items/rolledup_dates_shared_examples.rb b/spec/support/shared_examples/features/work_items/rolledup_dates_shared_examples.rb index 807e43efd1c..25405da2b95 100644 --- a/spec/support/shared_examples/features/work_items/rolledup_dates_shared_examples.rb +++ b/spec/support/shared_examples/features/work_items/rolledup_dates_shared_examples.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true RSpec.shared_examples 'work items rolled up dates' do - include WorkItemFeedbackHelpers - let(:work_item_due_dates_selector) { '[data-testid="work-item-due-dates"]' } let(:work_item_milestone_selector) { '[data-testid="work-item-milestone"]' } @@ -28,8 +26,6 @@ RSpec.shared_examples 'work items rolled up dates' do page.refresh wait_for_all_requests - - close_work_item_feedback_popover_if_present end context 'when using inheritable dates', :sidekiq_inline do