Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d0ac590b65
commit
c6d99422d9
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -24,11 +24,6 @@ export default {
|
|||
},
|
||||
},
|
||||
emits: ['deleted'],
|
||||
data() {
|
||||
return {
|
||||
deleting: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
buttonContent() {
|
||||
if (this.compact) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -30,11 +30,6 @@ export default {
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
compact: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['done'],
|
||||
data() {
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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') }}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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(*)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
15688b6b3dd8adc421d7d41159ef6b448dc9948cf922e2deed022ab46c622657
|
||||
|
|
@ -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. |
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||

|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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). |
|
||||
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB |
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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:)
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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 <img src="<span class="js-highlight" style="background-color: orange; display: inline-block;">prompt</span>">',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue