Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-03-14 21:13:11 +00:00
parent d0ac590b65
commit c6d99422d9
74 changed files with 700 additions and 227 deletions

View File

@ -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',

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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 });

View File

@ -1,6 +1,6 @@
<script>
import { GlIcon, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
import { sprintf, __, formatNumber } from '~/locale';
import { formatNumber } from '~/locale';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
@ -55,16 +55,6 @@ export default {
additionalIpAddressCount() {
return this.managersCount - 1;
},
createdBy() {
return this.runner?.createdBy;
},
createdByImgAlt() {
const name = this.createdBy?.name;
if (name) {
return sprintf(__("%{name}'s avatar"), { name });
}
return null;
},
},
methods: {
formatNumber,

View File

@ -76,9 +76,6 @@ export default {
return I18N_REGISTER_RUNNER;
}
},
isRegistrationTokenPresent() {
return Boolean(this.registrationToken);
},
state() {
if (this.registrationToken && this.allowRegistrationToken) {
// Legacy registration with registration token can be used, will be fully removed by 18.0

View File

@ -126,9 +126,12 @@ export default {
this.refocusSelectedPlatformButton();
},
methods: {
// show() can be invoked by parent components to show the modal
// eslint-disable-next-line vue/no-unused-properties
show() {
this.$refs.modal.show();
},
// close() can be invoked by parent components to close the modal
close() {
this.$refs.modal.close();
},

View File

@ -24,11 +24,6 @@ export default {
},
},
emits: ['deleted'],
data() {
return {
deleting: false,
};
},
computed: {
buttonContent() {
if (this.compact) {

View File

@ -52,6 +52,8 @@ export default {
},
},
methods: {
// show() can be invoked by parent components to show the modal
// eslint-disable-next-line vue/no-unused-properties
show() {
this.$refs.modal.show();
},

View File

@ -30,11 +30,6 @@ export default {
type: Object,
required: true,
},
compact: {
type: Boolean,
required: false,
default: false,
},
},
emits: ['done'],
data() {

View File

@ -86,6 +86,7 @@ export default {
},
// Component API
// eslint-disable-next-line vue/no-unused-properties
refetch() {
// Refresh all of the counts here, can be called by parent component
this.$refs[TAB_COUNT_REF].forEach((countComponent) => {

View File

@ -92,6 +92,7 @@ export default {
},
// Component API
// eslint-disable-next-line vue/no-unused-properties
refetch() {
// Parent components can use this method to refresh the count
this.$apollo.queries.count.refetch();

View File

@ -6,7 +6,7 @@ import { InternalEvents } from '~/tracking';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import PageHeading from '~/vue_shared/components/page_heading.vue';
import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue';
import { DEFAULT_PLATFORM, GROUP_TYPE } from '../constants';
import { GROUP_TYPE } from '../constants';
import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage';
export default {
@ -22,11 +22,6 @@ export default {
required: true,
},
},
data() {
return {
platform: DEFAULT_PLATFORM,
};
},
methods: {
onSaved(runner) {
this.trackEvent('click_create_group_runner_button');

View File

@ -55,14 +55,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 });

View File

@ -6,7 +6,7 @@ import { InternalEvents } from '~/tracking';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import PageHeading from '~/vue_shared/components/page_heading.vue';
import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue';
import { DEFAULT_PLATFORM, GOOGLE_CLOUD_PLATFORM, PROJECT_TYPE } from '../constants';
import { PROJECT_TYPE } from '../constants';
import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage';
export default {
@ -22,11 +22,6 @@ export default {
required: true,
},
},
data() {
return {
platform: DEFAULT_PLATFORM,
};
},
methods: {
onSaved(runner) {
this.trackEvent('click_create_project_runner_button');
@ -42,7 +37,6 @@ export default {
},
},
PROJECT_TYPE,
GOOGLE_CLOUD_PLATFORM,
};
</script>

View File

@ -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') }}

View File

@ -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 {
</div>
</div>
<div
v-if="shouldShowFindAndReplaceBar"
class="gl-border gl-absolute gl-right-0 gl-z-1 gl-flex gl-w-34 gl-rounded-bl-base gl-border-r-0 gl-bg-section gl-p-3 gl-shadow-sm"
v-if="findAndReplace.shouldShowBar"
class="gl-border gl-absolute gl-right-0 gl-z-3 gl-flex gl-w-34 gl-rounded-bl-base gl-border-r-0 gl-bg-section gl-p-3 gl-shadow-sm"
data-testid="find-and-replace"
>
<gl-form-input
@ -629,7 +725,8 @@ export default {
:placeholder="__('Find')"
autofocus
data-testid="find-btn"
@keydown="handleKeyDown"
@keydown="findAndReplace_handleKeyDown"
@keyup="findAndReplace_handleKeyUp"
/>
</div>
</div>

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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(*)

View File

@ -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

View File

@ -0,0 +1 @@
15688b6b3dd8adc421d7d41159ef6b448dc9948cf922e2deed022ab46c622657

View File

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

View File

@ -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)

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

View File

@ -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

View File

@ -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). |

View File

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

View File

@ -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

View File

@ -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

View File

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

View File

@ -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

View File

@ -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`

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

@ -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:)

View File

@ -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 ""

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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', () => {

View File

@ -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 <img src="prompt">';
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 &lt;img src="<span class="js-highlight" style="background-color: orange; display: inline-block;">prompt</span>"&gt;',
);
});
});
});

View File

@ -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

View File

@ -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,

View File

@ -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"
}

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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