Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
eb239d31bf
commit
820c5f6d5c
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
b8709da8be08aa79642e96dd95341cf2d9eb50d2
|
||||
ebb00e313b5ad11c8fd0b32d1843c3490a43d4c9
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -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: [] },
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -180,6 +180,10 @@ $tabs-holder-z-index: 250;
|
|||
.content + .content {
|
||||
@include gl-border-t;
|
||||
}
|
||||
|
||||
.notes-content {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.inline-diff-view {
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -421,7 +421,7 @@ To remove an issue from an epic:
|
|||
The **Remove issue** warning appears.
|
||||
1. Select **Remove**.
|
||||
|
||||

|
||||

|
||||
|
||||
### Reorder issues assigned to an epic
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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` |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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/)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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/)
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
166
public/-/sp.js
166
public/-/sp.js
File diff suppressed because one or more lines are too long
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'] }
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
Loading…
Reference in New Issue