Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-03-31 12:10:48 +00:00
parent eb239d31bf
commit 820c5f6d5c
104 changed files with 1704 additions and 915 deletions

View File

@ -1220,127 +1220,126 @@ lib/gitlab/checks/** @proglottis @toon
/lib/tasks/gitlab/password.rake @gitlab-org/manage/authentication-and-authorization/approvers
/lib/tasks/tokens.rake @gitlab-org/manage/authentication-and-authorization/approvers
^[Verify]
/app/**/ci/ @gitlab-org/maintainers/cicd-verify
/app/controllers/admin/jobs_controller.rb @gitlab-org/maintainers/cicd-verify
/app/controllers/admin/runner_projects_controller.rb @gitlab-org/maintainers/cicd-verify
/app/controllers/admin/runners_controller.rb @gitlab-org/maintainers/cicd-verify
/app/controllers/projects/artifacts_controller.rb @gitlab-org/maintainers/cicd-verify
/app/controllers/projects/build_artifacts_controller.rb @gitlab-org/maintainers/cicd-verify
/app/controllers/projects/builds_controller.rb @gitlab-org/maintainers/cicd-verify
/app/controllers/projects/jobs_controller.rb @gitlab-org/maintainers/cicd-verify
/app/controllers/runner_setup_controller.rb @gitlab-org/maintainers/cicd-verify
/app/controllers/projects/pipeline_schedules_controller.rb @gitlab-org/maintainers/cicd-verify
/app/controllers/projects/pipelines_controller.rb @gitlab-org/maintainers/cicd-verify
/app/controllers/projects/pipelines_settings_controller.rb @gitlab-org/maintainers/cicd-verify
/app/controllers/projects/runner_projects_controller.rb @gitlab-org/maintainers/cicd-verify
/app/controllers/projects/runners_controller.rb @gitlab-org/maintainers/cicd-verify
/app/controllers/projects/triggers_controller.rb @gitlab-org/maintainers/cicd-verify
/app/controllers/projects/variables_controller.rb @gitlab-org/maintainers/cicd-verify
/app/models/commit_status.rb @gitlab-org/maintainers/cicd-verify
/app/models/external_pull_request.rb @gitlab-org/maintainers/cicd-verify
/app/models/generic_commit_status.rb @gitlab-org/maintainers/cicd-verify
/app/models/namespace_ci_cd_setting.rb @gitlab-org/maintainers/cicd-verify
/app/models/project_ci_cd_setting.rb @gitlab-org/maintainers/cicd-verify
/app/presenters/commit_status_presenter.rb @gitlab-org/maintainers/cicd-verify
/app/presenters/generic_commit_status_presenter.rb @gitlab-org/maintainers/cicd-verify
/app/validators/json_schemas/build_metadata_id_tokens.json @gitlab-org/maintainers/cicd-verify
/app/views/projects/artifacts/ @gitlab-org/maintainers/cicd-verify
/app/views/projects/generic_commit_statuses/ @gitlab-org/maintainers/cicd-verify
/app/views/projects/jobs/ @gitlab-org/maintainers/cicd-verify
/app/views/projects/pipeline_schedules/ @gitlab-org/maintainers/cicd-verify
/app/views/projects/pipelines/ @gitlab-org/maintainers/cicd-verify
/app/views/projects/triggers/ @gitlab-org/maintainers/cicd-verify
/app/workers/build_hooks_worker.rb @gitlab-org/maintainers/cicd-verify
/app/workers/build_queue_worker.rb @gitlab-org/maintainers/cicd-verify
/app/workers/build_success_worker.rb @gitlab-org/maintainers/cicd-verify
/app/workers/ci_platform_metrics_update_cron_worker.rb @gitlab-org/maintainers/cicd-verify
/app/workers/create_pipeline_worker.rb @gitlab-org/maintainers/cicd-verify
/app/workers/expire_build_artifacts_worker.rb @gitlab-org/maintainers/cicd-verify
/app/workers/pipeline_hooks_worker.rb @gitlab-org/maintainers/cicd-verify
/app/workers/pipeline_metrics_worker.rb @gitlab-org/maintainers/cicd-verify
/app/workers/pipeline_notification_worker.rb @gitlab-org/maintainers/cicd-verify
/app/workers/pipeline_process_worker.rb @gitlab-org/maintainers/cicd-verify
/app/workers/pipeline_schedule_worker.rb @gitlab-org/maintainers/cicd-verify
/app/workers/run_pipeline_schedule_worker.rb @gitlab-org/maintainers/cicd-verify
/app/workers/stuck_ci_jobs_worker.rb @gitlab-org/maintainers/cicd-verify
/app/workers/update_external_pull_requests_worker.rb @gitlab-org/maintainers/cicd-verify
/lib/**/ci/ @gitlab-org/maintainers/cicd-verify
/lib/api/commit_statuses.rb @gitlab-org/maintainers/cicd-verify
/ee/app/**/ci/ @gitlab-org/maintainers/cicd-verify
/ee/app/**/merge_trains/ @gitlab-org/maintainers/cicd-verify
/ee/app/models/merge_train.rb @gitlab-org/maintainers/cicd-verify
/ee/app/finders/merge_trains_finder.rb @gitlab-org/maintainers/cicd-verify
/ee/app/services/auto_merge/add_to_merge_train_when_pipeline_succeeds_service.rb @gitlab-org/maintainers/cicd-verify
/ee/app/services/auto_merge/merge_train_service.rb @gitlab-org/maintainers/cicd-verify
/ee/app/services/system_notes/merge_train_service.rb @gitlab-org/maintainers/cicd-verify
/ee/app/controllers/ee/admin/runners_controller.rb @gitlab-org/maintainers/cicd-verify
/ee/app/controllers/ee/projects/pipelines_controller.rb @gitlab-org/maintainers/cicd-verify
/ee/app/controllers/projects/pipelines/ @gitlab-org/maintainers/cicd-verify
/ee/app/controllers/projects/subscriptions_controller.rb @gitlab-org/maintainers/cicd-verify
/ee/app/models/merge_train.rb @gitlab-org/maintainers/cicd-verify
/ee/app/helpers/ee/projects/pipeline_helper.rb @gitlab-org/maintainers/cicd-verify
/ee/app/views/ci_minutes_usage_mailer/ @gitlab-org/maintainers/cicd-verify
/ee/app/views/projects/pipelines/ @gitlab-org/maintainers/cicd-verify
/ee/app/views/projects/settings/ci_cd/ @gitlab-org/maintainers/cicd-verify
/ee/app/workers/clear_shared_runners_minutes_worker.rb @gitlab-org/maintainers/cicd-verify
/ee/lib/**/ci/ @gitlab-org/maintainers/cicd-verify
/ee/lib/ee/api/entities/merge_train.rb @gitlab-org/maintainers/cicd-verify
/spec/**/ci @gitlab-org/maintainers/cicd-verify
/spec/controllers/admin/jobs_controller_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/controllers/admin/runner_projects_controller_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/controllers/admin/runners_controller_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/controllers/projects/artifacts_controller_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/controllers/projects/jobs_controller_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/requests/runner_setup_controller_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/controllers/projects/pipeline_schedules_controller_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/requests/projects/pipelines_controller_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/controllers/projects/pipelines_settings_controller_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/controllers/projects/runners_controller_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/controllers/groups/variables_controller_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/models/commit_status_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/models/external_pull_request_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/models/generic_commit_status_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/models/namespace_ci_cd_setting_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/models/project_ci_cd_setting_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/presenters/commit_status_presenter_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/views/projects/jobs/ @gitlab-org/maintainers/cicd-verify
/spec/views/projects/pipeline_schedules/ @gitlab-org/maintainers/cicd-verify
/spec/views/projects/pipelines/ @gitlab-org/maintainers/cicd-verify
/spec/views/projects/settings/ci_cd/ @gitlab-org/maintainers/cicd-verify
/spec/workers/build_hooks_worker_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/workers/build_queue_worker_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/workers/build_success_worker_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/workers/ci_platform_metrics_update_cron_worker_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/workers/create_pipeline_worker_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/workers/expire_build_artifacts_worker_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/workers/pipeline_hooks_worker_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/workers/pipeline_metrics_worker_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/workers/pipeline_notification_worker_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/workers/pipeline_process_worker_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/workers/pipeline_schedule_worker_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/workers/run_pipeline_schedule_worker_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/workers/stuck_ci_jobs_worker_spec.rb @gitlab-org/maintainers/cicd-verify
/spec/workers/update_external_pull_requests_worker_spec.rb @gitlab-org/maintainers/cicd-verify
/ee/spec/**/ci @gitlab-org/maintainers/cicd-verify
/ee/spec/**/merge_trains @gitlab-org/maintainers/cicd-verify
/ee/spec/models/merge_train_spec.rb @gitlab-org/maintainers/cicd-verify
/ee/spec/finders/merge_trains_finder_spec.rb @gitlab-org/maintainers/cicd-verify
/ee/spec/services/auto_merge/add_to_merge_train_when_pipeline_succeeds_service_spec.rb @gitlab-org/maintainers/cicd-verify
/ee/spec/services/auto_merge/merge_train_service_spec.rb @gitlab-org/maintainers/cicd-verify
/ee/spec/services/system_notes/merge_train_service_spec.rb @gitlab-org/maintainers/cicd-verify
/ee/spec/controllers/projects/subscriptions_controller_spec.rb @gitlab-org/maintainers/cicd-verify
/ee/spec/helpers/ee/projects/pipeline_helper_spec.rb @gitlab-org/maintainers/cicd-verify
/ee/spec/workers/clear_shared_runners_minutes_worker_spec.rb @gitlab-org/maintainers/cicd-verify
/ee/spec/lib/**/ci/ @gitlab-org/maintainers/cicd-verify
/**/javascripts/jobs/ @gitlab-org/ci-cd/verify/frontend
/**/javascripts/pipelines/ @gitlab-org/ci-cd/verify/frontend
/app/assets/javascripts/ci/ @gitlab-org/ci-cd/verify/frontend
/app/assets/javascripts/ci/pipeline_schedules/ @gitlab-org/ci-cd/verify/frontend
/ee/app/assets/javascripts/ci/ @gitlab-org/ci-cd/verify/frontend
/app/assets/javascripts/token_access/ @gitlab-org/ci-cd/verify/frontend
/app/assets/javascripts/admin/application_settings/runner_token_expiration/ @gitlab-org/ci-cd/verify/frontend
/ee/app/assets/javascripts/usage_quotas/pipelines/ @gitlab-org/ci-cd/verify/frontend @fulfillment-group/utilization-group/fe
[Verify] @gitlab-org/maintainers/cicd-verify
# Verify Backend
/**/app/**/ci/
/**/lib/**/ci/
/**/spec/**/ci/
/**/app/**/merge_trains/
/**/spec/**/merge_trains/
/app/controllers/admin/jobs_controller.rb
/app/controllers/admin/runner_projects_controller.rb
/app/controllers/admin/runners_controller.rb
/app/controllers/projects/artifacts_controller.rb
/app/controllers/projects/build_artifacts_controller.rb
/app/controllers/projects/builds_controller.rb
/app/controllers/projects/jobs_controller.rb
/app/controllers/runner_setup_controller.rb
/app/controllers/projects/pipeline_schedules_controller.rb
/app/controllers/projects/pipelines_controller.rb
/app/controllers/projects/pipelines_settings_controller.rb
/app/controllers/projects/runner_projects_controller.rb
/app/controllers/projects/runners_controller.rb
/app/controllers/projects/triggers_controller.rb
/app/controllers/projects/variables_controller.rb
/app/models/commit_status.rb
/app/models/external_pull_request.rb
/app/models/generic_commit_status.rb
/app/models/namespace_ci_cd_setting.rb
/app/models/project_ci_cd_setting.rb
/app/presenters/commit_status_presenter.rb
/app/presenters/generic_commit_status_presenter.rb
/app/validators/json_schemas/build_metadata_id_tokens.json
/app/views/projects/artifacts/
/app/views/projects/generic_commit_statuses/
/app/views/projects/jobs/
/app/views/projects/pipeline_schedules/
/app/views/projects/pipelines/
/app/views/projects/triggers/
/app/workers/build_hooks_worker.rb
/app/workers/build_queue_worker.rb
/app/workers/build_success_worker.rb
/app/workers/ci_platform_metrics_update_cron_worker.rb
/app/workers/create_pipeline_worker.rb
/app/workers/expire_build_artifacts_worker.rb
/app/workers/pipeline_hooks_worker.rb
/app/workers/pipeline_metrics_worker.rb
/app/workers/pipeline_notification_worker.rb
/app/workers/pipeline_process_worker.rb
/app/workers/pipeline_schedule_worker.rb
/app/workers/run_pipeline_schedule_worker.rb
/app/workers/stuck_ci_jobs_worker.rb
/app/workers/update_external_pull_requests_worker.rb
/lib/api/commit_statuses.rb
/ee/app/models/merge_train.rb
/ee/app/finders/merge_trains_finder.rb
/ee/app/services/auto_merge/add_to_merge_train_when_pipeline_succeeds_service.rb
/ee/app/services/auto_merge/merge_train_service.rb
/ee/app/services/system_notes/merge_train_service.rb
/ee/app/controllers/ee/admin/runners_controller.rb
/ee/app/controllers/ee/projects/pipelines_controller.rb
/ee/app/controllers/projects/pipelines/
/ee/app/controllers/projects/subscriptions_controller.rb
/ee/app/models/merge_train.rb
/ee/app/helpers/ee/projects/pipeline_helper.rb
/ee/app/views/ci_minutes_usage_mailer/
/ee/app/views/projects/pipelines/
/ee/app/views/projects/settings/ci_cd/
/ee/app/workers/clear_shared_runners_minutes_worker.rb
/ee/lib/api/merge_trains.rb
/ee/lib/ee/api/entities/merge_train.rb
/ee/spec/requests/api/merge_trains_spec.rb
/spec/controllers/admin/jobs_controller_spec.rb
/spec/controllers/admin/runner_projects_controller_spec.rb
/spec/controllers/admin/runners_controller_spec.rb
/spec/controllers/projects/artifacts_controller_spec.rb
/spec/controllers/projects/jobs_controller_spec.rb
/spec/requests/runner_setup_controller_spec.rb
/spec/controllers/projects/pipeline_schedules_controller_spec.rb
/spec/requests/projects/pipelines_controller_spec.rb
/spec/controllers/projects/pipelines_settings_controller_spec.rb
/spec/controllers/projects/runners_controller_spec.rb
/spec/controllers/groups/variables_controller_spec.rb
/spec/models/commit_status_spec.rb
/spec/models/external_pull_request_spec.rb
/spec/models/generic_commit_status_spec.rb
/spec/models/namespace_ci_cd_setting_spec.rb
/spec/models/project_ci_cd_setting_spec.rb
/spec/presenters/commit_status_presenter_spec.rb
/spec/views/projects/jobs/
/spec/views/projects/pipeline_schedules/
/spec/views/projects/pipelines/
/spec/views/projects/settings/ci_cd/
/spec/workers/build_hooks_worker_spec.rb
/spec/workers/build_queue_worker_spec.rb
/spec/workers/build_success_worker_spec.rb
/spec/workers/ci_platform_metrics_update_cron_worker_spec.rb
/spec/workers/create_pipeline_worker_spec.rb
/spec/workers/expire_build_artifacts_worker_spec.rb
/spec/workers/pipeline_hooks_worker_spec.rb
/spec/workers/pipeline_metrics_worker_spec.rb
/spec/workers/pipeline_notification_worker_spec.rb
/spec/workers/pipeline_process_worker_spec.rb
/spec/workers/pipeline_schedule_worker_spec.rb
/spec/workers/run_pipeline_schedule_worker_spec.rb
/spec/workers/stuck_ci_jobs_worker_spec.rb
/spec/workers/update_external_pull_requests_worker_spec.rb
/ee/spec/models/merge_train_spec.rb
/ee/spec/finders/merge_trains_finder_spec.rb
/ee/spec/services/auto_merge/add_to_merge_train_when_pipeline_succeeds_service_spec.rb
/ee/spec/services/auto_merge/merge_train_service_spec.rb
/ee/spec/services/system_notes/merge_train_service_spec.rb
/ee/spec/controllers/projects/subscriptions_controller_spec.rb
/ee/spec/helpers/ee/projects/pipeline_helper_spec.rb
/ee/spec/workers/clear_shared_runners_minutes_worker_spec.rb
# Verify Frontend
/**/javascripts/ci/ @gitlab-org/ci-cd/verify/frontend
/**/javascripts/pipelines/ @gitlab-org/ci-cd/verify/frontend
/**/javascripts/jobs/ @gitlab-org/ci-cd/verify/frontend
/**/javascripts/token_access/ @gitlab-org/ci-cd/verify/frontend
/**/javascripts/admin/application_settings/runner_token_expiration/ @gitlab-org/ci-cd/verify/frontend
/**/javascripts/usage_quotas/pipelines/ @gitlab-org/ci-cd/verify/frontend @fulfillment-group/utilization-group/fe
[Manage::Workspace]
lib/api/entities/basic_project_details.rb @gitlab-org/manage/manage-workspace/backend-approvers

View File

@ -1 +1 @@
b8709da8be08aa79642e96dd95341cf2d9eb50d2
ebb00e313b5ad11c8fd0b32d1843c3490a43d4c9

View File

@ -411,10 +411,10 @@ export default {
{{ $options.i18n.internal }}
<gl-icon
v-gl-tooltip:tooltipcontainer.bottom
name="question"
name="question-o"
:size="16"
:title="$options.i18n.internalVisibility"
class="gl-text-gray-500"
class="gl-text-blue-500"
/>
</gl-form-checkbox>
<comment-type-dropdown

View File

@ -169,10 +169,17 @@ export const releaseDeleteMutationVariables = (state) => ({
},
});
export const formattedReleaseNotes = ({ includeTagNotes, release: { description }, tagNotes }) =>
includeTagNotes && tagNotes
? `${description}\n\n### ${s__('Releases|Tag message')}\n\n${tagNotes}\n`
export const formattedReleaseNotes = ({
includeTagNotes,
release: { description, tagMessage },
tagNotes,
showCreateFrom,
}) => {
const notes = showCreateFrom ? tagMessage : tagNotes;
return includeTagNotes && notes
? `${description}\n\n### ${s__('Releases|Tag message')}\n\n${notes}\n`
: description;
};
export const releasedAtChanged = ({ originalReleasedAt, release }) =>
originalReleasedAt !== release.releasedAt;

View File

@ -4,7 +4,6 @@ import { createAlert } from '~/alert';
import { TYPE_ISSUE } from '~/issues/constants';
import { dateInWords, formatDate, parsePikadayDate } from '~/lib/utils/datetime_utility';
import { __, sprintf } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { dateFields, dateTypes, dueDateQueries, startDateQueries, Tracking } from '../../constants';
import SidebarEditableItem from '../sidebar_editable_item.vue';
import SidebarFormattedDate from './sidebar_formatted_date.vue';
@ -31,7 +30,6 @@ export default {
SidebarFormattedDate,
SidebarInheritDate,
},
mixins: [glFeatureFlagsMixin()],
inject: ['canUpdate'],
props: {
iid: {
@ -182,12 +180,7 @@ export default {
return this.issuable.id;
},
skipIssueDueDateSubscription() {
return (
this.issuableType !== TYPE_ISSUE ||
!this.issuableId ||
this.isLoading ||
!this.glFeatures?.realTimeIssueDueDate
);
return this.issuableType !== TYPE_ISSUE || !this.issuableId || this.isLoading;
},
},
methods: {

View File

@ -1,13 +1,20 @@
<script>
import { GlIcon, GlButton, GlTooltipDirective } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { s__ } from '~/locale';
import AccessorUtilities from '~/lib/utils/accessor';
import { getTopFrequentItems, formatContextSwitcherItems } from '../utils';
import ItemsList from './items_list.vue';
export default {
components: {
GlIcon,
GlButton,
ItemsList,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
title: {
type: String,
@ -29,12 +36,16 @@ export default {
data() {
return {
cachedFrequentItems: [],
isItemsListEditable: false,
};
},
computed: {
isEmpty() {
return !this.cachedFrequentItems.length;
},
allowItemsEditing() {
return !this.isEmpty && AccessorUtilities.canUseLocalStorage();
},
},
created() {
this.getItemsFromLocalStorage();
@ -52,6 +63,27 @@ export default {
Sentry.captureException(e);
}
},
toggleItemsListEditablity() {
this.isItemsListEditable = !this.isItemsListEditable;
},
handleItemRemove(item) {
try {
// Remove item from local storage
const parsedCachedFrequentItems = JSON.parse(localStorage.getItem(this.storageKey));
localStorage.setItem(
this.storageKey,
JSON.stringify(parsedCachedFrequentItems.filter((i) => i.id !== item.id)),
);
// Update the list
this.cachedFrequentItems = this.cachedFrequentItems.filter((i) => i.id !== item.id);
} catch (e) {
Sentry.captureException(e);
}
},
},
i18n: {
frequentItemsEditToggle: s__('Navigation|Toggle edit mode'),
},
};
</script>
@ -61,14 +93,32 @@ export default {
<div
data-testid="list-title"
aria-hidden="true"
class="gl-text-transform-uppercase gl-text-secondary gl-font-weight-bold gl-font-xs gl-line-height-12 gl-letter-spacing-06em gl-my-3"
class="gl-display-flex gl-align-items-center gl-text-transform-uppercase gl-text-secondary gl-font-weight-bold gl-font-xs gl-line-height-12 gl-letter-spacing-06em gl-my-3"
>
{{ title }}
<span class="gl-flex-grow-1">{{ title }}</span>
<gl-button
v-if="allowItemsEditing"
v-gl-tooltip.left
size="small"
category="tertiary"
:aria-label="$options.i18n.frequentItemsEditToggle"
:title="$options.i18n.frequentItemsEditToggle"
:class="{ 'gl-bg-gray-100!': isItemsListEditable }"
class="gl-p-2!"
@click="toggleItemsListEditablity"
>
<gl-icon name="pencil" :class="{ 'gl-text-gray-900!': isItemsListEditable }" />
</gl-button>
</div>
<div v-if="isEmpty" data-testid="empty-text" class="gl-text-gray-500 gl-font-sm gl-my-3">
{{ pristineText }}
</div>
<items-list :aria-label="title" :items="cachedFrequentItems">
<items-list
:aria-label="title"
:items="cachedFrequentItems"
:editable="isItemsListEditable"
@remove-item="handleItemRemove"
>
<template #view-all-items>
<slot name="view-all-items"></slot>
</template>

View File

@ -1,18 +1,29 @@
<script>
import { GlIcon, GlButton, GlTooltipDirective } from '@gitlab/ui';
import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
import NavItem from './nav_item.vue';
export default {
components: {
GlIcon,
GlButton,
ProjectAvatar,
NavItem,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
items: {
type: Array,
required: false,
default: () => [],
},
editable: {
type: Boolean,
required: false,
default: false,
},
},
};
</script>
@ -34,6 +45,21 @@ export default {
aria-hidden="true"
/>
</template>
<template #actions>
<gl-button
v-if="editable"
v-gl-tooltip.left
size="small"
category="tertiary"
:aria-label="__('Remove')"
:title="__('Remove')"
class="gl-align-self-center gl-p-1! gl-absolute gl-right-4"
data-testid="item-remove"
@click.stop.prevent="$emit('remove-item', item)"
>
<gl-icon name="close" />
</gl-button>
</template>
</nav-item>
<slot name="view-all-items"></slot>
</ul>

View File

@ -132,6 +132,7 @@ export default {
{{ item.subtitle }}
</div>
</div>
<slot name="actions"></slot>
<span v-if="isSection || hasPill" class="gl-flex-grow-1 gl-text-right gl-mr-3">
<gl-badge v-if="hasPill" size="sm" variant="info">
{{ pillData }}

View File

@ -5,14 +5,11 @@ export const DEFAULT_SNOWPLOW_OPTIONS = {
hostname: window.location.hostname,
cookieDomain: window.location.hostname,
appId: '',
userFingerprint: false,
respectDoNotTrack: true,
forceSecureTracker: true,
eventMethod: 'post',
contexts: { webPage: true, performanceTiming: true },
formTracking: false,
linkClickTracking: false,
pageUnloadTimer: 10,
formTrackingConfig: {
forms: { allow: [] },
fields: { allow: [] },

View File

@ -26,7 +26,14 @@ export function dispatchSnowplowEvent(
}
try {
window.snowplow('trackStructEvent', category, action, label, property, value, contexts);
window.snowplow('trackStructEvent', {
category,
action,
label,
property,
value,
context: contexts,
});
return true;
} catch (error) {
Sentry.captureException(error);

View File

@ -7,7 +7,7 @@ export { Tracking as default };
/**
* Tracker initialization as defined in:
* https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/javascript-trackers/javascript-tracker/javascript-tracker-v2/tracker-setup/initializing-a-tracker-2/.
* https://docs.snowplow.io/docs/collecting-data/collecting-from-own-applications/javascript-trackers/javascript-tracker/javascript-tracker-v3/tracker-setup/initialization-options/.
* It also dispatches any event emitted before its execution.
*
* @returns {undefined}
@ -42,13 +42,19 @@ export function initDefaultTrackers() {
// must be before initializing the trackers
Tracking.setAnonymousUrls();
window.snowplow('enableActivityTracking', 30, 30);
window.snowplow('enableActivityTracking', {
minimumVisitLength: 30,
heartbeatDelay: 30,
});
// must be after enableActivityTracking
const standardContext = getStandardContext();
const experimentContexts = getAllExperimentContexts();
// To not expose personal identifying information, the page title is hardcoded as `GitLab`
// See: https://gitlab.com/gitlab-org/gitlab/-/issues/345243
window.snowplow('trackPageView', 'GitLab', [standardContext, ...experimentContexts]);
window.snowplow('trackPageView', {
title: 'GitLab',
context: [standardContext, ...experimentContexts],
});
window.snowplow('setDocumentTitle', 'GitLab');
if (window.snowplowOptions.formTracking) {

View File

@ -207,14 +207,18 @@ export const Tracker = {
const mappedConfig = {};
if (config.forms) {
mappedConfig.forms = renameKey(config.forms, 'allow', 'whitelist');
mappedConfig.forms = renameKey(config.forms, 'allow', 'allowlist');
}
if (config.fields) {
mappedConfig.fields = renameKey(config.fields, 'allow', 'whitelist');
mappedConfig.fields = renameKey(config.fields, 'allow', 'allowlist');
}
const enabler = () => window.snowplow('enableFormTracking', mappedConfig, userProvidedContexts);
const enabler = () =>
window.snowplow('enableFormTracking', {
options: mappedConfig,
context: userProvidedContexts,
});
if (document.readyState === 'complete') {
enabler();

View File

@ -192,8 +192,8 @@ pre.code,
}
&.hll {
--highlight-border-color: #{$orange-200};
background-color: $orange-50;
--highlight-border-color: #{$blue-300};
background-color: $blue-50;
}
}
@ -247,8 +247,8 @@ pre.code,
}
&.hll {
--highlight-border-color: #{$orange-200};
background-color: $orange-50;
--highlight-border-color: #{$blue-300};
background-color: $blue-50;
}
}
@ -269,8 +269,8 @@ pre.code,
}
&.hll {
--highlight-border-color: #{$orange-200};
background-color: $orange-50;
--highlight-border-color: #{$blue-300};
background-color: $blue-50;
}
}
}

View File

@ -180,6 +180,10 @@ $tabs-holder-z-index: 250;
.content + .content {
@include gl-border-t;
}
.notes-content {
border: 0;
}
}
&.inline-diff-view {

View File

@ -67,7 +67,6 @@ class Projects::IssuesController < Projects::ApplicationController
push_force_frontend_feature_flag(:work_items_mvc, project&.work_items_mvc_feature_flag_enabled?)
push_force_frontend_feature_flag(:work_items_mvc_2, project&.work_items_mvc_2_feature_flag_enabled?)
push_frontend_feature_flag(:epic_widget_edit_confirmation, project)
push_frontend_feature_flag(:real_time_issue_due_date, project)
end
around_action :allow_gitaly_ref_name_caching, only: [:discussions]

View File

@ -20,7 +20,7 @@ class GitlabSchema < GraphQL::Schema
use Gitlab::Graphql::GenericTracing
use Gitlab::Graphql::Tracers::TimerTracer
use GraphQL::Subscriptions::ActionCableSubscriptions
use Gitlab::Graphql::Subscriptions::ActionCableWithLoadBalancing
use BatchLoader::GraphQL
use Gitlab::Graphql::Pagination::Connections
use Gitlab::Graphql::Timeout, max_seconds: Gitlab.config.gitlab.graphql_timeout

View File

@ -1,8 +1,8 @@
---
name: real_time_issue_due_date
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114927
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/397720
name: ci_includable_files_interpolation
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113211
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/399146
milestone: '15.11'
type: development
group: group::application performance
group: group::pipeline authoring
default_enabled: false

View File

@ -7,7 +7,7 @@ product_stage: create
product_group: code_review
product_category: code_review
value_type: number
status: active
status: removed
time_frame: 28d
data_source: redis_hll
distribution:
@ -19,3 +19,5 @@ tier:
- ultimate
performance_indicator_type: []
milestone: "<13.9"
milestone_removed: "15.10"
removed_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/115037

View File

@ -725,7 +725,7 @@ Depending on your installation method, this file is located at:
- Omnibus GitLab: `/var/log/gitlab/gitlab-rails/importer.log`
- Installations from source: `/home/git/gitlab/log/importer.log`
It logs the progress of the import process.
This file logs the progress of [project imports and migrations](../../user/project/import/index.md).
## `exporter.log`
@ -821,6 +821,8 @@ Depending on your installation method, this file is located at:
- Omnibus GitLab: `/var/log/gitlab/gitlab-rails/migrations.log`
- Installations from source: `/home/git/gitlab/log/migrations.log`
This file logs the progress of [database migrations](../raketasks/maintenance.md#display-status-of-database-migrations).
## `mail_room_json.log` (default)
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/19186) in GitLab 12.6.

View File

@ -158,7 +158,7 @@ CT: 297 ROUTE: /api/:version/projects/:id/repository/tags DURS: 731.39,
CT: 190 ROUTE: /api/:version/projects/:id/repository/commits DURS: 1079.02, 979.68, 958.21
```
### Print top API user agents
#### Print top API user agents
```shell
jq --raw-output '[.route, .ua] | @tsv' api_json.log | sort | uniq -c | sort -n
@ -180,9 +180,20 @@ if the output contains many:
You can also [use `fast-stats top`](#parsing-gitlab-logs-with-jq) to extract performance statistics.
### Parsing `gitlab-rails/importer.log`
To troubleshoot [project imports](../../administration/raketasks/project_import_export.md) or
[migrations](../../user/project/import/index.md), run this command:
```shell
jq 'select(.project_path == "<namespace>/<project>").error_messages' importer.log
```
For common issues, see [troubleshooting](../../administration/raketasks/project_import_export.md#troubleshooting).
### Parsing `gitlab-workhorse/current`
### Print top Workhorse user agents
#### Print top Workhorse user agents
```shell
jq --raw-output '[.uri, .user_agent] | @tsv' current | sort | uniq -c | sort -n

View File

@ -30,7 +30,7 @@ by default:
| PgBouncer exporter | No | Port | X | 9188 |
| GitLab Exporter | Yes | Port | X | 9168 |
| Sidekiq exporter | Yes | Port | X | 8082 |
| Sidekiq health check | No | Port | X | 8092[^Sidekiq-health] |
| Sidekiq health check | Yes | Port | X | 8092[^Sidekiq-health] |
| Web exporter | No | Port | X | 8083 |
| Geo PostgreSQL | No | Socket | Port (5431) | X |
| Redis Sentinel | No | Port | X | 26379 |

View File

@ -11,6 +11,11 @@ info: To determine the technical writer assigned to the Stage/Group associated w
With the group migration by direct transfer API, you can start and view the progress of migrations initiated with
[group migration by direct transfer](../user/group/import/index.md#migrate-groups-by-direct-transfer-recommended).
## Prerequisites
For information on prerequisites for group migration by direct transfer API, see
prerequisites for [migrating groups by direct transfer](../user/group/import/index.md#prerequisites).
## Start a new group migration
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66353) in GitLab 14.2.

View File

@ -286,6 +286,24 @@ We've asked for the note details, but it doesn't exist anymore, so we get `null`
More about mutations:
[GraphQL Documentation](https://graphql.org/learn/queries/#mutations).
### Update project settings
You can update multiple project settings in a single GraphQL mutation.
This example is a workaround for [the major change](../../update/deprecations.md#default-cicd-job-token-ci_job_token-scope-changed)
in `CI_JOB_TOKEN` scoping behavior.
```graphql
mutation DisableCI_JOB_TOKENscope {
projectCiCdSettingsUpdate(input:{fullPath: "<namespace>/<project-name>", inboundJobTokenScopeEnabled: false, jobTokenScopeEnabled: false}) {
ciCdSettings {
inboundJobTokenScopeEnabled
jobTokenScopeEnabled
}
errors
}
}
```
### Introspective queries
Clients can query the GraphQL endpoint for information about its own schema.

View File

@ -6,8 +6,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Group import and export API **(FREE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20353) in GitLab 12.8.
Use the group import and export API to export a group structure and import it to a new location.
When you use the group import and export API with the [project import and export API](project_import_export.md), you can preserve connections with
group-level relationships, such as connections between project issues and group epics.
@ -29,6 +27,11 @@ If you import groups into a parent group, the subgroups inherit by default a sim
To preserve the member list and their respective permissions on imported groups, review the users in these groups. Make sure these users exist before importing the desired groups.
## Prerequisites
For information on prerequisites for group import and export API, see prerequisites for
[migrating groups by uploading an export file](../user/group/import/index.md#preparation).
## Schedule new export
Start a new group export.

View File

@ -10,16 +10,16 @@ Use the Import API to import repositories from GitHub or Bitbucket Server.
Related APIs include:
- [Group migration by direct transfer API](bulk_imports.md)
- [Group import and export API](group_import_export.md)
- [Project import and export API](project_import_export.md)
- [Group migration by direct transfer API](bulk_imports.md).
- [Group import and export API](group_import_export.md).
- [Project import and export API](project_import_export.md).
## Prerequisites
For information on prerequisites for using the Import API, see:
- [Prerequisites for GitHub importer](../user/project/import/github.md#prerequisites)
- [Prerequisites for Bitbucket Server importer](../user/project/import/bitbucket_server.md#import-your-bitbucket-repositories)
- [Prerequisites for GitHub importer](../user/project/import/github.md#prerequisites).
- [Prerequisites for Bitbucket Server importer](../user/project/import/bitbucket_server.md#import-your-bitbucket-repositories).
## Import repository from GitHub

View File

@ -45,7 +45,7 @@ GET /groups/:id/-/debian_distributions
| `suite` | string | no | Filter with specific `suite`. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/debian_distributions"
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/-/debian_distributions"
```
Example response:
@ -86,7 +86,7 @@ GET /groups/:id/-/debian_distributions/:codename
| `codename` | integer | yes | The `codename` of a distribution. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/debian_distributions/unstable"
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/-/debian_distributions/unstable"
```
Example response:
@ -125,7 +125,7 @@ GET /groups/:id/-/debian_distributions/:codename/key.asc
| `codename` | integer | yes | The `codename` of a distribution. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/debian_distributions/unstable/key.asc"
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/-/debian_distributions/unstable/key.asc"
```
Example response:
@ -170,7 +170,7 @@ POST /groups/:id/-/debian_distributions
| `architectures` | architectures | no | The new Debian distribution's list of architectures. |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/debian_distributions?codename=sid"
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/-/debian_distributions?codename=sid"
```
Example response:
@ -217,7 +217,7 @@ PUT /groups/:id/-/debian_distributions/:codename
| `architectures` | architectures | no | The Debian distribution's new list of architectures. |
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/debian_distributions/unstable?suite=new-suite&valid_time_duration_seconds=604800"
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/-/debian_distributions/unstable?suite=new-suite&valid_time_duration_seconds=604800"
```
Example response:
@ -256,5 +256,5 @@ DELETE /groups/:id/-/debian_distributions/:codename
| `codename` | integer | yes | The codename of the Debian distribution. |
```shell
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/debian_distributions/unstable"
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/-/debian_distributions/unstable"
```

View File

@ -8,14 +8,16 @@ info: To determine the technical writer assigned to the Stage/Group associated w
Use the project import and export API to import and export projects using file transfers.
For more information, see:
- [Migrating projects using file exports](../user/project/settings/import_export.md)
- [Project import and export Rake tasks](../administration/raketasks/project_import_export.md)
Before using the project import and export API, you might want to use the
[group import and export API](group_import_export.md).
## Prerequisites
For information on prerequisites for project import and export API, see:
- Prerequisites for [project export](../user/project/settings/import_export.md#export-a-project-and-its-data).
- Prerequisites for [project import](../user/project/settings/import_export.md#import-a-project-and-its-data).
## Schedule an export
Start a new export.
@ -444,3 +446,8 @@ GitHub and how many were already imported:
}
}
```
## Related topics
- [Migrating projects using file exports](../user/project/settings/import_export.md).
- [Project import and export Rake tasks](../administration/raketasks/project_import_export.md).

View File

@ -238,6 +238,8 @@ CI job token failures are usually shown as responses like `404 Not Found` or sim
While troubleshooting CI/CD job token authentication issues, be aware that:
- A [GraphQL example mutation](../../api/graphql/getting_started.md#update-project-settings)
is available to toggle the inbound outbound scope settings per project.
- When the [CI/CD job token scopes](#configure-cicd-job-token-access) are enabled,
and the job token is being used to access a different project:
- The user that executes the job must be a member of the project that is being accessed.

View File

@ -30,6 +30,9 @@ In addition to the four primary topic types, we also have:
If inline links are not sufficient, you can create a topic called **Related topics**
and include an unordered list of related topics. This topic should be above the Troubleshooting section.
Links in this section should be brief and scannable. They are usually not
full sentences, and so should not end in a period.
```markdown
## Related topics

View File

@ -2139,7 +2139,7 @@ instead of `email`, let GitLab know by setting it on your configuration:
By default, the local part of the email address in the SAML response is used to
generate the user's GitLab username.
Configure `nickname` in `attribute_statements` to specify one or more attributes that contain a user's desired username:
Configure [`username` or `nickname`](omniauth.md#per-provider-configuration) in `attribute_statements` to specify one or more attributes that contain a user's desired username:
::Tabs

View File

@ -30,14 +30,21 @@ is usually not allowed to create or delete the SQL database needed to import
data into (`gitlabhq_production`). All existing data is either erased
(SQL) or moved to a separate directory (such as repositories and uploads).
To restore a backup, you must restore `/etc/gitlab/gitlab-secrets.json`
(for Omnibus packages) or `/home/git/gitlab/.secret` (for installations from
source). This file contains the database encryption key,
[CI/CD variables](../ci/variables/index.md), and
To restore a backup, **you must also restore the GitLab secrets**.
These include the database encryption key, [CI/CD variables](../ci/variables/index.md), and
variables used for [two-factor authentication](../user/profile/account/two_factor_authentication.md).
If you fail to restore this encryption key file along with the application data
backup, users with two-factor authentication enabled and GitLab Runner
loses access to your GitLab server.
Without the keys, [multiple issues occur](backup_restore.md#when-the-secrets-file-is-lost),
including loss of access by users with [two-factor authentication enabled](../user/profile/account/two_factor_authentication.md),
and GitLab Runners cannot log in.
Restore:
- `/etc/gitlab/gitlab-secrets.json` (Linux package)
- `/home/git/gitlab/.secret` (self-compiled installations)
- Rails secret (cloud-native GitLab)
- [This can be converted to the Linux package format](https://docs.gitlab.com/charts/installation/migration/helm_to_package.html), if required.
You may also want to restore your previous `/etc/gitlab/gitlab.rb` (for Omnibus packages)
or `/home/git/gitlab/config/gitlab.yml` (for installations from source) and
@ -155,9 +162,34 @@ the restore target directories are empty.
For both these installation types, the backup tarball has to be available in
the backup location (default location is `/var/opt/gitlab/backups`).
If you use Docker Swarm, [first disable the health check](#restore-gitlab-from-backup-using-docker-swarm).
### Restore for Helm chart installations
For Docker installations, the restore task can be run from host:
The GitLab Helm chart uses the process documented in
[restoring a GitLab Helm chart installation](https://docs.gitlab.com/charts/backup-restore/restore.html#restoring-a-gitlab-installation)
### Restore for Docker image installations
If you're using [Docker Swarm](../install/docker.md#install-gitlab-using-docker-swarm-mode),
the container might restart during the restore process because Puma is shut down,
and so the container health check fails. To work around this problem,
temporarily disable the health check mechanism.
1. Edit `docker-compose.yml`:
```yaml
healthcheck:
disable: true
```
1. Deploy the stack:
```shell
docker stack deploy --compose-file docker-compose.yml mystack
```
For more information, see [issue 6846](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6846 "GitLab restore can fail owing to `gitlab-healthcheck`").
The restore task can be run from the host:
```shell
# Stop the processes that are connected to the database
@ -177,31 +209,6 @@ docker restart <name of container>
docker exec -it <name of container> gitlab-rake gitlab:check SANITIZE=true
```
Users of GitLab 12.1 and earlier should use the command `gitlab-rake gitlab:backup:create` instead.
WARNING:
`gitlab-rake gitlab:backup:restore` doesn't set the correct file system
permissions on your Registry directory. This is a [known issue](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/62759).
In GitLab 12.2 or later, you can use `gitlab-backup restore` to avoid this
issue.
The GitLab Helm chart uses a different process, documented in
[restoring a GitLab Helm chart installation](https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/backup-restore/restore.md).
### Restore GitLab from backup using Docker Swarm
Docker Swarm might restart the container during the restore process because Puma is shut down,
and so the container health check fails. To work around this problem, disable the health check
mechanism in Docker compose file:
```yaml
healthcheck:
disable: true
```
Read more in issue #6846,
[GitLab restore can fail owing to `gitlab-healthcheck`](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6846).
## Restore for installation from source
First, ensure your backup tar file is in the backup directory described in the

View File

@ -67,7 +67,8 @@ billable user, with the following exceptions:
[blocked users](../../user/admin_area/moderate_users.md#block-a-user) don't count as billable users in the current subscription. When they are either deactivated or blocked they release a _billable user_ seat. However, they may
count toward overages in the subscribed seat count.
- Users who are [pending approval](../../user/admin_area/moderate_users.md#users-pending-approval).
- Members with the [Guest role on an Ultimate subscription](#free-guest-users).
- Users with only the [Minimal Access role](../../user/permissions.md#users-with-minimal-access) on self-managed Ultimate subscriptions or any GitLab.com subscriptions.
- Users with only the [Guest or Minimal Access roles on an Ultimate subscription](#free-guest-users).
- Users without project or group memberships on an Ultimate subscription.
- GitLab-created service accounts:
- [Ghost User](../../user/profile/account/delete_account.md#associated-records).

View File

@ -45,7 +45,7 @@ issues, epics, and more.
| [Create a project from a template](https://gitlab.com/projects/new#create_from_template) | Choose a project template and create a project with files to get you started. | |
| [Migrate to GitLab](../user/project/import/index.md) | If you are coming to GitLab from another platform, you can import or convert your projects. | |
| [Run an agile iteration](agile_sprint.md) | Use group, projects, and iterations to run an agile development iteration. |
| [Epics and Issue Boards](https://www.youtube.com/watch?v=I1bFIAQBHB8) | Find out how to use epics and issue boards for project management. | |
| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Epics and Issue Boards](https://www.youtube.com/watch?v=I1bFIAQBHB8) | Find out how to use epics and issue boards for project management. | |
## Use CI/CD pipelines

View File

@ -173,7 +173,9 @@ For authentication CI/CD variables, see [Authentication](authentication.md).
| `DAST_BROWSER_ALLOWED_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are considered in scope when crawled. By default the `DAST_WEBSITE` hostname is included in the allowed hosts list. Headers set using `DAST_REQUEST_HEADERS` are added to every request made to these hostnames. |
| `DAST_BROWSER_COOKIES` | dictionary | `abtesting_group:3,region:locked` | A cookie name and value to be added to every request. |
| `DAST_BROWSER_CRAWL_GRAPH` | boolean | `true` | Set to `true` to generate an SVG graph of navigation paths visited during crawl phase of the scan. You must also define `gl-dast-crawl-graph.svg` as a CI job artifact to be able to access the generated graph. |
| `DAST_BROWSER_DEVTOOLS_LOG` | string | `Default:messageAndBody,truncate:2000` | Set to log protocol messages between DAST and the Chromium browser. | |
| `DAST_BROWSER_CRAWL_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `5m` | The maximum amount of time to wait for the crawl phase of the scan to complete. Defaults to `24h`. |
| `DAST_BROWSER_DEVTOOLS_LOG` | string | `Default:messageAndBody,truncate:2000` | Set to log protocol messages between DAST and the Chromium browser. |
| `DAST_BROWSER_DOM_READY_AFTER_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `200ms` | Define how long to wait for updates to the DOM before checking a page is stable. Defaults to `500ms`. |
| `DAST_BROWSER_ELEMENT_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `600ms` | The maximum amount of time to wait for an element before determining it is ready for analysis. |
| `DAST_BROWSER_EXCLUDED_ELEMENTS` | selector | `a[href='2.html'],css:.no-follow` | Comma-separated list of selectors that are ignored when scanning. |
| `DAST_BROWSER_EXCLUDED_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are considered excluded and connections are forcibly dropped. |
@ -192,6 +194,7 @@ For authentication CI/CD variables, see [Authentication](authentication.md).
| `DAST_BROWSER_NUMBER_OF_BROWSERS` | number | `3` | The maximum number of concurrent browser instances to use. For shared runners on GitLab.com, we recommended a maximum of three. Private runners with more resources may benefit from a higher number, but are likely to produce little benefit after five to seven instances. |
| `DAST_BROWSER_PAGE_LOADING_SELECTOR` | selector | `css:#page-is-loading` | Selector that when is no longer visible on the page, indicates to the analyzer that the page has finished loading and the scan can continue. Cannot be used with `DAST_BROWSER_PAGE_READY_SELECTOR`. |
| `DAST_BROWSER_PAGE_READY_SELECTOR` | selector | `css:#page-is-ready` | Selector that when detected as visible on the page, indicates to the analyzer that the page has finished loading and the scan can continue. Cannot be used with `DAST_BROWSER_PAGE_LOADING_SELECTOR`. |
| `DAST_BROWSER_PASSIVE_CHECK_WORKERS` | int | `5` | Number of workers that passive scan in parallel. Recommend setting to the number of available CPUs. |
| `DAST_BROWSER_SCAN` | boolean | `true` | Required to be `true` to run a browser-based scan. |
| `DAST_BROWSER_SEARCH_ELEMENT_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `3s` | The maximum amount of time to allow the browser to search for new elements or user actions. |
| `DAST_BROWSER_STABILITY_TIMEOUT` | [Duration string](https://pkg.go.dev/time#ParseDuration) | `7s` | The maximum amount of time to wait for a browser to consider a page loaded and ready for analysis. |
@ -232,9 +235,14 @@ This can come at a cost of increased scan time.
You can manage the trade-off between coverage and scan time with the following measures:
- Vertically scale the runner and use a higher number of browsers with the [variable](#available-cicd-variables) `DAST_BROWSER_NUMBER_OF_BROWSERS`. The default is `3`.
- Limit the number of actions executed by the browser with the [variable](#available-cicd-variables) `DAST_BROWSER_MAX_ACTIONS`. The default is `10,000`.
- Limit the page depth that the browser-based crawler checks coverage on with the [variable](#available-cicd-variables) `DAST_BROWSER_MAX_DEPTH`. The crawler uses a breadth-first search strategy, so pages with smaller depth are crawled first. The default is `10`.
- Vertically scale the runner and use a higher number of browsers with [variable](#available-cicd-variables) `DAST_BROWSER_NUMBER_OF_BROWSERS`. The default is `3`.
- Limit the time taken to crawl the target application with the [variable](#available-cicd-variables) `DAST_BROWSER_CRAWL_TIMEOUT`. The default is `24h`. Scans continue with passive and active checks when the crawler times out.
- Build the crawl graph with the [variable](#available-cicd-variables) `DAST_BROWSER_CRAWL_GRAPH` to see what pages are being crawled.
- Prevent pages from being crawled using the [variable](#available-cicd-variables) `DAST_EXCLUDE_URLS`.
- Prevent elements being selected using the [variable](#available-cicd-variables) `DAST_BROWSER_EXCLUDED_ELEMENTS`. Use with caution, as defining this variable causes an extra lookup for each page crawled.
- If the target application has minimal or fast rendering, consider reducing the [variable](#available-cicd-variables) `DAST_BROWSER_DOM_READY_AFTER_TIMEOUT` to a smaller value. The default is `500ms`.
## Timeouts

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -421,7 +421,7 @@ To remove an issue from an epic:
The **Remove issue** warning appears.
1. Select **Remove**.
![List of issues assigned to an epic](img/issue_list_v13_1.png)
![List of issues assigned to an epic](img/issue_list_v15_11.png)
### Reorder issues assigned to an epic

View File

@ -98,8 +98,11 @@ with one of the following:
## Create a Distribution
On the project-level, Debian packages are published using *Debian Distributions*. To publish
packages on the group level, create a distribution with the same `codename`.
At the project level, Debian packages are published with **Debian distributions**. At the
group level, Debian packages are aggregated from the projects in the group provided that:
- The project visibility is set to `public`.
- The Debian `codename` for the group matches the Debian `codename` for the project.
To create a project-level distribution using a personal access token:

View File

@ -430,7 +430,7 @@ For more information, see
Owners can add members with a "minimal access" role to a root group. Such users do not:
- Count as licensed seats.
- Count as licensed seats on self-managed Ultimate subscriptions or any GitLab.com subscriptions.
- Automatically have access to projects and subgroups in that root group.
Owners must explicitly add these "minimal access" users to the specific subgroups and

View File

@ -79,6 +79,7 @@ Prerequisite:
| Jitsu project ID | `g0maofw84gx5sjxgse2k` |
| Jitsu administrator email | `jitsu.admin@gitlab.com` |
| Jitsu administrator password | `<your_password>` |
| Collector host | `https://collector.gitlab.com` |
| Clickhouse URL | `https://<username>:<password>@clickhouse.gitlab.com:8123` |
| Cube API URL | `https://cube.gitlab.com` |
| Cube API key | `25718201b3e9...ae6bbdc62dbb` |

View File

@ -156,7 +156,7 @@ To set a default description template for merge requests, either:
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Settings > Merge requests**.
1. In the **Merge commit message template** section, fill in **Default description template for merge requests**.
1. In the **Default description template for merge requests** section, fill in the text area.
1. Select **Save changes**.
To set a default description template for issues, either:
@ -167,9 +167,9 @@ To set a default description template for issues, either:
- Users on GitLab Premium and higher: set the default template in project settings:
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Settings**.
1. Expand **Default issue template**.
1. Fill in the **Default description template for issues** text area.
1. On the left sidebar, select **Settings > General**.
1. Expand **Default description template for issues**.
1. Fill in the text area.
1. Select **Save changes**.
Because GitLab merge request and issues support [Markdown](../markdown.md), you can use it to format

View File

@ -172,3 +172,14 @@ For more information, see:
including settings that need checking afterwards and other limitations.
For support, customers must enter into a paid engagement with GitLab Professional Services.
## Troubleshooting
### Imported repository is missing branches
If an imported repository does not contain all branches of the source repository:
1. Set the [environment variable](../../../administration/logs/index.md#override-default-log-level) `IMPORT_DEBUG=true`.
1. Retry the import with a [different group, subgroup, or project name](https://about.gitlab.com/releases/2023/02/22/gitlab-15-9-released/#re-import-projects-from-external-providers).
1. If some branches are still missing, inspect [`importer.log`](../../../administration/logs/index.md#importerlog)
(for example, with [`jq`](../../../administration/logs/log_parsing.md#parsing-gitlab-railsimporterlog)).

View File

@ -162,9 +162,8 @@ merge commit. Verify it contains no unintended changes and doesn't break your bu
## Related topics
- [Introduction to Git rebase and force-push](../../../topics/git/git_rebase.md).
- [Git GUI apps](https://git-scm.com/downloads/guis) to help you visualize the
differences between branches and resolve them.
- [Introduction to Git rebase and force-push](../../../topics/git/git_rebase.md)
- [Git applications for visualizing the Git workflow](https://git-scm.com/downloads/guis)
<!-- ## Troubleshooting

View File

@ -427,12 +427,7 @@ To view the HTML and other assets that were created for the site,
## Related topics
For more information, see the following blog posts.
- Use GitLab CI/CD `environments` to
[deploy your web app to staging and production](https://about.gitlab.com/blog/2021/02/05/ci-deployment-and-environments/).
- Learn how to run jobs
[sequentially, in parallel, or build a custom pipeline](https://about.gitlab.com/blog/2020/12/10/basics-of-gitlab-ci-updated/).
- Learn [how to pull specific directories from different projects](https://about.gitlab.com/blog/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
to deploy this website, <https://docs.gitlab.com>.
- Learn [how to use GitLab Pages to produce a code coverage report](https://about.gitlab.com/blog/2016/11/03/publish-code-coverage-report-with-gitlab-pages/).
- [Deploy your web app to staging and production](https://about.gitlab.com/blog/2021/02/05/ci-deployment-and-environments/)
- [Run jobs sequentially, in parallel, or build a custom pipeline](https://about.gitlab.com/blog/2020/12/10/basics-of-gitlab-ci-updated/)
- [Pull specific directories from different projects](https://about.gitlab.com/blog/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
- [Use GitLab Pages to produce a code coverage report](https://about.gitlab.com/blog/2016/11/03/publish-code-coverage-report-with-gitlab-pages/)

View File

@ -227,10 +227,10 @@ automatically when a merge request was merged.
## Related topics
- [Protected branches](../../protected_branches.md) user documentation.
- [Branches API](../../../../api/branches.md), for information on operating on repository branches using the GitLab API.
- [Protected Branches API](../../../../api/protected_branches.md).
- [Getting started with Git](../../../../topics/git/index.md) and GitLab.
- [Protected branches](../../protected_branches.md)
- [Branches API](../../../../api/branches.md)
- [Protected Branches API](../../../../api/protected_branches.md)
- [Getting started with Git](../../../../topics/git/index.md)
## Troubleshooting

View File

@ -134,8 +134,8 @@ You can unlink your fork from its upstream project in the [advanced settings](..
## Related topics
- GitLab blog post: [How to keep your fork up to date with its origin](https://about.gitlab.com/blog/2016/12/01/how-to-keep-your-fork-up-to-date-with-its-origin/).
- GitLab community forum: [Refreshing a fork](https://forum.gitlab.com/t/refreshing-a-fork/).
- GitLab blog post: [Keep your fork up to date with its origin](https://about.gitlab.com/blog/2016/12/01/how-to-keep-your-fork-up-to-date-with-its-origin/)
- GitLab community forum: [Refreshing a fork](https://forum.gitlab.com/t/refreshing-a-fork/)
## Troubleshooting

View File

@ -277,15 +277,15 @@ to fetch configuration from a project that is renamed or moved.
## Related topics
- [GitLab Workflow VS Code extension](vscode.md).
- To lock files and prevent change conflicts, use [file locking](../file_lock.md).
- [Repository API](../../../api/repositories.md).
- [Find files](file_finder.md) in a repository.
- [Branches](branches/index.md).
- [Create a directory](web_editor.md#create-a-directory).
- [Find file history](git_history.md).
- [Identify changes by line (Git blame)](git_blame.md).
- [Use Jupyter notebooks with GitLab](jupyter_notebooks/index.md).
- [GitLab Workflow VS Code extension](vscode.md)
- [Lock files and prevent change conflicts](../file_lock.md)
- [Repository API](../../../api/repositories.md)
- [Find files](file_finder.md)
- [Branches](branches/index.md)
- [Create a directory](web_editor.md#create-a-directory)
- [Find file history](git_history.md)
- [Identify changes by line (Git blame)](git_blame.md)
- [Use Jupyter notebooks with GitLab](jupyter_notebooks/index.md)
## Troubleshooting

View File

@ -167,4 +167,4 @@ Read about [Git Fusion settings on Perforce.com](https://www.perforce.com/manual
## Related topics
- [Configure server hooks](../../../../administration/server_hooks.md) on a GitLab server.
- [Configure server hooks](../../../../administration/server_hooks.md)

View File

@ -141,6 +141,5 @@ end
## Related topics
- Configure [pull mirroring intervals](../../../../administration/instance_limits.md#pull-mirroring-interval)
on self-managed instances.
- Configure [pull mirroring through the API](../../../../api/projects.md#configure-pull-mirroring-for-a-project).
- [Pull mirroring intervals](../../../../administration/instance_limits.md#pull-mirroring-interval)
- [Pull mirroring API](../../../../api/projects.md#configure-pull-mirroring-for-a-project)

View File

@ -195,4 +195,4 @@ If it isn't working correctly, a red `error` tag appears, and shows the error me
## Related topics
- [Remote mirrors API](../../../../api/remote_mirrors.md).
- [Remote mirrors API](../../../../api/remote_mirrors.md)

View File

@ -115,6 +115,6 @@ you can trigger based on a new tag:
## Related topics
- [Tagging](https://git-scm.com/book/en/v2/Git-Basics-Tagging) Git reference page.
- [Protected tags](../../protected_tags.md).
- [Tags API](../../../../api/tags.md).
- [Tagging (Git reference page)](https://git-scm.com/book/en/v2/Git-Basics-Tagging)
- [Protected tags](../../protected_tags.md)
- [Tags API](../../../../api/tags.md)

View File

@ -101,5 +101,4 @@ Report any issues, bugs, or feature requests in the
- [Download the extension](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow)
- [Extension documentation](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/blob/main/README.md)
- The extension source code is available in the
[`gitlab-vscode-extension`](https://gitlab.com/gitlab-org/gitlab-vscode-extension/) project.
- [View source code](https://gitlab.com/gitlab-org/gitlab-vscode-extension/)

View File

@ -7,10 +7,7 @@ info: "To determine the technical writer assigned to the Stage/Group associated
# Migrating projects using file exports **(FREE)**
Existing projects on any self-managed GitLab instance or GitLab.com can be exported to a file and
then imported into a new GitLab instance. You can also:
- Migrate projects when you [migrate groups by direct transfer](../../group/import/index.md#migrate-groups-by-direct-transfer-recommended).
- [Migrate groups by using file exports](../../group/import/index.md#migrate-groups-by-uploading-an-export-file-deprecated).
then imported into a new GitLab instance.
GitLab maps user contributions correctly when an admin access token is used to perform the import.
@ -248,3 +245,5 @@ To help avoid abuse, by default, users are rate limited to:
- [Project import and export administration Rake tasks](../../../administration/raketasks/project_import_export.md)
- [Migrating GitLab groups](../../group/import/index.md)
- [Group import and export API](../../../api/group_import_export.md)
- [Migrate groups by direct transfer](../../group/import/index.md#migrate-groups-by-direct-transfer-recommended).
- [Migrate groups by using file exports](../../group/import/index.md#migrate-groups-by-uploading-an-export-file-deprecated).

View File

@ -5,8 +5,6 @@ module Gitlab
module RequestStoreCallbacks
def self.install
::ActionCable::Server::Worker.set_callback :work, :around, &wrapper
::ActionCable::Channel::Base.set_callback :subscribe, :around, &wrapper
::ActionCable::Channel::Base.set_callback :unsubscribe, :around, &wrapper
end
def self.wrapper

View File

@ -1,42 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Ci
module Components
##
# Components::Header class represents full component specification that is being prepended as first YAML document
# in the CI Component file.
#
class Header
attr_reader :errors
def initialize(header)
@header = header
@errors = []
end
def empty?
inputs_spec.to_h.empty?
end
def inputs(args)
@input ||= Ci::Input::Inputs.new(inputs_spec, args)
end
def context(args)
inputs(args).then do |input|
raise ArgumentError unless input.valid?
Ci::Interpolation::Context.new({ inputs: input.to_hash })
end
end
private
def inputs_spec
@header.dig(:spec, :inputs)
end
end
end
end
end

View File

@ -22,7 +22,7 @@ module Gitlab
strong_memoize(:content) do
Gitlab::Ci::ArtifactFileReader.new(artifact_job).read(location)
rescue Gitlab::Ci::ArtifactFileReader::Error => error
errors.push(error.message)
errors.push(error.message) # TODO this memoizes the error message as a content!
end
end

View File

@ -61,18 +61,6 @@ module Gitlab
[params, context.project&.full_path, context.sha].hash
end
def load_and_validate_expanded_hash!
context.logger.instrument(:config_file_fetch_content_hash) do
content_hash # calling the method loads then memoizes the result
end
context.logger.instrument(:config_file_expand_content_includes) do
expanded_content_hash # calling the method expands then memoizes the result
end
validate_hash!
end
# This method is overridden to load context into the memoized result
# or to lazily load context via BatchLoader
def preload_context
@ -94,32 +82,59 @@ module Gitlab
end
def validate_context!
raise NotImplementedError, 'subclass must implement validate_context'
raise NotImplementedError, 'subclass must implement `validate_context!`'
end
def validate_content!
if content.blank?
errors.push("Included file `#{masked_location}` is empty or does not exist!")
errors.push("Included file `#{masked_location}` is empty or does not exist!") if content.blank?
end
def load_and_validate_expanded_hash!
context.logger.instrument(:config_file_fetch_content_hash) do
content_result # calling the method loads YAML then memoizes the content result
end
context.logger.instrument(:config_file_interpolate_result) do
interpolator.interpolate!
end
return validate_interpolation! unless interpolator.valid?
context.logger.instrument(:config_file_expand_content_includes) do
expanded_content_hash # calling the method expands then memoizes the result
end
validate_hash!
end
protected
def content_result
strong_memoize(:content_hash) do
::Gitlab::Ci::Config::Yaml
.load_result!(content, project: context.project)
end
::Gitlab::Ci::Config::Yaml
.load_result!(content, project: context.project)
end
strong_memoize_attr :content_result
def content_inputs
params.to_h[:with]
end
strong_memoize_attr :content_inputs
def content_hash
return unless content_result.valid?
interpolator.interpolate!
content_result.content
interpolator.to_hash
end
strong_memoize_attr :content_hash
def interpolator
External::Interpolator
.new(content_result, content_inputs, context)
end
strong_memoize_attr :interpolator
def expanded_content_hash
return unless content_hash
return if content_hash.blank?
strong_memoize(:expanded_content_hash) do
expand_includes(content_hash)
@ -132,6 +147,12 @@ module Gitlab
end
end
def validate_interpolation!
return if interpolator.valid?
errors.push("`#{masked_location}`: #{interpolator.error_message}")
end
def expand_includes(hash)
External::Processor.new(hash, context.mutate(expand_context_attrs)).perform
end

View File

@ -11,6 +11,7 @@ module Gitlab
def initialize(params, context)
@location = params[:component]
super
end
@ -48,9 +49,7 @@ module Gitlab
end
def validate_content!
return if content.present?
errors.push(component_result.message)
errors.push(component_result.message) unless content.present?
end
private

View File

@ -0,0 +1,123 @@
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module External
##
# Config::External::Interpolation perform includable file interpolation, and surfaces all possible interpolation
# errors. It is designed to provide an external file's validation context too.
#
class Interpolator
include ::Gitlab::Utils::StrongMemoize
attr_reader :config, :args, :ctx, :errors
def initialize(config, args, ctx = nil)
@config = config
@args = args.to_h
@ctx = ctx
@errors = []
validate!
end
def valid?
@errors.none?
end
def ready?
##
# Interpolation is ready when it has been either interrupted by an error or finished with a result.
#
@result || @errors.any?
end
def interpolate?
enabled? && has_header? && valid?
end
def has_header?
config.has_header? && config.header.present?
end
def to_hash
@result.to_h
end
def error_message
# Interpolator can have multiple error messages, like: ["interpolation interrupted by errors", "unknown
# interpolation key: `abc`"] ?
#
# We are joining them together into a single one, because only one error can be surfaced when an external
# file gets included and is invalid. The limit to three error messages combined is more than required.
#
@errors.first(3).join(', ')
end
##
# TODO Add `instrument.logger` instrumentation blocks:
# https://gitlab.com/gitlab-org/gitlab/-/issues/396722
#
def interpolate!
return {} unless valid?
return @result ||= content.to_h unless interpolate?
return @errors.concat(header.errors) unless header.valid?
return @errors.concat(inputs.errors) unless inputs.valid?
return @errors.concat(context.errors) unless context.valid?
return @errors.concat(template.errors) unless template.valid?
@result ||= template.interpolated.to_h.deep_symbolize_keys
end
strong_memoize_attr :interpolate!
private
def validate!
return errors.push('content does not have a valid YAML syntax') unless config.valid?
return unless has_header? && !enabled?
errors.push('can not evaluate included file because interpolation is disabled')
end
def enabled?
return false if ctx.nil?
::Feature.enabled?(:ci_includable_files_interpolation, ctx.project)
end
def header
@entry ||= Ci::Config::Header::Root.new(config.header).tap do |header|
header.key = 'header'
header.compose!
end
end
def content
@content ||= config.content
end
def spec
@spec ||= header.inputs_value
end
def inputs
@inputs ||= Ci::Input::Inputs.new(spec, args)
end
def context
@context ||= Ci::Interpolation::Context.new({ inputs: inputs.to_hash })
end
def template
@template ||= ::Gitlab::Ci::Interpolation::Template
.new(content, context)
end
end
end
end
end
end

View File

@ -6,6 +6,7 @@ module Gitlab
module Header
##
# Input parameter used for interpolation with the CI configuration.
#
class Input < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable

View File

@ -17,6 +17,8 @@ module Gitlab
end
def has_header?
return false unless @config.first.is_a?(Hash)
@config.size > 1 && @config.first.key?(:spec)
end

View File

@ -9,7 +9,9 @@ module Gitlab
#
class Default < Input::Arguments::Base
def validate!
error('invalid specification') unless default.present?
return error('argument specification invalid') unless spec.key?(:default)
error('invalid default value') unless default.is_a?(String) || default.nil?
end
##
@ -35,6 +37,8 @@ module Gitlab
end
def self.matches?(spec)
return false unless spec.is_a?(Hash)
spec.count == 1 && spec.each_key.first == :default
end
end

View File

@ -25,7 +25,8 @@ module Gitlab
# The configuration above will return an empty value.
#
def validate!
return error('argument specification invalid') if options.to_a.empty?
return error('argument specification invalid') unless options.is_a?(Array)
return error('options argument empty') if options.empty?
if !value.nil?
error("argument value #{value} not allowlisted") unless options.include?(value)
@ -43,6 +44,8 @@ module Gitlab
end
def self.matches?(spec)
return false unless spec.is_a?(Hash)
spec.count == 1 && spec.each_key.first == :options
end
end

View File

@ -28,7 +28,7 @@ module Gitlab
# website:
# ```
#
# An empty value, that has no specification is also considered as a "required" input, however we should
# An empty string value, that has no specification is also considered as a "required" input, however we should
# never see that being used, because it will be rejected by Ci::Config::Header validation.
#
# ```yaml
@ -36,8 +36,17 @@ module Gitlab
# inputs:
# website: ""
# ```
#
# An empty hash value is also considered to be a required argument:
#
# ```yaml
# spec:
# inputs:
# website: {}
# ```
#
def self.matches?(spec)
spec.to_s.empty?
spec.blank?
end
end
end

View File

@ -19,8 +19,8 @@ module Gitlab
].freeze
def initialize(spec, args)
@spec = spec
@args = args
@spec = spec.to_h
@args = args.to_h
@inputs = []
@errors = []

View File

@ -45,7 +45,11 @@ module Gitlab
raise ArgumentError, 'access path invalid' unless valid?
@value ||= objects.inject(@ctx) do |memo, value|
memo.fetch(value.to_sym)
key = value.to_sym
break @errors.push("unknown interpolation key: `#{key}`") unless memo.key?(key)
memo.fetch(key)
end
rescue KeyError => e
@errors.push(e)

View File

@ -38,6 +38,10 @@ module Gitlab
@context.fetch(field)
end
def key?(name)
@context.key?(name)
end
def to_h
@context.to_h
end
@ -53,7 +57,7 @@ module Gitlab
end
end
values.max
values.max.to_i
end
def self.fabricate(context)

View File

@ -6,14 +6,10 @@ module Gitlab
module ActionCableCallbacks
def self.install
::ActionCable::Server::Worker.set_callback :work, :around, &wrapper
::ActionCable::Channel::Base.set_callback :subscribe, :around, &wrapper
::ActionCable::Channel::Base.set_callback :unsubscribe, :around, &wrapper
end
def self.wrapper
lambda do |_, inner|
::Gitlab::Database::LoadBalancing::Session.current.use_primary!
inner.call
ensure
::Gitlab::Database::LoadBalancing.release_hosts

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
module Gitlab
module Graphql
module Subscriptions
class ActionCableWithLoadBalancing < ::GraphQL::Subscriptions::ActionCableSubscriptions
extend ::Gitlab::Utils::Override
# When executing updates we are usually responding to a broadcast as a result of a DB update.
# We use the primary so that we are sure that we are returning the newly updated data.
override :execute_update
def execute_update(subscription_id, event, object)
::Gitlab::Database::LoadBalancing::Session.current.use_primary!
super
end
end
end
end
end

View File

@ -9,7 +9,6 @@ module Gitlab
attach_to :active_record
IGNORABLE_SQL = %w{BEGIN COMMIT}.freeze
DB_COUNTERS = %i{count write_count cached_count}.freeze
SQL_COMMANDS_WITH_COMMENTS_REGEX = %r{\A(/\*.*\*/\s)?((?!(.*[^\w'"](DELETE|UPDATE|INSERT INTO)[^\w'"])))(WITH.*)?(SELECT)((?!(FOR UPDATE|FOR SHARE)).)*$}i.freeze
@ -114,7 +113,7 @@ module Gitlab
end
def ignored_query?(payload)
payload[:name] == 'SCHEMA' || IGNORABLE_SQL.include?(payload[:sql])
payload[:name] == 'SCHEMA' || payload[:name] == 'TRANSACTION'
end
def cached_query?(payload)

View File

@ -370,16 +370,6 @@ module Gitlab
}
end
def merge_requests_users(time_period)
redis_usage_data do
Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(
event_names: :merge_request_action,
start_date: time_period[:created_at].first,
end_date: time_period[:created_at].last
)
end
end
def installation_type
if Rails.env.production?
Gitlab::INSTALLATION_TYPE
@ -447,9 +437,7 @@ module Gitlab
projects_without_disable_overriding_approvers_per_merge_request: count(::Project.where(time_period.merge(disable_overriding_approvers_per_merge_request: [false, nil]))),
remote_mirrors: distinct_count(::Project.with_remote_mirrors.where(time_period), :creator_id),
snippets: distinct_count(::Snippet.where(time_period), :author_id)
}.tap do |h|
h[:merge_requests_users] = merge_requests_users(time_period) if time_period.present?
end
}
end
# rubocop: enable CodeReuse/ActiveRecord

View File

@ -28627,6 +28627,9 @@ msgstr ""
msgid "Navigation|There was an error fetching search results."
msgstr ""
msgid "Navigation|Toggle edit mode"
msgstr ""
msgid "Navigation|View all groups"
msgstr ""

File diff suppressed because one or more lines are too long

View File

@ -2,10 +2,15 @@
module QA
RSpec.describe 'Create' do
describe 'Branch Rules Overview', product_group: :source_code, feature_flag: {
name: 'branch_rules',
scope: :project
} do
describe 'Branch Rules Overview', product_group: :source_code,
feature_flag: {
name: 'branch_rules',
scope: :project
},
quarantine: {
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/403583',
type: :flaky
} do
let(:branch_name) { 'new-branch' }
let(:allowed_to_push_role) { Resource::ProtectedBranch::Roles::NO_ONE }
let(:allowed_to_merge_role) { Resource::ProtectedBranch::Roles::MAINTAINERS }

View File

@ -324,7 +324,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :projects do
expect(page).to have_selector('[data-testid="pipelines-manual-actions-dropdown"] [data-testid="play-icon"]')
end
it "has link to the delayed job's action" do
it "has link to the delayed job's action", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/403601' do
find('[data-testid="pipelines-manual-actions-dropdown"]').click
wait_for_requests

View File

@ -424,14 +424,27 @@ describe('Release edit/new getters', () => {
describe('formattedReleaseNotes', () => {
it.each`
description | includeTagNotes | tagNotes | included
${'release notes'} | ${true} | ${'tag notes'} | ${true}
${'release notes'} | ${true} | ${''} | ${false}
${'release notes'} | ${false} | ${'tag notes'} | ${false}
description | includeTagNotes | tagNotes | included | showCreateFrom
${'release notes'} | ${true} | ${'tag notes'} | ${true} | ${false}
${'release notes'} | ${true} | ${''} | ${false} | ${false}
${'release notes'} | ${false} | ${'tag notes'} | ${false} | ${false}
${'release notes'} | ${true} | ${'tag notes'} | ${true} | ${true}
${'release notes'} | ${true} | ${''} | ${false} | ${true}
${'release notes'} | ${false} | ${'tag notes'} | ${false} | ${true}
`(
'should include tag notes=$included when includeTagNotes=$includeTagNotes and tagNotes=$tagNotes',
({ description, includeTagNotes, tagNotes, included }) => {
const state = { release: { description }, includeTagNotes, tagNotes };
'should include tag notes=$included when includeTagNotes=$includeTagNotes and tagNotes=$tagNotes and showCreateFrom=$showCreateFrom',
({ description, includeTagNotes, tagNotes, included, showCreateFrom }) => {
let state;
if (showCreateFrom) {
state = {
release: { description, tagMessage: tagNotes },
includeTagNotes,
showCreateFrom,
};
} else {
state = { release: { description }, includeTagNotes, tagNotes, showCreateFrom };
}
const text = `### ${s__('Releases|Tag message')}\n\n${tagNotes}\n`;
if (included) {

View File

@ -38,7 +38,6 @@ describe('Sidebar date Widget', () => {
canInherit = false,
dateType = undefined,
issuableType = 'issue',
realTimeIssueDueDate = true,
} = {}) => {
fakeApollo = createMockApollo([
[issueDueDateQuery, dueDateQueryHandler],
@ -50,9 +49,6 @@ describe('Sidebar date Widget', () => {
apolloProvider: fakeApollo,
provide: {
canUpdate: true,
glFeatures: {
realTimeIssueDueDate,
},
},
propsData: {
fullPath: 'group/project',
@ -148,28 +144,14 @@ describe('Sidebar date Widget', () => {
});
describe('real time issue due date feature', () => {
describe('when :real_time_issue_due_date feature is enabled', () => {
it('should call the subscription', async () => {
const dueDateSubscriptionHandler = jest
.fn()
.mockResolvedValue(issueDueDateSubscriptionResponse());
createComponent({ realTimeIssueDueDate: true, dueDateSubscriptionHandler });
await waitForPromises();
it('should call the subscription', async () => {
const dueDateSubscriptionHandler = jest
.fn()
.mockResolvedValue(issueDueDateSubscriptionResponse());
createComponent({ dueDateSubscriptionHandler });
await waitForPromises();
expect(dueDateSubscriptionHandler).toHaveBeenCalled();
});
});
describe('when :real_time_issue_due_date feature is disabled', () => {
it('should not call the subscription', async () => {
const dueDateSubscriptionHandler = jest
.fn()
.mockResolvedValue(issueDueDateSubscriptionResponse());
createComponent({ realTimeIssueDueDate: false, dueDateSubscriptionHandler });
await waitForPromises();
expect(dueDateSubscriptionHandler).not.toHaveBeenCalled();
});
expect(dueDateSubscriptionHandler).toHaveBeenCalled();
});
});

View File

@ -1,3 +1,4 @@
import { GlIcon, GlButton } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { s__ } from '~/locale';
import FrequentItemsList from '~/super_sidebar/components//frequent_items_list.vue';
@ -16,6 +17,7 @@ describe('FrequentItemsList component', () => {
let wrapper;
const findListTitle = () => wrapper.findByTestId('list-title');
const findListEditButton = () => findListTitle().findComponent(GlButton);
const findItemsList = () => wrapper.findComponent(ItemsList);
const findEmptyText = () => wrapper.findByTestId('empty-text');
@ -64,5 +66,38 @@ describe('FrequentItemsList component', () => {
it('does not render the empty text slot', () => {
expect(findEmptyText().exists()).toBe(false);
});
describe('items editing', () => {
it('renders edit button within header', () => {
const itemsEditButton = findListEditButton();
expect(itemsEditButton.exists()).toBe(true);
expect(itemsEditButton.attributes('title')).toBe('Toggle edit mode');
expect(itemsEditButton.findComponent(GlIcon).props('name')).toBe('pencil');
});
it('clicking edit button makes items list editable', async () => {
// Off by default
expect(findItemsList().props('editable')).toBe(false);
// On when clicked
await findListEditButton().vm.$emit('click');
expect(findItemsList().props('editable')).toBe(true);
// Off when clicked again
await findListEditButton().vm.$emit('click');
expect(findItemsList().props('editable')).toBe(false);
});
it('remove-item event emission from items-list causes list item to be removed', async () => {
const localStorageProjects = findItemsList().props('items');
await findListEditButton().vm.$emit('click');
await findItemsList().vm.$emit('remove-item', localStorageProjects[0]);
expect(findItemsList().props('items')).toHaveLength(maxItems - 1);
expect(findItemsList().props('items')).not.toContain(localStorageProjects[0]);
});
});
});
});

View File

@ -1,4 +1,5 @@
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { GlIcon } from '@gitlab/ui';
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
import ItemsList from '~/super_sidebar/components/items_list.vue';
import NavItem from '~/super_sidebar/components/nav_item.vue';
import { cachedFrequentProjects } from '../mock_data';
@ -11,8 +12,8 @@ describe('ItemsList component', () => {
const findNavItems = () => wrapper.findAllComponents(NavItem);
const createWrapper = ({ props = {}, slots = {} } = {}) => {
wrapper = shallowMountExtended(ItemsList, {
const createWrapper = ({ props = {}, slots = {}, mountFn = shallowMountExtended } = {}) => {
wrapper = mountFn(ItemsList, {
propsData: {
...props,
},
@ -60,4 +61,42 @@ describe('ItemsList component', () => {
expect(wrapper.findByTestId(testId).exists()).toBe(true);
});
describe('item removal', () => {
const findRemoveButton = () => wrapper.findByTestId('item-remove');
const mockProject = {
...firstMockedProject,
title: firstMockedProject.name,
};
beforeEach(() => {
createWrapper({
props: {
items: [mockProject],
editable: true,
},
mountFn: mountExtended,
});
});
it('renders the remove button', () => {
const itemRemoveButton = findRemoveButton();
expect(itemRemoveButton.exists()).toBe(true);
expect(itemRemoveButton.attributes('title')).toBe('Remove');
expect(itemRemoveButton.findComponent(GlIcon).props('name')).toBe('close');
});
it('emits `remove-item` event with item param when remove button is clicked', () => {
const itemRemoveButton = findRemoveButton();
itemRemoveButton.vm.$emit(
'click',
{ stopPropagation: jest.fn(), preventDefault: jest.fn() },
mockProject,
);
expect(wrapper.emitted('remove-item')).toEqual([[mockProject]]);
});
});
});

View File

@ -52,14 +52,11 @@ describe('Tracking', () => {
hostname: 'app.test.com',
cookieDomain: '.test.com',
appId: '',
userFingerprint: false,
respectDoNotTrack: true,
forceSecureTracker: true,
eventMethod: 'post',
contexts: { webPage: true, performanceTiming: true },
formTracking: false,
linkClickTracking: false,
pageUnloadTimer: 10,
formTrackingConfig: {
fields: { allow: [] },
forms: { allow: [] },
@ -80,8 +77,14 @@ describe('Tracking', () => {
it('should activate features based on what has been enabled', () => {
initDefaultTrackers();
expect(snowplowSpy).toHaveBeenCalledWith('enableActivityTracking', 30, 30);
expect(snowplowSpy).toHaveBeenCalledWith('trackPageView', 'GitLab', [standardContext]);
expect(snowplowSpy).toHaveBeenCalledWith('enableActivityTracking', {
minimumVisitLength: 30,
heartbeatDelay: 30,
});
expect(snowplowSpy).toHaveBeenCalledWith('trackPageView', {
title: 'GitLab',
context: [standardContext],
});
expect(snowplowSpy).toHaveBeenCalledWith('setDocumentTitle', 'GitLab');
expect(snowplowSpy).not.toHaveBeenCalledWith('enableFormTracking');
expect(snowplowSpy).not.toHaveBeenCalledWith('enableLinkClickTracking');
@ -131,10 +134,10 @@ describe('Tracking', () => {
it('includes those contexts alongside the standard context', () => {
initDefaultTrackers();
expect(snowplowSpy).toHaveBeenCalledWith('trackPageView', 'GitLab', [
standardContext,
...experimentContexts,
]);
expect(snowplowSpy).toHaveBeenCalledWith('trackPageView', {
title: 'GitLab',
context: [standardContext, ...experimentContexts],
});
});
});
});

View File

@ -65,15 +65,14 @@ describe('Tracking', () => {
it('tracks to snowplow (our current tracking system)', () => {
Tracking.event(TEST_CATEGORY, TEST_ACTION, { label: TEST_LABEL });
expect(snowplowSpy).toHaveBeenCalledWith(
'trackStructEvent',
TEST_CATEGORY,
TEST_ACTION,
TEST_LABEL,
undefined,
undefined,
[standardContext],
);
expect(snowplowSpy).toHaveBeenCalledWith('trackStructEvent', {
category: TEST_CATEGORY,
action: TEST_ACTION,
label: TEST_LABEL,
property: undefined,
value: undefined,
context: [standardContext],
});
});
it('returns `true` if the Snowplow library was called without issues', () => {
@ -93,14 +92,13 @@ describe('Tracking', () => {
Tracking.event(TEST_CATEGORY, TEST_ACTION, { extra });
expect(snowplowSpy).toHaveBeenCalledWith(
'trackStructEvent',
TEST_CATEGORY,
TEST_ACTION,
undefined,
undefined,
undefined,
[
expect(snowplowSpy).toHaveBeenCalledWith('trackStructEvent', {
category: TEST_CATEGORY,
action: TEST_ACTION,
label: undefined,
property: undefined,
value: undefined,
context: [
{
...standardContext,
data: {
@ -109,7 +107,7 @@ describe('Tracking', () => {
},
},
],
);
});
});
it('skips tracking if snowplow is unavailable', () => {
@ -209,14 +207,16 @@ describe('Tracking', () => {
describe('.enableFormTracking', () => {
it('tells snowplow to enable form tracking, with only explicit contexts', () => {
const config = { forms: { allow: ['form-class1'] }, fields: { allow: ['input-class1'] } };
const config = {
forms: { allow: ['form-class1'] },
fields: { allow: ['input-class1'] },
};
Tracking.enableFormTracking(config, ['_passed_context_', standardContext]);
expect(snowplowSpy).toHaveBeenCalledWith(
'enableFormTracking',
{ forms: { whitelist: ['form-class1'] }, fields: { whitelist: ['input-class1'] } },
['_passed_context_'],
);
expect(snowplowSpy).toHaveBeenCalledWith('enableFormTracking', {
options: { forms: { allowlist: ['form-class1'] }, fields: { allowlist: ['input-class1'] } },
context: ['_passed_context_'],
});
});
it('throws an error if no allow rules are provided', () => {
@ -232,11 +232,10 @@ describe('Tracking', () => {
it('does not add empty form allow rules', () => {
Tracking.enableFormTracking({ fields: { allow: ['input-class1'] } });
expect(snowplowSpy).toHaveBeenCalledWith(
'enableFormTracking',
{ fields: { whitelist: ['input-class1'] } },
[],
);
expect(snowplowSpy).toHaveBeenCalledWith('enableFormTracking', {
options: { fields: { allowlist: ['input-class1'] } },
context: [],
});
});
describe('when `document.readyState` does not equal `complete`', () => {
@ -285,15 +284,14 @@ describe('Tracking', () => {
Tracking.flushPendingEvents();
expect(snowplowSpy).toHaveBeenCalledWith(
'trackStructEvent',
TEST_CATEGORY,
TEST_ACTION,
TEST_LABEL,
undefined,
undefined,
[standardContext],
);
expect(snowplowSpy).toHaveBeenCalledWith('trackStructEvent', {
category: TEST_CATEGORY,
action: TEST_ACTION,
label: TEST_LABEL,
property: undefined,
value: undefined,
context: [standardContext],
});
});
});
@ -457,15 +455,14 @@ describe('Tracking', () => {
value: '0',
});
expect(snowplowSpy).toHaveBeenCalledWith(
'trackStructEvent',
TEST_CATEGORY,
'click_input2',
undefined,
undefined,
0,
[standardContext],
);
expect(snowplowSpy).toHaveBeenCalledWith('trackStructEvent', {
category: TEST_CATEGORY,
action: 'click_input2',
label: undefined,
property: undefined,
value: 0,
context: [standardContext],
});
});
it('handles checkbox values correctly', () => {

View File

@ -1,50 +0,0 @@
# frozen_string_literal: true
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Components::Header, feature_category: :pipeline_composition do
subject { described_class.new(spec) }
context 'when spec is valid' do
let(:spec) do
{
spec: {
inputs: {
website: nil,
run: {
options: %w[opt1 opt2]
}
}
}
}
end
it 'fabricates a spec from valid data' do
expect(subject).not_to be_empty
end
describe '#inputs' do
it 'fabricates input data' do
input = subject.inputs({ website: 'https//gitlab.com', run: 'opt1' })
expect(input.count).to eq 2
end
end
describe '#context' do
it 'fabricates interpolation context' do
ctx = subject.context({ website: 'https//gitlab.com', run: 'opt1' })
expect(ctx).to be_valid
end
end
end
context 'when spec is empty' do
let(:spec) { { spec: {} } }
it 'returns an empty header' do
expect(subject).to be_empty
end
end
end

View File

@ -4,9 +4,11 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::External::File::Artifact, feature_category: :pipeline_composition do
let(:parent_pipeline) { create(:ci_pipeline) }
let(:project) { parent_pipeline.project }
let(:variables) {}
let(:context) do
Gitlab::Ci::Config::External::Context.new(variables: variables, parent_pipeline: parent_pipeline)
Gitlab::Ci::Config::External::Context
.new(variables: variables, parent_pipeline: parent_pipeline, project: project)
end
let(:external_file) { described_class.new(params, context) }
@ -43,7 +45,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Artifact, feature_category: :
end
describe 'when used in non child pipeline context' do
let(:parent_pipeline) { nil }
let(:context) { Gitlab::Ci::Config::External::Context.new }
let(:params) { { artifact: 'generated.yml' } }
let(:expected_error) do
@ -201,7 +203,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Artifact, feature_category: :
it {
is_expected.to eq(
context_project: nil,
context_project: project.full_path,
context_sha: nil,
type: :artifact,
location: 'generated.yml',
@ -218,7 +220,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Artifact, feature_category: :
it {
is_expected.to eq(
context_project: nil,
context_project: project.full_path,
context_sha: nil,
type: :artifact,
location: 'generated.yml',
@ -227,4 +229,35 @@ RSpec.describe Gitlab::Ci::Config::External::File::Artifact, feature_category: :
}
end
end
describe '#to_hash' do
context 'when interpolation is being used' do
let!(:job) { create(:ci_build, name: 'generator', pipeline: parent_pipeline) }
let!(:artifacts) { create(:ci_job_artifact, :archive, job: job) }
let!(:metadata) { create(:ci_job_artifact, :metadata, job: job) }
before do
allow_next_instance_of(Gitlab::Ci::ArtifactFileReader) do |reader|
allow(reader).to receive(:read).and_return(template)
end
end
let(:template) do
<<~YAML
spec:
inputs:
env:
---
deploy:
script: deploy $[[ inputs.env ]]
YAML
end
let(:params) { { artifact: 'generated.yml', job: 'generator', with: { env: 'production' } } }
it 'correctly interpolates content' do
expect(external_file.to_hash).to eq({ deploy: { script: 'deploy production' } })
end
end
end
end

View File

@ -3,14 +3,15 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::External::File::Base, feature_category: :pipeline_composition do
let_it_be(:project) { create(:project) }
let(:variables) {}
let(:context_params) { { sha: 'HEAD', variables: variables } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:context_params) { { sha: 'HEAD', variables: variables, project: project } }
let(:ctx) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:test_class) do
Class.new(described_class) do
def initialize(params, context)
@location = params
def initialize(params, ctx)
@location = params[:location]
super
end
@ -18,15 +19,18 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base, feature_category: :pipe
def validate_context!
# no-op
end
def content
params[:content]
end
end
end
subject(:file) { test_class.new(location, context) }
let(:content) { 'key: value' }
subject(:file) { test_class.new({ location: location, content: content }, ctx) }
before do
allow_any_instance_of(test_class)
.to receive(:content).and_return('key: value')
allow_any_instance_of(Gitlab::Ci::Config::External::Context)
.to receive(:check_execution_time!)
end
@ -51,7 +55,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base, feature_category: :pipe
describe '#valid?' do
subject(:valid?) do
Gitlab::Ci::Config::External::Mapper::Verifier.new(context).process([file])
Gitlab::Ci::Config::External::Mapper::Verifier.new(ctx).process([file])
file.valid?
end
@ -87,7 +91,12 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base, feature_category: :pipe
context 'when there are YAML syntax errors' do
let(:location) { 'some/file/secret_file_name.yml' }
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file_name', 'masked' => true }]) }
let(:variables) do
Gitlab::Ci::Variables::Collection.new(
[{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file_name', 'masked' => true }]
)
end
before do
allow_any_instance_of(test_class)
@ -96,15 +105,16 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base, feature_category: :pipe
it 'is not a valid file' do
expect(valid?).to be_falsy
expect(file.error_message).to eq('Included file `some/file/xxxxxxxxxxxxxxxx.yml` does not have valid YAML syntax!')
expect(file.error_message)
.to eq('`some/file/xxxxxxxxxxxxxxxx.yml`: content does not have a valid YAML syntax')
end
end
context 'when the class has no validate_context!' do
let(:test_class) do
Class.new(described_class) do
def initialize(params, context)
@location = params
def initialize(params, ctx)
@location = params[:location]
super
end
@ -117,6 +127,71 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base, feature_category: :pipe
expect { valid? }.to raise_error(NotImplementedError)
end
end
context 'when interpolation is disabled but there is a spec header' do
before do
stub_feature_flags(ci_includable_files_interpolation: false)
end
let(:location) { 'some-location.yml' }
let(:content) do
<<~YAML
spec:
include:
website:
---
run:
script: deploy $[[ inputs.website ]]
YAML
end
it 'returns an error saying that interpolation is disabled' do
expect(valid?).to be_falsy
expect(file.errors)
.to include('`some-location.yml`: can not evaluate included file because interpolation is disabled')
end
end
context 'when interpolation was unsuccessful' do
let(:location) { 'some-location.yml' }
context 'when context key is missing' do
let(:content) do
<<~YAML
spec:
inputs:
---
run:
script: deploy $[[ inputs.abcd ]]
YAML
end
it 'surfaces interpolation errors' do
expect(valid?).to be_falsy
expect(file.errors)
.to include('`some-location.yml`: interpolation interrupted by errors, unknown interpolation key: `abcd`')
end
end
context 'when header is invalid' do
let(:content) do
<<~YAML
spec:
a: abc
---
run:
script: deploy $[[ inputs.abcd ]]
YAML
end
it 'surfaces header errors' do
expect(valid?).to be_falsy
expect(file.errors)
.to include('`some-location.yml`: header:spec config contains unknown keys: a')
end
end
end
end
describe '#to_hash' do
@ -142,7 +217,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base, feature_category: :pipe
it {
is_expected.to eq(
context_project: nil,
context_project: project.full_path,
context_sha: 'HEAD'
)
}
@ -154,13 +229,13 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base, feature_category: :pipe
subject(:eql) { file.eql?(other_file) }
context 'when the other file has the same params' do
let(:other_file) { test_class.new(location, context) }
let(:other_file) { test_class.new({ location: location, content: content }, ctx) }
it { is_expected.to eq(true) }
end
context 'when the other file has not the same params' do
let(:other_file) { test_class.new('some/other/file', context) }
let(:other_file) { test_class.new({ location: 'some/other/file', content: content }, ctx) }
it { is_expected.to eq(false) }
end
@ -172,14 +247,15 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base, feature_category: :pipe
subject(:filehash) { file.hash }
context 'with a project' do
let(:project) { create(:project) }
let(:context_params) { { project: project, sha: 'HEAD', variables: variables } }
it { is_expected.to eq([location, project.full_path, 'HEAD'].hash) }
it { is_expected.to eq([{ location: location, content: content }, project.full_path, 'HEAD'].hash) }
end
context 'without a project' do
it { is_expected.to eq([location, nil, 'HEAD'].hash) }
let(:context_params) { { sha: 'HEAD', variables: variables } }
it { is_expected.to eq([{ location: location, content: content }, nil, 'HEAD'].hash) }
end
end
end

View File

@ -121,7 +121,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Component, feature_category:
it 'is invalid' do
expect(subject).to be_falsy
expect(external_resource.error_message).to match(/does not have valid YAML syntax/)
expect(external_resource.error_message).to match(/does not have a valid YAML syntax/)
end
end
end
@ -176,4 +176,35 @@ RSpec.describe Gitlab::Ci::Config::External::File::Component, feature_category:
variables: context.variables)
end
end
describe '#to_hash' do
context 'when interpolation is being used' do
let(:response) do
ServiceResponse.success(payload: { content: content, path: path })
end
let(:path) do
instance_double(::Gitlab::Ci::Components::InstancePath, project: project, sha: '12345')
end
let(:content) do
<<~YAML
spec:
inputs:
env:
---
deploy:
script: deploy $[[ inputs.env ]]
YAML
end
let(:params) do
{ component: 'gitlab.com/acme/components/my-component@1.0', with: { env: 'production' } }
end
it 'correctly interpolates the content' do
expect(external_resource.to_hash).to eq({ deploy: { script: 'deploy production' } })
end
end
end
end

View File

@ -228,6 +228,34 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local, feature_category: :pip
expect(local_file.to_hash).to include(:rspec)
end
end
context 'when interpolaton is being used' do
let(:local_file_content) do
<<~YAML
spec:
inputs:
website:
---
test:
script: cap deploy $[[ inputs.website ]]
YAML
end
let(:location) { '/lib/gitlab/ci/templates/existent-file.yml' }
let(:params) { { local: location, with: { website: 'gitlab.com' } } }
before do
allow_any_instance_of(described_class)
.to receive(:fetch_local_content)
.and_return(local_file_content)
end
it 'correctly interpolates the local template' do
expect(local_file).to be_valid
expect(local_file.to_hash)
.to eq({ test: { script: 'cap deploy gitlab.com' } })
end
end
end
describe '#metadata' do

View File

@ -289,4 +289,37 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project, feature_category: :p
}
end
end
describe '#to_hash' do
context 'when interpolation is being used' do
before do
project.repository.create_file(
user,
'template-file.yml',
template,
message: 'Add template',
branch_name: 'master'
)
end
let(:template) do
<<~YAML
spec:
inputs:
name:
---
rspec:
script: rspec --suite $[[ inputs.name ]]
YAML
end
let(:params) do
{ file: 'template-file.yml', ref: 'master', project: project.full_path, with: { name: 'abc' } }
end
it 'correctly interpolates the content' do
expect(project_file.to_hash).to eq({ rspec: { script: 'rspec --suite abc' } })
end
end
end
end

View File

@ -234,15 +234,13 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote, feature_category: :pi
end
describe '#to_hash' do
subject(:to_hash) { remote_file.to_hash }
before do
stub_full_request(location).to_return(body: remote_file_content)
end
context 'with a valid remote file' do
it 'returns the content as a hash' do
expect(to_hash).to eql(
expect(remote_file.to_hash).to eql(
before_script: ["apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs",
"ruby -v",
"which ruby",
@ -262,7 +260,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote, feature_category: :pi
end
it 'returns the content as a hash' do
expect(to_hash).to eql(
expect(remote_file.to_hash).to eql(
include: [
{ local: 'another-file.yml',
rules: [{ exists: ['Dockerfile'] }] }
@ -270,5 +268,38 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote, feature_category: :pi
)
end
end
context 'when interpolation has been used' do
let_it_be(:project) { create(:project) }
let(:remote_file_content) do
<<~YAML
spec:
inputs:
include:
---
include:
- local: $[[ inputs.include ]]
rules:
- exists: [Dockerfile]
YAML
end
let(:params) { { remote: location, with: { include: 'some-file.yml' } } }
let(:context_params) do
{ sha: '12345', variables: variables, project: project }
end
it 'returns the content as a hash' do
expect(remote_file).to be_valid
expect(remote_file.to_hash).to eql(
include: [
{ local: 'some-file.yml',
rules: [{ exists: ['Dockerfile'] }] }
]
)
end
end
end
end

View File

@ -130,4 +130,37 @@ RSpec.describe Gitlab::Ci::Config::External::File::Template, feature_category: :
)
}
end
describe '#to_hash' do
context 'when interpolation is being used' do
before do
allow(Gitlab::Template::GitlabCiYmlTemplate)
.to receive(:find)
.and_return(template_double)
end
let(:template_double) do
instance_double(Gitlab::Template::GitlabCiYmlTemplate, content: template_content)
end
let(:template_content) do
<<~YAML
spec:
inputs:
env:
---
deploy:
script: deploy $[[ inputs.env ]]
YAML
end
let(:params) do
{ template: template, with: { env: 'production' } }
end
it 'correctly interpolates the content' do
expect(template_file.to_hash).to eq({ deploy: { script: 'deploy production' } })
end
end
end
end

View File

@ -0,0 +1,312 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::External::Interpolator, feature_category: :pipeline_composition do
let_it_be(:project) { create(:project) }
let(:ctx) { instance_double(Gitlab::Ci::Config::External::Context, project: project) }
let(:result) { ::Gitlab::Ci::Config::Yaml::Result.new(config: [header, content]) }
subject { described_class.new(result, arguments, ctx) }
context 'when input data is valid' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
{ test: 'deploy $[[ inputs.website ]]' }
end
let(:arguments) do
{ website: 'gitlab.com' }
end
it 'correctly interpolates the config' do
subject.interpolate!
expect(subject).to be_valid
expect(subject.to_hash).to eq({ test: 'deploy gitlab.com' })
end
end
context 'when config has a syntax error' do
let(:result) { ::Gitlab::Ci::Config::Yaml::Result.new(error: ArgumentError.new) }
let(:arguments) do
{ website: 'gitlab.com' }
end
it 'surfaces an error about invalid config' do
subject.interpolate!
expect(subject).not_to be_valid
expect(subject.error_message).to eq subject.errors.first
expect(subject.errors).to include 'content does not have a valid YAML syntax'
end
end
context 'when spec header is invalid' do
let(:header) do
{ spec: { arguments: { website: nil } } }
end
let(:content) do
{ test: 'deploy $[[ inputs.website ]]' }
end
let(:arguments) do
{ website: 'gitlab.com' }
end
it 'surfaces an error about invalid header' do
subject.interpolate!
expect(subject).not_to be_valid
expect(subject.error_message).to eq subject.errors.first
expect(subject.errors).to include('header:spec config contains unknown keys: arguments')
end
end
context 'when interpolation block is invalid' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
{ test: 'deploy $[[ inputs.abc ]]' }
end
let(:arguments) do
{ website: 'gitlab.com' }
end
it 'correctly interpolates the config' do
subject.interpolate!
expect(subject).not_to be_valid
expect(subject.errors).to include 'unknown interpolation key: `abc`'
expect(subject.error_message).to eq 'interpolation interrupted by errors, unknown interpolation key: `abc`'
end
end
context 'when provided interpolation argument is invalid' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
{ test: 'deploy $[[ inputs.website ]]' }
end
let(:arguments) do
{ website: ['gitlab.com'] }
end
it 'correctly interpolates the config' do
subject.interpolate!
expect(subject).not_to be_valid
expect(subject.error_message).to eq subject.errors.first
expect(subject.errors).to include 'unsupported value in input argument `website`'
end
end
context 'when multiple interpolation blocks are invalid' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
{ test: 'deploy $[[ inputs.something.abc ]] $[[ inputs.cde ]] $[[ efg ]]' }
end
let(:arguments) do
{ website: 'gitlab.com' }
end
it 'correctly interpolates the config' do
subject.interpolate!
expect(subject).not_to be_valid
expect(subject.error_message).to eq 'interpolation interrupted by errors, unknown interpolation key: `something`'
end
end
describe '#to_hash' do
context 'when interpolation is disabled' do
before do
stub_feature_flags(ci_includable_files_interpolation: false)
end
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
{ test: 'deploy $[[ inputs.website ]]' }
end
let(:arguments) { {} }
it 'returns an empty hash' do
subject.interpolate!
expect(subject.to_hash).to be_empty
end
end
context 'when interpolation is not used' do
let(:result) do
::Gitlab::Ci::Config::Yaml::Result.new(config: content)
end
let(:content) do
{ test: 'deploy production' }
end
let(:arguments) { nil }
it 'returns original content' do
subject.interpolate!
expect(subject.to_hash).to eq(content)
end
end
context 'when interpolation is available' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
{ test: 'deploy $[[ inputs.website ]]' }
end
let(:arguments) do
{ website: 'gitlab.com' }
end
it 'correctly interpolates content' do
subject.interpolate!
expect(subject.to_hash).to eq({ test: 'deploy gitlab.com' })
end
end
end
describe '#ready?' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
{ test: 'deploy $[[ inputs.website ]]' }
end
let(:arguments) do
{ website: 'gitlab.com' }
end
it 'returns false if interpolation has not been done yet' do
expect(subject).not_to be_ready
end
it 'returns true if interpolation has been performed' do
subject.interpolate!
expect(subject).to be_ready
end
context 'when interpolation can not be performed' do
let(:result) do
::Gitlab::Ci::Config::Yaml::Result.new(error: ArgumentError.new)
end
it 'returns true if interpolator has preliminary errors' do
expect(subject).to be_ready
end
it 'returns true if interpolation has been attempted' do
subject.interpolate!
expect(subject).to be_ready
end
end
end
describe '#interpolate?' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
{ test: 'deploy $[[ inputs.something.abc ]] $[[ inputs.cde ]] $[[ efg ]]' }
end
let(:arguments) do
{ website: 'gitlab.com' }
end
context 'when interpolation can be performed' do
it 'will perform interpolation' do
expect(subject.interpolate?).to eq true
end
end
context 'when interpolation is disabled' do
before do
stub_feature_flags(ci_includable_files_interpolation: false)
end
it 'will not perform interpolation' do
expect(subject.interpolate?).to eq false
end
end
context 'when an interpolation header is missing' do
let(:header) { nil }
it 'will not perform interpolation' do
expect(subject.interpolate?).to eq false
end
end
context 'when interpolator has preliminary errors' do
let(:result) do
::Gitlab::Ci::Config::Yaml::Result.new(error: ArgumentError.new)
end
it 'will not perform interpolation' do
expect(subject.interpolate?).to eq false
end
end
end
describe '#has_header?' do
let(:content) do
{ test: 'deploy $[[ inputs.something.abc ]] $[[ inputs.cde ]] $[[ efg ]]' }
end
let(:arguments) do
{ website: 'gitlab.com' }
end
context 'when header is an empty hash' do
let(:header) { {} }
it 'does not have a header available' do
expect(subject).not_to have_header
end
end
context 'when header is not specified' do
let(:header) { nil }
it 'does not have a header available' do
expect(subject).not_to have_header
end
end
end
end

View File

@ -221,7 +221,7 @@ RSpec.describe Gitlab::Ci::Config::External::Processor, feature_category: :pipel
it 'raises an error' do
expect { processor.perform }.to raise_error(
described_class::IncludeError,
"Included file `lib/gitlab/ci/templates/template.yml` does not have valid YAML syntax!"
'`lib/gitlab/ci/templates/template.yml`: content does not have a valid YAML syntax'
)
end
end

View File

@ -28,6 +28,18 @@ RSpec.describe Gitlab::Ci::Config::Header::Spec, feature_category: :pipeline_com
end
end
context 'when spec contains a required value' do
let(:spec_hash) do
{ inputs: { foo: nil } }
end
it 'parses the config correctly' do
expect(config).to be_valid
expect(config.errors).to be_empty
expect(config.value).to eq({ inputs: { foo: {} } })
end
end
context 'when spec contains unknown keywords' do
let(:spec_hash) { { test: 123 } }
let(:expected_errors) { ['spec config contains unknown keys: test'] }

View File

@ -30,6 +30,15 @@ RSpec.describe Gitlab::Ci::Config::Yaml::Result, feature_category: :pipeline_com
end
end
context 'when the first document is undefined' do
it 'does not have header' do
result = described_class.new(config: [nil, { a: 1 }])
expect(result).not_to have_header
expect(result.content).to be_nil
end
end
it 'raises an error when reading a header when there is none' do
result = described_class.new(config: { b: 2 })

View File

@ -127,21 +127,6 @@ RSpec.describe Gitlab::Ci::Config::Yaml, feature_category: :pipeline_composition
end
context 'when the first document is a header' do
let(:yaml) do
<<~YAML
spec:
---
b: 2
YAML
end
it 'considers the first document as header and the second as content' do
expect(result).to be_valid
expect(result.error).to be_nil
expect(result.header).to eq({ spec: nil })
expect(result.content).to eq({ b: 2 })
end
context 'with explicit document start marker' do
let(:yaml) do
<<~YAML
@ -161,6 +146,51 @@ RSpec.describe Gitlab::Ci::Config::Yaml, feature_category: :pipeline_composition
end
end
context 'when first document is empty' do
let(:yaml) do
<<~YAML
---
---
b: 2
YAML
end
it 'considers the first document as header and the second as content' do
expect(result).not_to have_header
end
end
context 'when first document is an empty hash' do
let(:yaml) do
<<~YAML
{}
---
b: 2
YAML
end
it 'returns second document as a content' do
expect(result).not_to have_header
expect(result.content).to eq({ b: 2 })
end
end
context 'when first an array' do
let(:yaml) do
<<~YAML
---
- a
- b
---
b: 2
YAML
end
it 'considers the first document as header and the second as content' do
expect(result).not_to have_header
end
end
context 'when the first document is not a header' do
let(:yaml) do
<<~YAML

View File

@ -27,6 +27,12 @@ RSpec.describe Gitlab::Ci::Input::Arguments::Default, feature_category: :pipelin
expect(argument.to_hash).to eq({ website: 'https://gitlab.com' })
end
it 'returns an error if the default argument has not been recognized' do
argument = described_class.new(:website, { default: ['gitlab.com'] }, 'abc')
expect(argument).not_to be_valid
end
it 'returns an error if the argument has not been fabricated correctly' do
argument = described_class.new(:website, { required: 'https://gitlab.com' }, 'https://example.gitlab.com')
@ -40,6 +46,8 @@ RSpec.describe Gitlab::Ci::Input::Arguments::Default, feature_category: :pipelin
it 'does not match specs different configuration keyword' do
expect(described_class.matches?({ options: %w[a b] })).to be false
expect(described_class.matches?('a b c')).to be false
expect(described_class.matches?(%w[default a])).to be false
end
end
end

View File

@ -29,7 +29,7 @@ RSpec.describe Gitlab::Ci::Input::Arguments::Options, feature_category: :pipelin
argument = described_class.new(:website, { options: { a: 1 } }, 'opt1')
expect(argument).not_to be_valid
expect(argument.errors.first).to eq '`website` input: argument value opt1 not allowlisted'
expect(argument.errors.first).to eq '`website` input: argument specification invalid'
end
it 'returns an empty value if it is allowlisted' do
@ -47,6 +47,8 @@ RSpec.describe Gitlab::Ci::Input::Arguments::Options, feature_category: :pipelin
it 'does not match specs different configuration keyword' do
expect(described_class.matches?({ default: 'abc' })).to be false
expect(described_class.matches?(['options'])).to be false
expect(described_class.matches?('options')).to be false
end
end
end

View File

@ -34,6 +34,10 @@ RSpec.describe Gitlab::Ci::Input::Arguments::Required, feature_category: :pipeli
expect(described_class.matches?('')).to be true
end
it 'matches specs with an empty hash configuration' do
expect(described_class.matches?({})).to be true
end
it 'does not match specs with configuration' do
expect(described_class.matches?({ options: %w[a b] })).to be false
end

View File

@ -2,18 +2,13 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::LoadBalancing::ActionCableCallbacks, :request_store do
RSpec.describe Gitlab::Database::LoadBalancing::ActionCableCallbacks, :request_store, feature_category: :shared do
describe '.wrapper' do
it 'uses primary and then releases the connection and clears the session' do
it 'releases the connection and clears the session' do
expect(Gitlab::Database::LoadBalancing).to receive(:release_hosts)
expect(Gitlab::Database::LoadBalancing::Session).to receive(:clear_session)
described_class.wrapper.call(
nil,
lambda do
expect(Gitlab::Database::LoadBalancing::Session.current.use_primary?).to eq(true)
end
)
described_class.wrapper.call(nil, lambda {})
end
context 'with an exception' do

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Graphql::Subscriptions::ActionCableWithLoadBalancing, feature_category: :shared do
let(:session_class) { ::Gitlab::Database::LoadBalancing::Session }
let(:session) { instance_double(session_class) }
let(:event) { instance_double(::GraphQL::Subscriptions::Event) }
subject(:subscriptions) { described_class.new(schema: GitlabSchema) }
it 'forces use of DB primary when executing subscription updates' do
expect(session_class).to receive(:current).and_return(session)
expect(session).to receive(:use_primary!)
subscriptions.execute_update('sub:123', event, {})
end
end

View File

@ -226,7 +226,7 @@ RSpec.describe Gitlab::Metrics::Subscribers::ActiveRecord do
# Emulate Marginalia pre-pending comments
def sql(query, comments: true)
if comments && !%w[BEGIN COMMIT].include?(query)
if comments
"/*application:web,controller:badges,action:pipeline,correlation_id:01EYN39K9VMJC56Z7808N7RSRH*/ #{query}"
else
query
@ -244,8 +244,9 @@ RSpec.describe Gitlab::Metrics::Subscribers::ActiveRecord do
'SQL' | 'UPDATE users SET admin = true WHERE id = 10' | true | true | false
'CACHE' | 'SELECT * FROM users WHERE id = 10' | true | false | true
'SCHEMA' | "SELECT attr.attname FROM pg_attribute attr INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey) WHERE cons.contype = 'p' AND cons.conrelid = '\"projects\"'::regclass" | false | false | false
nil | 'BEGIN' | false | false | false
nil | 'COMMIT' | false | false | false
'TRANSACTION' | 'BEGIN' | false | false | false
'TRANSACTION' | 'COMMIT' | false | false | false
'TRANSACTION' | 'ROLLBACK' | false | false | false
end
with_them do
@ -291,7 +292,7 @@ RSpec.describe Gitlab::Metrics::Subscribers::ActiveRecord do
# Emulate Marginalia pre-pending comments
def sql(query, comments: true)
if comments && !%w[BEGIN COMMIT].include?(query)
if comments
"/*application:web,controller:badges,action:pipeline,correlation_id:01EYN39K9VMJC56Z7808N7RSRH*/ #{query}"
else
query
@ -313,8 +314,9 @@ RSpec.describe Gitlab::Metrics::Subscribers::ActiveRecord do
'CACHE' | 'SELECT pg_last_wal_replay_lsn()::text AS location' | true | false | true | true
'CACHE' | 'SELECT * FROM users WHERE id = 10' | true | false | true | false
'SCHEMA' | "SELECT attr.attname FROM pg_attribute attr INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey) WHERE cons.contype = 'p' AND cons.conrelid = '\"projects\"'::regclass" | false | false | false | false
nil | 'BEGIN' | false | false | false | false
nil | 'COMMIT' | false | false | false | false
'TRANSACTION' | 'BEGIN' | false | false | false | false
'TRANSACTION' | 'COMMIT' | false | false | false | false
'TRANSACTION' | 'ROLLBACK' | false | false | false | false
end
with_them do

Some files were not shown because too many files have changed in this diff Show More