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.
-
-
-## 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.
-
-
## 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.
-
-
## 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.
-
-
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.
-
-
## 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.
-
-
## 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