summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml2
-rw-r--r--app/assets/javascripts/design_management/components/design_note_pin.vue14
-rw-r--r--app/assets/javascripts/design_management/components/design_overlay.vue4
-rw-r--r--app/assets/javascripts/lib/utils/css_utils.js19
-rw-r--r--app/assets/javascripts/packages/list/constants.js2
-rw-r--r--app/assets/javascripts/packages/shared/utils.js2
-rw-r--r--app/assets/javascripts/profile/gl_crop.js4
-rw-r--r--app/assets/stylesheets/application.scss1
-rw-r--r--app/assets/stylesheets/components/design_management/design.scss70
-rw-r--r--app/assets/stylesheets/lazy_bundles/cropper.css378
-rw-r--r--app/controllers/help_controller.rb1
-rw-r--r--app/controllers/ide_controller.rb2
-rw-r--r--app/controllers/import/available_namespaces_controller.rb2
-rw-r--r--app/controllers/import/base_controller.rb1
-rw-r--r--app/controllers/import/bulk_imports_controller.rb2
-rw-r--r--app/controllers/import/gitlab_groups_controller.rb2
-rw-r--r--app/controllers/invites_controller.rb2
-rw-r--r--app/controllers/jira_connect/application_controller.rb2
-rw-r--r--app/controllers/jwt_controller.rb2
-rw-r--r--app/controllers/notification_settings_controller.rb2
-rw-r--r--app/controllers/oauth/jira/authorizations_controller.rb2
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb2
-rw-r--r--app/controllers/registrations/experience_levels_controller.rb2
-rw-r--r--app/controllers/registrations_controller.rb2
-rw-r--r--app/controllers/repositories/git_http_client_controller.rb2
-rw-r--r--app/controllers/runner_setup_controller.rb2
-rw-r--r--app/controllers/search_controller.rb2
-rw-r--r--app/controllers/sent_notifications_controller.rb2
-rw-r--r--app/controllers/sessions_controller.rb2
-rw-r--r--app/controllers/snippets/application_controller.rb2
-rw-r--r--app/controllers/snippets/notes_controller.rb2
-rw-r--r--app/controllers/uploads_controller.rb2
-rw-r--r--app/controllers/user_callouts_controller.rb2
-rw-r--r--app/controllers/users/terms_controller.rb2
-rw-r--r--app/controllers/users_controller.rb2
-rw-r--r--app/graphql/types/merge_request_type.rb7
-rw-r--r--app/graphql/types/package_type_enum.rb2
-rw-r--r--app/models/ci/build.rb5
-rw-r--r--app/models/ci/pipeline.rb7
-rw-r--r--app/models/packages/event.rb2
-rw-r--r--app/models/packages/package.rb2
-rw-r--r--app/presenters/packages/pypi/package_presenter.rb4
-rw-r--r--app/serializers/build_details_entity.rb2
-rw-r--r--app/serializers/pipeline_entity.rb4
-rw-r--r--app/services/ci/build_report_result_service.rb29
-rw-r--r--app/services/system_notes/issuables_service.rb21
-rw-r--r--app/views/profiles/show.html.haml2
-rw-r--r--changelogs/unreleased/219951-improve-styling-of-design-pins.yml5
-rw-r--r--changelogs/unreleased/229918-track-additional-issue-change-events.yml5
-rw-r--r--changelogs/unreleased/231467-reduce-cached-query-jobscontroller-show.yml5
-rw-r--r--changelogs/unreleased/242285-approve-graphql-ce.yml5
-rw-r--r--changelogs/unreleased/251113-create_framework_model.yml5
-rw-r--r--changelogs/unreleased/251935-add-default_branch_name-column-to-namespace_settings-table.yml5
-rw-r--r--changelogs/unreleased/eb-unit-tests-parsed-usage-ping.yml5
-rw-r--r--changelogs/unreleased/fix-spelling-of-pypi.yml5
-rw-r--r--changelogs/unreleased/mc-feature-fix-keep-path-artifact-locking.yml5
-rw-r--r--changelogs/unreleased/mc-feature-project-ci-lint-api.yml5
-rw-r--r--changelogs/unreleased/tz-lazy-load-cropper.yml5
-rw-r--r--config/application.rb1
-rw-r--r--config/feature_flags/development/track_unique_test_cases_parsed.yml7
-rw-r--r--config/feature_flags/development/usage_data_i_testing_test_case_parsed.yml7
-rw-r--r--db/migrate/20200919200318_add_default_branch_name_to_namespace_settings.rb15
-rw-r--r--db/migrate/20200919204155_add_text_limit_to_namespace_settings_default_branch_name.rb19
-rw-r--r--db/migrate/20200922075244_add_compliance_framework_model.rb32
-rw-r--r--db/schema_migrations/202009192003181
-rw-r--r--db/schema_migrations/202009192041551
-rw-r--r--db/schema_migrations/202009220752441
-rw-r--r--db/structure.sql36
-rw-r--r--doc/administration/geo/replication/datatypes.md2
-rw-r--r--doc/administration/gitaly/index.md13
-rw-r--r--doc/administration/gitaly/praefect.md4
-rw-r--r--doc/administration/packages/index.md2
-rw-r--r--doc/administration/reference_architectures/10k_users.md2
-rw-r--r--doc/administration/reference_architectures/25k_users.md2
-rw-r--r--doc/administration/reference_architectures/2k_users.md2
-rw-r--r--doc/administration/reference_architectures/3k_users.md2
-rw-r--r--doc/administration/reference_architectures/50k_users.md2
-rw-r--r--doc/administration/reference_architectures/5k_users.md2
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql67
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json179
-rw-r--r--doc/api/graphql/reference/index.md27
-rw-r--r--doc/api/lint.md48
-rw-r--r--doc/development/internal_api.md1
-rw-r--r--doc/integration/elasticsearch.md2
-rw-r--r--doc/raketasks/backup_restore.md2
-rw-r--r--doc/user/packages/pypi_repository/index.md20
-rw-r--r--lib/api/entities/ci/lint/result.rb16
-rw-r--r--lib/api/lint.rb19
-rw-r--r--lib/gitlab/ci/lint.rb7
-rw-r--r--lib/gitlab/ci/pipeline/chain/config/process.rb2
-rw-r--r--lib/gitlab/ci/reports/test_case.rb6
-rw-r--r--lib/gitlab/ci/reports/test_suite.rb16
-rw-r--r--lib/gitlab/redis/hll.rb16
-rw-r--r--lib/gitlab/usage_data_counters/issue_activity_unique_counter.rb35
-rw-r--r--lib/gitlab/usage_data_counters/known_events.yml33
-rw-r--r--locale/gitlab.pot4
-rwxr-xr-xscripts/review_apps/review-apps.sh4
-rw-r--r--spec/controllers/every_controller_spec.rb14
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb42
-rw-r--r--spec/factories/packages.rb4
-rw-r--r--spec/frontend/design_management/components/__snapshots__/design_note_pin_spec.js.snap18
-rw-r--r--spec/frontend/design_management/components/design_note_pin_spec.js17
-rw-r--r--spec/frontend/design_management/components/design_overlay_spec.js6
-rw-r--r--spec/frontend/packages/details/store/getters_spec.js2
-rw-r--r--spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap6
-rw-r--r--spec/frontend/packages/shared/utils_spec.js2
-rw-r--r--spec/graphql/types/merge_request_type_spec.rb3
-rw-r--r--spec/graphql/types/package_type_enum_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/lint_spec.rb43
-rw-r--r--spec/lib/gitlab/ci/reports/test_suite_spec.rb14
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb19
-rw-r--r--spec/lib/gitlab/redis/hll_spec.rb30
-rw-r--r--spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb70
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb4
-rw-r--r--spec/models/ci/build_spec.rb48
-rw-r--r--spec/requests/api/graphql/project/merge_requests_spec.rb22
-rw-r--r--spec/requests/api/lint_spec.rb111
-rw-r--r--spec/requests/api/pypi_packages_spec.rb146
-rw-r--r--spec/services/ci/build_report_result_service_spec.rb21
-rw-r--r--spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/services/packages_shared_examples.rb1
122 files changed, 1723 insertions, 279 deletions
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index fab33dd3c07..0fd2c01bb04 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -72,7 +72,7 @@ review-deploy:
- download_chart
- date
- deploy || (display_deployment_debug && exit 1)
- - disable_sign_ups
+ - disable_sign_ups || (delete_release && exit 1)
# When the job is manual, review-qa-smoke is also manual and we don't want people
# to have to manually start the jobs in sequence, so we do it for them.
- '[ -z $CI_JOB_MANUAL ] || play_job "review-qa-smoke"'
diff --git a/app/assets/javascripts/design_management/components/design_note_pin.vue b/app/assets/javascripts/design_management/components/design_note_pin.vue
index 2b5e62c2870..320e0654aab 100644
--- a/app/assets/javascripts/design_management/components/design_note_pin.vue
+++ b/app/assets/javascripts/design_management/components/design_note_pin.vue
@@ -17,19 +17,11 @@ export default {
required: false,
default: null,
},
- repositioning: {
- type: Boolean,
- required: false,
- default: false,
- },
},
computed: {
isNewNote() {
return this.label === null;
},
- pinStyle() {
- return this.repositioning ? { ...this.position, cursor: 'move' } : this.position;
- },
pinLabel() {
return this.isNewNote
? __('Comment form position')
@@ -41,13 +33,13 @@ export default {
<template>
<button
- :style="pinStyle"
+ :style="position"
:aria-label="pinLabel"
:class="{
- 'btn-transparent comment-indicator': isNewNote,
+ 'btn-transparent comment-indicator gl-p-0': isNewNote,
'js-image-badge badge badge-pill': !isNewNote,
}"
- class="design-pin gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-0"
+ class="gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-font-lg gl-outline-0!"
type="button"
@mousedown="$emit('mousedown', $event)"
@mouseup="$emit('mouseup', $event)"
diff --git a/app/assets/javascripts/design_management/components/design_overlay.vue b/app/assets/javascripts/design_management/components/design_overlay.vue
index 5c4a3ab5f94..88f3ce0b8ea 100644
--- a/app/assets/javascripts/design_management/components/design_overlay.vue
+++ b/app/assets/javascripts/design_management/components/design_overlay.vue
@@ -266,7 +266,7 @@ export default {
type="button"
role="button"
:aria-label="$options.i18n.newCommentButtonLabel"
- class="gl-absolute gl-w-full gl-h-full gl-p-0 gl-top-0 gl-left-0 gl-outline-0! btn-transparent design-detail-overlay-add-comment"
+ class="gl-absolute gl-w-full gl-h-full gl-p-0 gl-top-0 gl-left-0 gl-outline-0! btn-transparent gl-hover-cursor-crosshair"
data-qa-selector="design_image_button"
@mouseup="onAddCommentMouseup"
></button>
@@ -276,7 +276,6 @@ export default {
v-if="resolvedDiscussionsExpanded || !note.resolved"
:key="note.id"
:label="note.index"
- :repositioning="isMovingNote(note.id)"
:position="
isMovingNote(note.id) && movingNoteNewPosition
? getNotePositionStyle(movingNoteNewPosition)
@@ -290,7 +289,6 @@ export default {
<design-note-pin
v-if="currentCommentForm"
:position="currentCommentPositionStyle"
- :repositioning="isMovingCurrentComment"
@mousedown.stop="onNoteMousedown"
@mouseup.stop="onNoteMouseup"
/>
diff --git a/app/assets/javascripts/lib/utils/css_utils.js b/app/assets/javascripts/lib/utils/css_utils.js
new file mode 100644
index 00000000000..90213221443
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/css_utils.js
@@ -0,0 +1,19 @@
+export function loadCSSFile(path) {
+ return new Promise(resolve => {
+ if (document.querySelector(`link[href="${path}"]`)) {
+ resolve();
+ } else {
+ const linkElement = document.createElement('link');
+ linkElement.type = 'text/css';
+ linkElement.rel = 'stylesheet';
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ linkElement.media = 'screen,print';
+ linkElement.onload = () => {
+ resolve();
+ };
+ linkElement.href = path;
+
+ document.head.appendChild(linkElement);
+ }
+ });
+}
diff --git a/app/assets/javascripts/packages/list/constants.js b/app/assets/javascripts/packages/list/constants.js
index 37242822e35..6a0e92bff2d 100644
--- a/app/assets/javascripts/packages/list/constants.js
+++ b/app/assets/javascripts/packages/list/constants.js
@@ -82,7 +82,7 @@ export const PACKAGE_REGISTRY_TABS = [
type: PackageType.NUGET,
},
{
- title: s__('PackageRegistry|PyPi'),
+ title: s__('PackageRegistry|PyPI'),
type: PackageType.PYPI,
},
];
diff --git a/app/assets/javascripts/packages/shared/utils.js b/app/assets/javascripts/packages/shared/utils.js
index a0c7389651d..b0807558266 100644
--- a/app/assets/javascripts/packages/shared/utils.js
+++ b/app/assets/javascripts/packages/shared/utils.js
@@ -18,7 +18,7 @@ export const getPackageTypeLabel = packageType => {
case PackageType.NUGET:
return s__('PackageType|NuGet');
case PackageType.PYPI:
- return s__('PackageType|PyPi');
+ return s__('PackageType|PyPI');
case PackageType.COMPOSER:
return s__('PackageType|Composer');
diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js
index 55bc9fb8955..ecb69422287 100644
--- a/app/assets/javascripts/profile/gl_crop.js
+++ b/app/assets/javascripts/profile/gl_crop.js
@@ -3,6 +3,7 @@
import $ from 'jquery';
import 'cropper';
import { isString } from 'lodash';
+import { loadCSSFile } from '../lib/utils/css_utils';
(() => {
// Matches everything but the file name
@@ -180,6 +181,9 @@ import { isString } from 'lodash';
}
}
+ const cropModal = document.querySelector('.modal-profile-crop');
+ if (cropModal) loadCSSFile(cropModal.dataset.cropperCssPath);
+
$.fn.glCrop = function(opts) {
return this.each(function() {
return $(this).data('glcrop', new GitLabCrop(this, opts));
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index cae886bf846..4b1139d2354 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -6,7 +6,6 @@
@import '@gitlab/at.js/dist/css/jquery.atwho';
@import 'dropzone/dist/basic';
@import 'select2';
-@import 'cropper';
// GitLab UI framework
@import 'framework';
diff --git a/app/assets/stylesheets/components/design_management/design.scss b/app/assets/stylesheets/components/design_management/design.scss
index f198c06c2df..81f2091e915 100644
--- a/app/assets/stylesheets/components/design_management/design.scss
+++ b/app/assets/stylesheets/components/design_management/design.scss
@@ -1,3 +1,6 @@
+$design-pin-diameter: 28px;
+$t-gray-a-16-design-pin: rgba($black, 0.16);
+
.layout-page.design-detail-layout {
max-height: 100vh;
}
@@ -9,34 +12,61 @@
top: 35px;
}
- .design-pin {
- transition: opacity $gl-transition-duration-medium $general-hover-transition-curve;
-
- &.inactive {
- @include gl-opacity-5;
-
- &:hover {
- @include gl-opacity-10;
- }
- }
- }
-
.badge.badge-pill {
display: flex;
- height: 28px;
- width: 28px;
- background-color: $blue-400;
+ height: $design-pin-diameter;
+ width: $design-pin-diameter;
+ box-sizing: content-box;
+ background-color: $purple-500;
color: $white;
- border: $white 1px solid;
+ font-weight: $gl-font-weight-bold;
border-radius: 50%;
+ z-index: 1;
+ padding: 0;
&.resolved {
background-color: $gray-500;
}
}
- .design-detail-overlay-add-comment {
- cursor: crosshair;
+ .comment-indicator {
+ border-radius: 50%;
+ }
+
+ .comment-indicator,
+ .frame .badge.badge-pill {
+ &:active {
+ cursor: grabbing;
+ }
+ }
+
+ /**
+ * Design pin that overlays the design
+ */
+ .frame .badge.badge-pill {
+ box-shadow: 0 2px 4px $t-gray-a-08, 0 0 1px $t-gray-a-24;
+ border: $white 2px solid;
+ will-change: transform, box-shadow, opacity;
+ // NOTE: verbose transition property required for Safari
+ transition: transform $general-hover-transition-duration linear, box-shadow $general-hover-transition-duration linear, opacity $general-hover-transition-duration linear;
+ transform-origin: 0 0;
+ transform: translate(-50%, -50%);
+
+ &:hover {
+ transform: scale(1.2) translate(-50%, -50%);
+ }
+
+ &:active {
+ box-shadow: 0 0 4px $t-gray-a-16-design-pin, 0 4px 12px $t-gray-a-16-design-pin;
+ }
+
+ &.inactive {
+ @include gl-opacity-5;
+
+ &:hover {
+ @include gl-opacity-10;
+ }
+ }
}
}
@@ -105,8 +135,8 @@
border-left: 1px solid $gray-100;
position: absolute;
left: 28px;
- top: -18px;
- height: 18px;
+ top: -17px;
+ height: 17px;
}
.design-note {
diff --git a/app/assets/stylesheets/lazy_bundles/cropper.css b/app/assets/stylesheets/lazy_bundles/cropper.css
new file mode 100644
index 00000000000..9c7fdded117
--- /dev/null
+++ b/app/assets/stylesheets/lazy_bundles/cropper.css
@@ -0,0 +1,378 @@
+/*!
+ * Cropper v2.3.0
+ * https://github.com/fengyuanchen/cropper
+ *
+ * Copyright (c) 2014-2016 Fengyuan Chen and contributors
+ * Released under the MIT license
+ *
+ * Date: 2016-02-22T02:13:13.332Z
+ */
+.cropper-container {
+ font-size: 0;
+ line-height: 0;
+
+ position: relative;
+
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+
+ direction: ltr !important;
+ touch-action: none;
+ -webkit-tap-highlight-color: transparent;
+ -webkit-touch-callout: none;
+}
+
+.cropper-container img {
+ display: block;
+
+ width: 100%;
+ min-width: 0 !important;
+ max-width: none !important;
+ height: 100%;
+ min-height: 0 !important;
+ max-height: none !important;
+
+ image-orientation: 0deg !important;
+}
+
+.cropper-wrap-box,
+.cropper-canvas,
+.cropper-drag-box,
+.cropper-crop-box,
+.cropper-modal {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+}
+
+.cropper-wrap-box {
+ overflow: hidden;
+}
+
+.cropper-drag-box {
+ opacity: 0;
+ background-color: #fff;
+
+ filter: alpha(opacity=0);
+}
+
+.cropper-modal {
+ opacity: 0.5;
+ background-color: #000;
+
+ filter: alpha(opacity=50);
+}
+
+.cropper-view-box {
+ display: block;
+ overflow: hidden;
+
+ width: 100%;
+ height: 100%;
+
+ outline: 1px solid #39f;
+ outline-color: rgba(51, 153, 255, 0.75);
+}
+
+.cropper-dashed {
+ position: absolute;
+
+ display: block;
+
+ opacity: 0.5;
+ border: 0 dashed #eee;
+
+ filter: alpha(opacity=50);
+}
+
+.cropper-dashed.dashed-h {
+ top: 33.33333%;
+ left: 0;
+
+ width: 100%;
+ height: 33.33333%;
+
+ border-top-width: 1px;
+ border-bottom-width: 1px;
+}
+
+.cropper-dashed.dashed-v {
+ top: 0;
+ left: 33.33333%;
+
+ width: 33.33333%;
+ height: 100%;
+
+ border-right-width: 1px;
+ border-left-width: 1px;
+}
+
+.cropper-center {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+
+ display: block;
+
+ width: 0;
+ height: 0;
+
+ opacity: 0.75;
+
+ filter: alpha(opacity=75);
+}
+
+.cropper-center::before,
+.cropper-center::after {
+ position: absolute;
+
+ display: block;
+
+ content: ' ';
+
+ background-color: #eee;
+}
+
+.cropper-center::before {
+ top: 0;
+ left: -3px;
+
+ width: 7px;
+ height: 1px;
+}
+
+.cropper-center::after {
+ top: -3px;
+ left: 0;
+
+ width: 1px;
+ height: 7px;
+}
+
+.cropper-face,
+.cropper-line,
+.cropper-point {
+ position: absolute;
+
+ display: block;
+
+ width: 100%;
+ height: 100%;
+
+ opacity: 0.1;
+
+ filter: alpha(opacity=10);
+}
+
+.cropper-face {
+ top: 0;
+ left: 0;
+
+ background-color: #fff;
+}
+
+.cropper-line {
+ background-color: #39f;
+}
+
+.cropper-line.line-e {
+ top: 0;
+ right: -3px;
+
+ width: 5px;
+
+ cursor: e-resize;
+}
+
+.cropper-line.line-n {
+ top: -3px;
+ left: 0;
+
+ height: 5px;
+
+ cursor: n-resize;
+}
+
+.cropper-line.line-w {
+ top: 0;
+ left: -3px;
+
+ width: 5px;
+
+ cursor: w-resize;
+}
+
+.cropper-line.line-s {
+ bottom: -3px;
+ left: 0;
+
+ height: 5px;
+
+ cursor: s-resize;
+}
+
+.cropper-point {
+ width: 5px;
+ height: 5px;
+
+ opacity: 0.75;
+ background-color: #39f;
+
+ filter: alpha(opacity=75);
+}
+
+.cropper-point.point-e {
+ top: 50%;
+ right: -3px;
+
+ margin-top: -3px;
+
+ cursor: e-resize;
+}
+
+.cropper-point.point-n {
+ top: -3px;
+ left: 50%;
+
+ margin-left: -3px;
+
+ cursor: n-resize;
+}
+
+.cropper-point.point-w {
+ top: 50%;
+ left: -3px;
+
+ margin-top: -3px;
+
+ cursor: w-resize;
+}
+
+.cropper-point.point-s {
+ bottom: -3px;
+ left: 50%;
+
+ margin-left: -3px;
+
+ cursor: s-resize;
+}
+
+.cropper-point.point-ne {
+ top: -3px;
+ right: -3px;
+
+ cursor: ne-resize;
+}
+
+.cropper-point.point-nw {
+ top: -3px;
+ left: -3px;
+
+ cursor: nw-resize;
+}
+
+.cropper-point.point-sw {
+ bottom: -3px;
+ left: -3px;
+
+ cursor: sw-resize;
+}
+
+.cropper-point.point-se {
+ right: -3px;
+ bottom: -3px;
+
+ width: 20px;
+ height: 20px;
+
+ cursor: se-resize;
+
+ opacity: 1;
+
+ filter: alpha(opacity=100);
+}
+
+.cropper-point.point-se::before {
+ position: absolute;
+ right: -50%;
+ bottom: -50%;
+
+ display: block;
+
+ width: 200%;
+ height: 200%;
+
+ content: ' ';
+
+ opacity: 0;
+ background-color: #39f;
+
+ filter: alpha(opacity=0);
+}
+
+@media (min-width: 768px) {
+ .cropper-point.point-se {
+ width: 15px;
+ height: 15px;
+ }
+}
+
+@media (min-width: 992px) {
+ .cropper-point.point-se {
+ width: 10px;
+ height: 10px;
+ }
+}
+
+@media (min-width: 1200px) {
+ .cropper-point.point-se {
+ width: 5px;
+ height: 5px;
+
+ opacity: 0.75;
+
+ filter: alpha(opacity=75);
+ }
+}
+
+.cropper-invisible {
+ opacity: 0;
+
+ filter: alpha(opacity=0);
+}
+
+.cropper-bg {
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');
+}
+
+.cropper-hide {
+ position: absolute;
+
+ display: block;
+
+ width: 0;
+ height: 0;
+}
+
+.cropper-hidden {
+ display: none !important;
+}
+
+.cropper-move {
+ cursor: move;
+}
+
+.cropper-crop {
+ cursor: crosshair;
+}
+
+.cropper-disabled .cropper-drag-box,
+.cropper-disabled .cropper-face,
+.cropper-disabled .cropper-line,
+.cropper-disabled .cropper-point {
+ cursor: not-allowed;
+}
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index a1bbcf34f69..14de8d6c6ab 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -2,6 +2,7 @@
class HelpController < ApplicationController
skip_before_action :authenticate_user!, unless: :public_visibility_restricted?
+ feature_category :not_owned
layout 'help'
diff --git a/app/controllers/ide_controller.rb b/app/controllers/ide_controller.rb
index 2c17f5b5542..8c0414ad5da 100644
--- a/app/controllers/ide_controller.rb
+++ b/app/controllers/ide_controller.rb
@@ -11,6 +11,8 @@ class IdeController < ApplicationController
push_frontend_feature_flag(:schema_linting)
end
+ feature_category :web_ide
+
def index
Gitlab::UsageDataCounters::WebIdeCounter.increment_views_count
end
diff --git a/app/controllers/import/available_namespaces_controller.rb b/app/controllers/import/available_namespaces_controller.rb
index 7983b4f20b5..c6211b33d28 100644
--- a/app/controllers/import/available_namespaces_controller.rb
+++ b/app/controllers/import/available_namespaces_controller.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Import::AvailableNamespacesController < ApplicationController
+ feature_category :importers
+
def index
render json: NamespaceSerializer.new.represent(current_user.manageable_groups_with_routes)
end
diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb
index 8a7a4c92b37..151ba46e629 100644
--- a/app/controllers/import/base_controller.rb
+++ b/app/controllers/import/base_controller.rb
@@ -4,6 +4,7 @@ class Import::BaseController < ApplicationController
include ActionView::Helpers::SanitizeHelper
before_action :import_rate_limit, only: [:create]
+ feature_category :importers
def status
respond_to do |format|
diff --git a/app/controllers/import/bulk_imports_controller.rb b/app/controllers/import/bulk_imports_controller.rb
index 58b9f8c0fbb..cad181f0d64 100644
--- a/app/controllers/import/bulk_imports_controller.rb
+++ b/app/controllers/import/bulk_imports_controller.rb
@@ -4,6 +4,8 @@ class Import::BulkImportsController < ApplicationController
before_action :ensure_group_import_enabled
before_action :verify_blocked_uri, only: :status
+ feature_category :importers
+
def configure
session[access_token_key] = params[access_token_key]&.strip
session[url_key] = params[url_key]
diff --git a/app/controllers/import/gitlab_groups_controller.rb b/app/controllers/import/gitlab_groups_controller.rb
index 330af68385e..d8118477a80 100644
--- a/app/controllers/import/gitlab_groups_controller.rb
+++ b/app/controllers/import/gitlab_groups_controller.rb
@@ -6,6 +6,8 @@ class Import::GitlabGroupsController < ApplicationController
before_action :ensure_group_import_enabled
before_action :import_rate_limit, only: %i[create]
+ feature_category :importers
+
def create
unless file_is_valid?(group_params[:file])
return redirect_back_or_default(options: { alert: s_('GroupImport|Unable to process group import file') })
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 591ded7630c..c7b8486d1c9 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -12,6 +12,8 @@ class InvitesController < ApplicationController
respond_to :html
+ feature_category :authentication_and_authorization
+
def show
track_new_user_invite_experiment('opened')
accept if skip_invitation_prompt?
diff --git a/app/controllers/jira_connect/application_controller.rb b/app/controllers/jira_connect/application_controller.rb
index a84f25998a6..9c311f92b69 100644
--- a/app/controllers/jira_connect/application_controller.rb
+++ b/app/controllers/jira_connect/application_controller.rb
@@ -7,6 +7,8 @@ class JiraConnect::ApplicationController < ApplicationController
skip_before_action :verify_authenticity_token
before_action :verify_atlassian_jwt!
+ feature_category :integrations
+
attr_reader :current_jira_installation
private
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
index 3e7755046cd..5199bb25c8c 100644
--- a/app/controllers/jwt_controller.rb
+++ b/app/controllers/jwt_controller.rb
@@ -8,6 +8,8 @@ class JwtController < ApplicationController
# Add this before other actions, since we want to have the user or project
prepend_before_action :auth_user, :authenticate_project_or_user
+ feature_category :authentication_and_authorization
+
SERVICES = {
Auth::ContainerRegistryAuthenticationService::AUDIENCE => Auth::ContainerRegistryAuthenticationService
}.freeze
diff --git a/app/controllers/notification_settings_controller.rb b/app/controllers/notification_settings_controller.rb
index e5d4a4bb073..7d8c035c852 100644
--- a/app/controllers/notification_settings_controller.rb
+++ b/app/controllers/notification_settings_controller.rb
@@ -3,6 +3,8 @@
class NotificationSettingsController < ApplicationController
before_action :authenticate_user!
+ feature_category :users
+
def create
return render_404 unless can_read?(resource)
diff --git a/app/controllers/oauth/jira/authorizations_controller.rb b/app/controllers/oauth/jira/authorizations_controller.rb
index f552b0dc10c..f23149c8544 100644
--- a/app/controllers/oauth/jira/authorizations_controller.rb
+++ b/app/controllers/oauth/jira/authorizations_controller.rb
@@ -8,6 +8,8 @@ class Oauth::Jira::AuthorizationsController < ApplicationController
skip_before_action :authenticate_user!
skip_before_action :verify_authenticity_token
+ feature_category :integrations
+
# 1. Rewire Jira OAuth initial request to our stablished OAuth authorization URL.
def new
session[:redirect_uri] = params['redirect_uri']
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 2708e6669e7..c9791703413 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -11,6 +11,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
protect_from_forgery except: [:kerberos, :saml, :cas3, :failure], with: :exception, prepend: true
+ feature_category :authentication_and_authorization
+
def handle_omniauth
omniauth_flow(Gitlab::Auth::OAuth)
end
diff --git a/app/controllers/registrations/experience_levels_controller.rb b/app/controllers/registrations/experience_levels_controller.rb
index 38cffff91eb..eb2b899d20e 100644
--- a/app/controllers/registrations/experience_levels_controller.rb
+++ b/app/controllers/registrations/experience_levels_controller.rb
@@ -7,6 +7,8 @@ module Registrations
before_action :check_experiment_enabled
before_action :ensure_namespace_path_param
+ feature_category :navigation
+
def update
current_user.experience_level = params[:experience_level]
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 204520a3e71..99b97bca73d 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -15,6 +15,8 @@ class RegistrationsController < Devise::RegistrationsController
if: -> { action_name == 'create' && Gitlab::CurrentSettings.current_application_settings.enforce_terms? }
before_action :load_recaptcha, only: :new
+ feature_category :authentication_and_authorization
+
def new
if experiment_enabled?(:signup_flow)
track_experiment_event(:terms_opt_in, 'start')
diff --git a/app/controllers/repositories/git_http_client_controller.rb b/app/controllers/repositories/git_http_client_controller.rb
index e02955433b2..de452aa69b7 100644
--- a/app/controllers/repositories/git_http_client_controller.rb
+++ b/app/controllers/repositories/git_http_client_controller.rb
@@ -20,6 +20,8 @@ module Repositories
prepend_before_action :authenticate_user, :parse_repo_path
+ feature_category :source_code_management
+
private
def download_request?
diff --git a/app/controllers/runner_setup_controller.rb b/app/controllers/runner_setup_controller.rb
index 2cb204b729c..e0e9c5b7c23 100644
--- a/app/controllers/runner_setup_controller.rb
+++ b/app/controllers/runner_setup_controller.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class RunnerSetupController < ApplicationController
+ feature_category :continuous_integration
+
def platforms
render json: Gitlab::Ci::RunnerInstructions::OS.merge(Gitlab::Ci::RunnerInstructions::OTHER_ENVIRONMENTS)
end
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index b5e221a8894..5bc01cbb354 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -25,6 +25,8 @@ class SearchController < ApplicationController
layout 'search'
+ feature_category :global_search
+
def show
@project = search_service.project
@group = search_service.group
diff --git a/app/controllers/sent_notifications_controller.rb b/app/controllers/sent_notifications_controller.rb
index 20134de81a0..db07b212d00 100644
--- a/app/controllers/sent_notifications_controller.rb
+++ b/app/controllers/sent_notifications_controller.rb
@@ -3,6 +3,8 @@
class SentNotificationsController < ApplicationController
skip_before_action :authenticate_user!
+ feature_category :users
+
def unsubscribe
@sent_notification = SentNotification.for(params[:id])
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 318553b5e0a..ae9744b224a 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -49,6 +49,8 @@ class SessionsController < Devise::SessionsController
# token mismatch.
protect_from_forgery with: :exception, prepend: true, except: :destroy
+ feature_category :authentication_and_authorization
+
CAPTCHA_HEADER = 'X-GitLab-Show-Login-Captcha'
MAX_FAILED_LOGIN_ATTEMPTS = 5
diff --git a/app/controllers/snippets/application_controller.rb b/app/controllers/snippets/application_controller.rb
index a533e46a75d..f259f4569ef 100644
--- a/app/controllers/snippets/application_controller.rb
+++ b/app/controllers/snippets/application_controller.rb
@@ -4,6 +4,8 @@ class Snippets::ApplicationController < ApplicationController
include FindSnippet
include SnippetAuthorizations
+ feature_category :snippets
+
private
def authorize_read_snippet!
diff --git a/app/controllers/snippets/notes_controller.rb b/app/controllers/snippets/notes_controller.rb
index a7e8ef0798b..8532257cb8d 100644
--- a/app/controllers/snippets/notes_controller.rb
+++ b/app/controllers/snippets/notes_controller.rb
@@ -8,6 +8,8 @@ class Snippets::NotesController < ApplicationController
before_action :authorize_read_snippet!, only: [:show, :index]
before_action :authorize_create_note!, only: [:create]
+ feature_category :snippets
+
private
def note
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index 9510734bc9b..6692c285335 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -25,6 +25,8 @@ class UploadsController < ApplicationController
before_action :authorize_create_access!, only: [:create, :authorize]
before_action :verify_workhorse_api!, only: [:authorize]
+ feature_category :not_owned
+
def uploader_class
PersonalFileUploader
end
diff --git a/app/controllers/user_callouts_controller.rb b/app/controllers/user_callouts_controller.rb
index 06f422b9d90..cfec9d6d905 100644
--- a/app/controllers/user_callouts_controller.rb
+++ b/app/controllers/user_callouts_controller.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class UserCalloutsController < ApplicationController
+ feature_category :navigation
+
def create
callout = ensure_callout
diff --git a/app/controllers/users/terms_controller.rb b/app/controllers/users/terms_controller.rb
index 231e449f733..be670658048 100644
--- a/app/controllers/users/terms_controller.rb
+++ b/app/controllers/users/terms_controller.rb
@@ -14,6 +14,8 @@ module Users
layout 'terms'
+ feature_category :users
+
def index
@redirect = redirect_path
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index e7af60beade..672f36dedc0 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -21,6 +21,8 @@ class UsersController < ApplicationController
before_action :authorize_read_user_profile!,
only: [:calendar, :calendar_activities, :groups, :projects, :contributed_projects, :starred_projects, :snippets]
+ feature_category :users
+
def show
respond_to do |format|
format.html
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index 573818b1b7a..adb689baf6a 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -152,6 +152,13 @@ module Types
field :auto_merge_enabled, GraphQL::BOOLEAN_TYPE, null: false,
description: 'Indicates if auto merge is enabled for the merge request'
+ field :approved_by, Types::UserType.connection_type, null: true,
+ description: 'Users who approved the merge request'
+
+ def approved_by
+ object.approved_by_users
+ end
+
def diff_stats(path: nil)
stats = Array.wrap(object.diff_stats&.to_a)
diff --git a/app/graphql/types/package_type_enum.rb b/app/graphql/types/package_type_enum.rb
index bc03b8f5f8b..fe6e4d80a1a 100644
--- a/app/graphql/types/package_type_enum.rb
+++ b/app/graphql/types/package_type_enum.rb
@@ -3,7 +3,7 @@
module Types
class PackageTypeEnum < BaseEnum
::Packages::Package.package_types.keys.each do |package_type|
- value package_type.to_s.upcase, "Packages from the #{package_type} package manager", value: package_type.to_s
+ value package_type.to_s.upcase, "Packages from the #{package_type.capitalize} package manager", value: package_type.to_s
end
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 6b4a71d4e28..21c3ab3eebe 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -782,6 +782,11 @@ module Ci
end
end
+ def has_expired_locked_archive_artifacts?
+ locked_artifacts? &&
+ artifacts_expire_at.present? && artifacts_expire_at < Time.current
+ end
+
def has_expiring_archive_artifacts?
has_expiring_artifacts? && job_artifacts_archive.present?
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index f9a55fa9157..c9e05b45dbd 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -27,6 +27,13 @@ module Ci
sha_attribute :source_sha
sha_attribute :target_sha
+ # Ci::CreatePipelineService returns Ci::Pipeline so this is the only place
+ # where we can pass additional information from the service. This accessor
+ # is used for storing the processed CI YAML contents for linting purposes.
+ # There is an open issue to address this:
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/259010
+ attr_accessor :merged_yaml
+
belongs_to :project, inverse_of: :all_pipelines
belongs_to :user
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
diff --git a/app/models/packages/event.rb b/app/models/packages/event.rb
index 730ce267273..42b92f3d671 100644
--- a/app/models/packages/event.rb
+++ b/app/models/packages/event.rb
@@ -4,7 +4,7 @@ class Packages::Event < ApplicationRecord
belongs_to :package, optional: true
# FIXME: Remove debian: 9 from here when it's added to the types in package.rb model
- EVENT_SCOPES = ::Packages::Package.package_types.merge(debian: 9, container: 1000, tag: 1001).freeze
+ EVENT_SCOPES = ::Packages::Package.package_types.merge(container: 1000, tag: 1001).freeze
enum event_scope: EVENT_SCOPES
diff --git a/app/models/packages/package.rb b/app/models/packages/package.rb
index caf2522e3dd..64f79b0ad0e 100644
--- a/app/models/packages/package.rb
+++ b/app/models/packages/package.rb
@@ -47,7 +47,7 @@ class Packages::Package < ApplicationRecord
format: { with: Gitlab::Regex.generic_package_version_regex },
if: :generic?
- enum package_type: { maven: 1, npm: 2, conan: 3, nuget: 4, pypi: 5, composer: 6, generic: 7, golang: 8 }
+ enum package_type: { maven: 1, npm: 2, conan: 3, nuget: 4, pypi: 5, composer: 6, generic: 7, golang: 8, debian: 9 }
scope :with_name, ->(name) { where(name: name) }
scope :with_name_like, ->(name) { where(arel_table[:name].matches(name)) }
diff --git a/app/presenters/packages/pypi/package_presenter.rb b/app/presenters/packages/pypi/package_presenter.rb
index 4192e974645..1cb11c7be1a 100644
--- a/app/presenters/packages/pypi/package_presenter.rb
+++ b/app/presenters/packages/pypi/package_presenter.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-# Display package version data acording to PyPi
+# Display package version data acording to PyPI
# Simple API: https://warehouse.pypa.io/api-reference/legacy/#simple-project-api
module Packages
module Pypi
@@ -12,7 +12,7 @@ module Packages
@project = project
end
- # Returns the HTML body for PyPi simple API.
+ # Returns the HTML body for PyPI simple API.
# Basically a list of package download links for a specific
# package
def body
diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb
index 2b8522539b4..6da3261a4a1 100644
--- a/app/serializers/build_details_entity.rb
+++ b/app/serializers/build_details_entity.rb
@@ -35,7 +35,7 @@ class BuildDetailsEntity < JobEntity
browse_project_job_artifacts_path(project, build)
end
- expose :keep_path, if: -> (*) { (build.locked_artifacts? || build.has_expiring_archive_artifacts?) && can?(current_user, :update_build, build) } do |build|
+ expose :keep_path, if: -> (*) { (build.has_expired_locked_archive_artifacts? || build.has_expiring_archive_artifacts?) && can?(current_user, :update_build, build) } do |build|
keep_project_job_artifacts_path(project, build)
end
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index 64958b06f1d..2d278f0e30d 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -82,7 +82,9 @@ class PipelineEntity < Grape::Entity
end
expose :failed_builds, if: -> (*) { can_retry? }, using: JobEntity do |pipeline|
- pipeline.failed_builds
+ pipeline.failed_builds.each do |build|
+ build.project = pipeline.project
+ end
end
private
diff --git a/app/services/ci/build_report_result_service.rb b/app/services/ci/build_report_result_service.rb
index ca66ad8249d..3fc070f5d96 100644
--- a/app/services/ci/build_report_result_service.rb
+++ b/app/services/ci/build_report_result_service.rb
@@ -2,12 +2,20 @@
module Ci
class BuildReportResultService
+ include Gitlab::Utils::UsageData
+
+ EVENT_NAME = 'i_testing_test_case_parsed'
+
def execute(build)
return unless build.has_test_reports?
+ test_suite = generate_test_suite_report(build)
+
+ track_test_cases(build, test_suite)
+
build.report_results.create!(
project_id: build.project_id,
- data: tests_params(build)
+ data: tests_params(test_suite)
)
end
@@ -17,9 +25,7 @@ module Ci
build.collect_test_reports!(Gitlab::Ci::Reports::TestReports.new)
end
- def tests_params(build)
- test_suite = generate_test_suite_report(build)
-
+ def tests_params(test_suite)
{
tests: {
name: test_suite.name,
@@ -31,5 +37,20 @@ module Ci
}
}
end
+
+ def track_test_cases(build, test_suite)
+ return if Feature.disabled?(:track_unique_test_cases_parsed, build.project)
+
+ track_usage_event(EVENT_NAME, test_case_hashes(build, test_suite))
+ end
+
+ def test_case_hashes(build, test_suite)
+ [].tap do |hashes|
+ test_suite.each_test_case do |test_case|
+ key = "#{build.project_id}-#{test_suite.name}-#{test_case.key}"
+ hashes << Digest::SHA256.hexdigest(key)
+ end
+ end
+ end
end
end
diff --git a/app/services/system_notes/issuables_service.rb b/app/services/system_notes/issuables_service.rb
index 784bd6b9699..7a73af0a81a 100644
--- a/app/services/system_notes/issuables_service.rb
+++ b/app/services/system_notes/issuables_service.rb
@@ -13,6 +13,8 @@ module SystemNotes
def relate_issue(noteable_ref)
body = "marked this issue as related to #{noteable_ref.to_reference(noteable.project)}"
+ issue_activity_counter.track_issue_related_action(author: author) if noteable.is_a?(Issue)
+
create_note(NoteSummary.new(noteable, project, author, body, action: 'relate'))
end
@@ -27,6 +29,8 @@ module SystemNotes
def unrelate_issue(noteable_ref)
body = "removed the relation with #{noteable_ref.to_reference(noteable.project)}"
+ issue_activity_counter.track_issue_unrelated_action(author: author) if noteable.is_a?(Issue)
+
create_note(NoteSummary.new(noteable, project, author, body, action: 'unrelate'))
end
@@ -174,6 +178,8 @@ module SystemNotes
if noteable.is_a?(ExternalIssue)
noteable.project.external_issue_tracker.create_cross_reference_note(noteable, mentioner, author)
else
+ issue_activity_counter.track_issue_cross_referenced_action(author: author) if noteable.is_a?(Issue)
+
create_note(NoteSummary.new(noteable, noteable.project, author, body, action: 'cross_reference'))
end
end
@@ -208,6 +214,8 @@ module SystemNotes
status_label = new_task.complete? ? Taskable::COMPLETED : Taskable::INCOMPLETE
body = "marked the task **#{new_task.source}** as #{status_label}"
+ issue_activity_counter.track_issue_description_changed_action(author: author) if noteable.is_a?(Issue)
+
create_note(NoteSummary.new(noteable, project, author, body, action: 'task'))
end
@@ -229,6 +237,8 @@ module SystemNotes
cross_reference = noteable_ref.to_reference(project)
body = "moved #{direction} #{cross_reference}"
+ issue_activity_counter.track_issue_moved_action(author: author) if noteable.is_a?(Issue)
+
create_note(NoteSummary.new(noteable, project, author, body, action: 'moved'))
end
@@ -299,6 +309,9 @@ module SystemNotes
# Returns the created Note object
def mark_duplicate_issue(canonical_issue)
body = "marked this issue as a duplicate of #{canonical_issue.to_reference(project)}"
+
+ issue_activity_counter.track_issue_marked_as_duplicate_action(author: author) if noteable.is_a?(Issue)
+
create_note(NoteSummary.new(noteable, project, author, body, action: 'duplicate'))
end
@@ -322,6 +335,14 @@ module SystemNotes
action = noteable.discussion_locked? ? 'locked' : 'unlocked'
body = "#{action} this #{noteable.class.to_s.titleize.downcase}"
+ if noteable.is_a?(Issue)
+ if action == 'locked'
+ issue_activity_counter.track_issue_locked_action(author: author)
+ else
+ issue_activity_counter.track_issue_unlocked_action(author: author)
+ end
+ end
+
create_note(NoteSummary.new(noteable, project, author, body, action: action))
end
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 1eb3a14525f..86474ea699e 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -122,7 +122,7 @@
= f.submit s_("Profiles|Update profile settings"), class: 'btn btn-success'
= link_to _("Cancel"), user_path(current_user), class: 'btn btn-cancel'
-.modal.modal-profile-crop
+.modal.modal-profile-crop{ data: { cropper_css_path: ActionController::Base.helpers.stylesheet_path('lazy_bundles/cropper.css') } }
.modal-dialog
.modal-content
.modal-header
diff --git a/changelogs/unreleased/219951-improve-styling-of-design-pins.yml b/changelogs/unreleased/219951-improve-styling-of-design-pins.yml
new file mode 100644
index 00000000000..e81e2e7409b
--- /dev/null
+++ b/changelogs/unreleased/219951-improve-styling-of-design-pins.yml
@@ -0,0 +1,5 @@
+---
+title: Update styling of design comment pins
+merge_request: 39797
+author:
+type: changed
diff --git a/changelogs/unreleased/229918-track-additional-issue-change-events.yml b/changelogs/unreleased/229918-track-additional-issue-change-events.yml
new file mode 100644
index 00000000000..0e34439be06
--- /dev/null
+++ b/changelogs/unreleased/229918-track-additional-issue-change-events.yml
@@ -0,0 +1,5 @@
+---
+title: Add more issue change events to usage ping
+merge_request: 43828
+author:
+type: other
diff --git a/changelogs/unreleased/231467-reduce-cached-query-jobscontroller-show.yml b/changelogs/unreleased/231467-reduce-cached-query-jobscontroller-show.yml
new file mode 100644
index 00000000000..8a6e84f3a0b
--- /dev/null
+++ b/changelogs/unreleased/231467-reduce-cached-query-jobscontroller-show.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce cached SQL for JobsController#show
+merge_request: 43559
+author:
+type: performance
diff --git a/changelogs/unreleased/242285-approve-graphql-ce.yml b/changelogs/unreleased/242285-approve-graphql-ce.yml
new file mode 100644
index 00000000000..12415795839
--- /dev/null
+++ b/changelogs/unreleased/242285-approve-graphql-ce.yml
@@ -0,0 +1,5 @@
+---
+title: Allow get approvals on merge request by GraphQL in CE
+author: Pavel Kuznetsov
+merge_request: 43325
+type: add
diff --git a/changelogs/unreleased/251113-create_framework_model.yml b/changelogs/unreleased/251113-create_framework_model.yml
new file mode 100644
index 00000000000..7db07459e9e
--- /dev/null
+++ b/changelogs/unreleased/251113-create_framework_model.yml
@@ -0,0 +1,5 @@
+---
+title: Create ComplianceManagement::Framework Model
+merge_request: 43301
+author:
+type: changed
diff --git a/changelogs/unreleased/251935-add-default_branch_name-column-to-namespace_settings-table.yml b/changelogs/unreleased/251935-add-default_branch_name-column-to-namespace_settings-table.yml
new file mode 100644
index 00000000000..6a6139c3c45
--- /dev/null
+++ b/changelogs/unreleased/251935-add-default_branch_name-column-to-namespace_settings-table.yml
@@ -0,0 +1,5 @@
+---
+title: Add :default_branch_name column to namespace_settings
+merge_request: 42778
+author:
+type: added
diff --git a/changelogs/unreleased/eb-unit-tests-parsed-usage-ping.yml b/changelogs/unreleased/eb-unit-tests-parsed-usage-ping.yml
new file mode 100644
index 00000000000..0ac94f0cafb
--- /dev/null
+++ b/changelogs/unreleased/eb-unit-tests-parsed-usage-ping.yml
@@ -0,0 +1,5 @@
+---
+title: Track unique number of test cases parsed
+merge_request: 41918
+author:
+type: added
diff --git a/changelogs/unreleased/fix-spelling-of-pypi.yml b/changelogs/unreleased/fix-spelling-of-pypi.yml
new file mode 100644
index 00000000000..3101367cbc6
--- /dev/null
+++ b/changelogs/unreleased/fix-spelling-of-pypi.yml
@@ -0,0 +1,5 @@
+---
+title: Fix spelling of PyPI
+merge_request: 44058
+author: Peter Bittner (@bittner)
+type: other
diff --git a/changelogs/unreleased/mc-feature-fix-keep-path-artifact-locking.yml b/changelogs/unreleased/mc-feature-fix-keep-path-artifact-locking.yml
new file mode 100644
index 00000000000..bdd19afb0b7
--- /dev/null
+++ b/changelogs/unreleased/mc-feature-fix-keep-path-artifact-locking.yml
@@ -0,0 +1,5 @@
+---
+title: Show keep path for expired locked artifacts.
+merge_request: 43866
+author:
+type: changed
diff --git a/changelogs/unreleased/mc-feature-project-ci-lint-api.yml b/changelogs/unreleased/mc-feature-project-ci-lint-api.yml
new file mode 100644
index 00000000000..db24843e65f
--- /dev/null
+++ b/changelogs/unreleased/mc-feature-project-ci-lint-api.yml
@@ -0,0 +1,5 @@
+---
+title: Add project scoped CI lint API endpoint.
+merge_request: 42998
+author:
+type: added
diff --git a/changelogs/unreleased/tz-lazy-load-cropper.yml b/changelogs/unreleased/tz-lazy-load-cropper.yml
new file mode 100644
index 00000000000..41f9e01940a
--- /dev/null
+++ b/changelogs/unreleased/tz-lazy-load-cropper.yml
@@ -0,0 +1,5 @@
+---
+title: Loads cropper css only when needed
+merge_request: 44137
+author:
+type: performance
diff --git a/config/application.rb b/config/application.rb
index f5d7b2600ba..1d9dfd256de 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -194,6 +194,7 @@ module Gitlab
config.assets.precompile << "page_bundles/milestone.css"
config.assets.precompile << "page_bundles/todos.css"
config.assets.precompile << "page_bundles/xterm.css"
+ config.assets.precompile << "lazy_bundles/cropper.css"
config.assets.precompile << "performance_bar.css"
config.assets.precompile << "lib/ace.js"
config.assets.precompile << "disable_animations.css"
diff --git a/config/feature_flags/development/track_unique_test_cases_parsed.yml b/config/feature_flags/development/track_unique_test_cases_parsed.yml
new file mode 100644
index 00000000000..98ae38e1cb0
--- /dev/null
+++ b/config/feature_flags/development/track_unique_test_cases_parsed.yml
@@ -0,0 +1,7 @@
+---
+name: track_unique_test_cases_parsed
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41918
+rollout_issue_url:
+group: group::testing
+type: development
+default_enabled: false
diff --git a/config/feature_flags/development/usage_data_i_testing_test_case_parsed.yml b/config/feature_flags/development/usage_data_i_testing_test_case_parsed.yml
new file mode 100644
index 00000000000..095010da56b
--- /dev/null
+++ b/config/feature_flags/development/usage_data_i_testing_test_case_parsed.yml
@@ -0,0 +1,7 @@
+---
+name: usage_data_i_testing_test_case_parsed
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41918
+rollout_issue_url:
+group: group::testing
+type: development
+default_enabled: true
diff --git a/db/migrate/20200919200318_add_default_branch_name_to_namespace_settings.rb b/db/migrate/20200919200318_add_default_branch_name_to_namespace_settings.rb
new file mode 100644
index 00000000000..c8c856c7533
--- /dev/null
+++ b/db/migrate/20200919200318_add_default_branch_name_to_namespace_settings.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class AddDefaultBranchNameToNamespaceSettings < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ # rubocop:disable Migration/AddLimitToTextColumns
+
+ # limit is added in 20200919204155_add_text_limit_to_namespace_settings_default_branch_name
+ #
+ def change
+ add_column :namespace_settings, :default_branch_name, :text
+ end
+
+ # rubocop:enable Migration/AddLimitToTextColumns
+end
diff --git a/db/migrate/20200919204155_add_text_limit_to_namespace_settings_default_branch_name.rb b/db/migrate/20200919204155_add_text_limit_to_namespace_settings_default_branch_name.rb
new file mode 100644
index 00000000000..174a1a9c556
--- /dev/null
+++ b/db/migrate/20200919204155_add_text_limit_to_namespace_settings_default_branch_name.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddTextLimitToNamespaceSettingsDefaultBranchName < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_text_limit :namespace_settings, :default_branch_name, 255
+ end
+
+ def down
+ # Down is required as `add_text_limit` is not reversible
+ #
+ remove_text_limit :namespace_settings, :default_branch_name
+ end
+end
diff --git a/db/migrate/20200922075244_add_compliance_framework_model.rb b/db/migrate/20200922075244_add_compliance_framework_model.rb
new file mode 100644
index 00000000000..376482d9005
--- /dev/null
+++ b/db/migrate/20200922075244_add_compliance_framework_model.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+class AddComplianceFrameworkModel < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ unless table_exists?(:compliance_management_frameworks)
+ with_lock_retries do
+ create_table :compliance_management_frameworks do |t|
+ t.references :group, foreign_key: { to_table: :namespaces, on_delete: :cascade }, null: false, index: false
+ t.text :name, null: false
+ t.text :description, null: false
+ t.text :color, null: false
+ t.index [:group_id, :name], unique: true
+ end
+ end
+ end
+
+ add_text_limit :compliance_management_frameworks, :name, 255
+ add_text_limit :compliance_management_frameworks, :description, 255
+ add_text_limit :compliance_management_frameworks, :color, 10
+ end
+
+ def down
+ with_lock_retries do
+ drop_table :compliance_management_frameworks
+ end
+ end
+end
diff --git a/db/schema_migrations/20200919200318 b/db/schema_migrations/20200919200318
new file mode 100644
index 00000000000..29721a3ef2b
--- /dev/null
+++ b/db/schema_migrations/20200919200318
@@ -0,0 +1 @@
+f33c66297ca7848c576778dc275e42801f5f9d7cdcf8c4d2fb205d4eb9770937 \ No newline at end of file
diff --git a/db/schema_migrations/20200919204155 b/db/schema_migrations/20200919204155
new file mode 100644
index 00000000000..39b608deed4
--- /dev/null
+++ b/db/schema_migrations/20200919204155
@@ -0,0 +1 @@
+097cb7a36fdc831045f3a33a047f8bda6474b8165ef5fc95dbdeea45d0ac04a3 \ No newline at end of file
diff --git a/db/schema_migrations/20200922075244 b/db/schema_migrations/20200922075244
new file mode 100644
index 00000000000..82a2fc5f304
--- /dev/null
+++ b/db/schema_migrations/20200922075244
@@ -0,0 +1 @@
+a8450c6c21b1182afd06c88af18c0d9be92b0e7fdc73505c07d4ab3fddd39abf \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index bde64076670..cc330feeab1 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -11087,6 +11087,26 @@ CREATE SEQUENCE commit_user_mentions_id_seq
ALTER SEQUENCE commit_user_mentions_id_seq OWNED BY commit_user_mentions.id;
+CREATE TABLE compliance_management_frameworks (
+ id bigint NOT NULL,
+ group_id bigint NOT NULL,
+ name text NOT NULL,
+ description text NOT NULL,
+ color text NOT NULL,
+ CONSTRAINT check_08cd34b2c2 CHECK ((char_length(color) <= 10)),
+ CONSTRAINT check_1617e0b87e CHECK ((char_length(description) <= 255)),
+ CONSTRAINT check_ab00bc2193 CHECK ((char_length(name) <= 255))
+);
+
+CREATE SEQUENCE compliance_management_frameworks_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE compliance_management_frameworks_id_seq OWNED BY compliance_management_frameworks.id;
+
CREATE TABLE container_expiration_policies (
project_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
@@ -13609,7 +13629,9 @@ CREATE TABLE namespace_settings (
updated_at timestamp with time zone NOT NULL,
namespace_id integer NOT NULL,
prevent_forking_outside_group boolean DEFAULT false NOT NULL,
- allow_mfa_for_subgroups boolean DEFAULT true NOT NULL
+ allow_mfa_for_subgroups boolean DEFAULT true NOT NULL,
+ default_branch_name text,
+ CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255))
);
CREATE TABLE namespace_statistics (
@@ -17344,6 +17366,8 @@ ALTER TABLE ONLY clusters_kubernetes_namespaces ALTER COLUMN id SET DEFAULT next
ALTER TABLE ONLY commit_user_mentions ALTER COLUMN id SET DEFAULT nextval('commit_user_mentions_id_seq'::regclass);
+ALTER TABLE ONLY compliance_management_frameworks ALTER COLUMN id SET DEFAULT nextval('compliance_management_frameworks_id_seq'::regclass);
+
ALTER TABLE ONLY container_repositories ALTER COLUMN id SET DEFAULT nextval('container_repositories_id_seq'::regclass);
ALTER TABLE ONLY conversational_development_index_metrics ALTER COLUMN id SET DEFAULT nextval('conversational_development_index_metrics_id_seq'::regclass);
@@ -18377,6 +18401,9 @@ ALTER TABLE ONLY clusters
ALTER TABLE ONLY commit_user_mentions
ADD CONSTRAINT commit_user_mentions_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY compliance_management_frameworks
+ ADD CONSTRAINT compliance_management_frameworks_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY container_expiration_policies
ADD CONSTRAINT container_expiration_policies_pkey PRIMARY KEY (project_id);
@@ -19445,6 +19472,8 @@ CREATE UNIQUE INDEX idx_jira_connect_subscriptions_on_installation_id_namespace_
CREATE INDEX idx_members_created_at_user_id_invite_token ON members USING btree (created_at) WHERE ((invite_token IS NOT NULL) AND (user_id IS NULL));
+CREATE INDEX idx_merge_requests_on_id_and_merge_jid ON merge_requests USING btree (id, merge_jid) WHERE ((merge_jid IS NOT NULL) AND (state_id = 4));
+
CREATE INDEX idx_merge_requests_on_source_project_and_branch_state_opened ON merge_requests USING btree (source_project_id, source_branch) WHERE (state_id = 1);
CREATE INDEX idx_merge_requests_on_state_id_and_merge_status ON merge_requests USING btree (state_id, merge_status) WHERE ((state_id = 1) AND ((merge_status)::text = 'can_be_merged'::text));
@@ -19987,6 +20016,8 @@ CREATE INDEX index_clusters_on_user_id ON clusters USING btree (user_id);
CREATE UNIQUE INDEX index_commit_user_mentions_on_note_id ON commit_user_mentions USING btree (note_id);
+CREATE UNIQUE INDEX index_compliance_management_frameworks_on_group_id_and_name ON compliance_management_frameworks USING btree (group_id, name);
+
CREATE INDEX index_container_expiration_policies_on_next_run_at_and_enabled ON container_expiration_policies USING btree (next_run_at, enabled);
CREATE INDEX index_container_repositories_on_project_id ON container_repositories USING btree (project_id);
@@ -23668,6 +23699,9 @@ ALTER TABLE ONLY requirements_management_test_reports
ALTER TABLE ONLY pool_repositories
ADD CONSTRAINT fk_rails_d2711daad4 FOREIGN KEY (source_project_id) REFERENCES projects(id) ON DELETE SET NULL;
+ALTER TABLE ONLY compliance_management_frameworks
+ ADD CONSTRAINT fk_rails_d3240d6339 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY group_group_links
ADD CONSTRAINT fk_rails_d3a0488427 FOREIGN KEY (shared_group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
diff --git a/doc/administration/geo/replication/datatypes.md b/doc/administration/geo/replication/datatypes.md
index 52a2b1521a9..18c788d63bc 100644
--- a/doc/administration/geo/replication/datatypes.md
+++ b/doc/administration/geo/replication/datatypes.md
@@ -181,7 +181,7 @@ successfully, you must replicate their data using some other means.
| [Maven Repository](../../../user/packages/maven_repository/index.md) | **Yes** (13.2) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default |
| [Conan Repository](../../../user/packages/conan_repository/index.md) | **Yes** (13.2) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default |
| [NuGet Repository](../../../user/packages/nuget_repository/index.md) | **Yes** (13.2) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default |
-| [PyPi Repository](../../../user/packages/pypi_repository/index.md) | **Yes** (13.2) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default |
+| [PyPI Repository](../../../user/packages/pypi_repository/index.md) | **Yes** (13.2) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default |
| [Composer Repository](../../../user/packages/composer_repository/index.md) | **Yes** (13.2) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default |
| [Versioned Terraform State](../../terraform_state.md) | **Yes** (13.5) | No | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_terraform_state_version_replication`, enabled by default |
| [External merge request diffs](../../merge_request_diffs.md) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/33817) | No | Via Object Storage provider if supported. Native Geo support (Beta). | |
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index 750e6aab687..7178db3be6d 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -317,7 +317,7 @@ disable enforcement. For more information, see the documentation on configuring
```
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
-1. Run `sudo /opt/gitlab/embedded/service/gitlab-shell/bin/check -config /opt/gitlab/embedded/service/gitlab-shell/config.yml`
+1. Run `sudo /opt/gitlab/embedded/bin/gitaly-hooks check /var/opt/gitlab/gitaly/config.toml`
to confirm that Gitaly can perform callbacks to the GitLab internal API.
**For installations from source**
@@ -364,7 +364,7 @@ disable enforcement. For more information, see the documentation on configuring
```
1. Save the files and [restart GitLab](../restart_gitlab.md#installations-from-source).
-1. Run `sudo -u git /home/git/gitlab-shell/bin/check -config /home/git/gitlab-shell/config.yml`
+1. Run `sudo -u git /home/git/gitaly/gitaly-hooks check /home/git/gitaly/config.toml`
to confirm that Gitaly can perform callbacks to the GitLab internal API.
### Configure Gitaly clients
@@ -711,6 +711,15 @@ Gitaly Go process. Some examples of things that are implemented in `gitaly-ruby`
- RPCs that deal with wikis.
- RPCs that create commits on behalf of a user, such as merge commits.
+We recommend:
+
+- At least 300MB memory per worker.
+- No more than one worker per core.
+
+NOTE: **Note:**
+`gitaly-ruby` is planned to be eventually removed. To track progress, see the
+[Remove the Gitaly-Ruby sidecar](https://gitlab.com/groups/gitlab-org/-/epics/2862) epic.
+
### Configure number of `gitaly-ruby` workers
`gitaly-ruby` has much less capacity than Gitaly implemented in Go. If your Gitaly server has to handle lots of
diff --git a/doc/administration/gitaly/praefect.md b/doc/administration/gitaly/praefect.md
index 45c077cded1..5a96c489083 100644
--- a/doc/administration/gitaly/praefect.md
+++ b/doc/administration/gitaly/praefect.md
@@ -873,10 +873,10 @@ Particular attention should be shown to:
gitlab-ctl reconfigure
```
-1. Verify each `gitlab-shell` on each Gitaly node can reach GitLab. On each Gitaly node run:
+1. Verify on each Gitaly node the Git Hooks can reach GitLab. On each Gitaly node run:
```shell
- /opt/gitlab/embedded/service/gitlab-shell/bin/check -config /opt/gitlab/embedded/service/gitlab-shell/config.yml
+ /opt/gitlab/embedded/bin/gitaly-hooks check /var/opt/gitlab/gitaly/config.toml
```
1. Verify that GitLab can reach Praefect:
diff --git a/doc/administration/packages/index.md b/doc/administration/packages/index.md
index 6b9e9ae3d5f..6d841d47caf 100644
--- a/doc/administration/packages/index.md
+++ b/doc/administration/packages/index.md
@@ -14,7 +14,7 @@ The Packages feature allows GitLab to act as a repository for the following:
| Software repository | Description | Available in GitLab version |
| ------------------- | ----------- | --------------------------- |
-| [PyPi Repository](../../user/packages/pypi_repository/index.md) | The GitLab PyPi Repository enables every project in GitLab to have its own space to store [PyPi](https://pypi.org/) packages. | 12.10+ |
+| [PyPI Repository](../../user/packages/pypi_repository/index.md) | The GitLab PyPI Repository enables every project in GitLab to have its own space to store [PyPI](https://pypi.org/) packages. | 12.10+ |
| [Composer Repository](../../user/packages/composer_repository/index.md) | The GitLab Composer Repository enables every project in GitLab to have its own space to store [Composer](https://getcomposer.org/) packages. | 13.1+ |
| [NuGet Repository](../../user/packages/nuget_repository/index.md) | The GitLab NuGet Repository enables every project in GitLab to have its own space to store [NuGet](https://www.nuget.org/) packages. | 12.8+ |
| [Conan Repository](../../user/packages/conan_repository/index.md) | The GitLab Conan Repository enables every project in GitLab to have its own space to store [Conan](https://conan.io/) packages. | 12.4+ |
diff --git a/doc/administration/reference_architectures/10k_users.md b/doc/administration/reference_architectures/10k_users.md
index 2f32cf9fb04..e4ddca4e5f9 100644
--- a/doc/administration/reference_architectures/10k_users.md
+++ b/doc/administration/reference_architectures/10k_users.md
@@ -1879,7 +1879,7 @@ On each node perform the following:
1. Optionally, from the Gitaly servers, confirm that Gitaly can perform callbacks to the internal API:
```shell
- sudo /opt/gitlab/embedded/service/gitlab-shell/bin/check -config /opt/gitlab/embedded/service/gitlab-shell/config.yml
+ sudo /opt/gitlab/embedded/bin/gitaly-hooks check /var/opt/gitlab/gitaly/config.toml
```
NOTE: **Note:**
diff --git a/doc/administration/reference_architectures/25k_users.md b/doc/administration/reference_architectures/25k_users.md
index 55de38645d7..cdec6c4ee9e 100644
--- a/doc/administration/reference_architectures/25k_users.md
+++ b/doc/administration/reference_architectures/25k_users.md
@@ -1879,7 +1879,7 @@ On each node perform the following:
1. Optionally, from the Gitaly servers, confirm that Gitaly can perform callbacks to the internal API:
```shell
- sudo /opt/gitlab/embedded/service/gitlab-shell/bin/check -config /opt/gitlab/embedded/service/gitlab-shell/config.yml
+ sudo /opt/gitlab/embedded/bin/gitaly-hooks check /var/opt/gitlab/gitaly/config.toml
```
NOTE: **Note:**
diff --git a/doc/administration/reference_architectures/2k_users.md b/doc/administration/reference_architectures/2k_users.md
index 1c174fc6e03..427815519ab 100644
--- a/doc/administration/reference_architectures/2k_users.md
+++ b/doc/administration/reference_architectures/2k_users.md
@@ -466,7 +466,7 @@ To configure the Gitaly server:
1. Confirm that Gitaly can perform callbacks to the internal API:
```shell
- sudo /opt/gitlab/embedded/service/gitlab-shell/bin/check -config /opt/gitlab/embedded/service/gitlab-shell/config.yml
+ sudo /opt/gitlab/embedded/bin/gitaly-hooks check /var/opt/gitlab/gitaly/config.toml
```
### Gitaly TLS support
diff --git a/doc/administration/reference_architectures/3k_users.md b/doc/administration/reference_architectures/3k_users.md
index f69e19ffef8..2a4156fee6b 100644
--- a/doc/administration/reference_architectures/3k_users.md
+++ b/doc/administration/reference_architectures/3k_users.md
@@ -1223,7 +1223,7 @@ On each node:
1. Confirm that Gitaly can perform callbacks to the internal API:
```shell
- sudo /opt/gitlab/embedded/service/gitlab-shell/bin/check -config /opt/gitlab/embedded/service/gitlab-shell/config.yml
+ sudo /opt/gitlab/embedded/bin/gitaly-hooks check /var/opt/gitlab/gitaly/config.toml
```
1. Verify the GitLab services are running:
diff --git a/doc/administration/reference_architectures/50k_users.md b/doc/administration/reference_architectures/50k_users.md
index 7ea571108fb..126725421d4 100644
--- a/doc/administration/reference_architectures/50k_users.md
+++ b/doc/administration/reference_architectures/50k_users.md
@@ -1879,7 +1879,7 @@ On each node perform the following:
1. Optionally, from the Gitaly servers, confirm that Gitaly can perform callbacks to the internal API:
```shell
- sudo /opt/gitlab/embedded/service/gitlab-shell/bin/check -config /opt/gitlab/embedded/service/gitlab-shell/config.yml
+ sudo /opt/gitlab/embedded/bin/gitaly-hooks check /var/opt/gitlab/gitaly/config.toml
```
NOTE: **Note:**
diff --git a/doc/administration/reference_architectures/5k_users.md b/doc/administration/reference_architectures/5k_users.md
index b32560d7055..960ad2b7368 100644
--- a/doc/administration/reference_architectures/5k_users.md
+++ b/doc/administration/reference_architectures/5k_users.md
@@ -1222,7 +1222,7 @@ On each node:
1. Confirm that Gitaly can perform callbacks to the internal API:
```shell
- sudo /opt/gitlab/embedded/service/gitlab-shell/bin/check -config /opt/gitlab/embedded/service/gitlab-shell/config.yml
+ sudo /opt/gitlab/embedded/bin/gitaly-hooks check /var/opt/gitlab/gitaly/config.toml
```
1. Verify the GitLab services are running:
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 84ac8d30660..1dc91105add 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -11619,6 +11619,7 @@ type Mutation {
toggleAwardEmoji(input: ToggleAwardEmojiInput!): ToggleAwardEmojiPayload @deprecated(reason: "Use awardEmojiToggle. Deprecated in 13.2")
updateAlertStatus(input: UpdateAlertStatusInput!): UpdateAlertStatusPayload
updateBoard(input: UpdateBoardInput!): UpdateBoardPayload
+ updateBoardEpicUserPreferences(input: UpdateBoardEpicUserPreferencesInput!): UpdateBoardEpicUserPreferencesPayload
updateBoardList(input: UpdateBoardListInput!): UpdateBoardListPayload
updateContainerExpirationPolicy(input: UpdateContainerExpirationPolicyInput!): UpdateContainerExpirationPolicyPayload
updateEpic(input: UpdateEpicInput!): UpdateEpicPayload
@@ -12257,42 +12258,47 @@ type PackageFileRegistryEdge {
enum PackageTypeEnum {
"""
- Packages from the composer package manager
+ Packages from the Composer package manager
"""
COMPOSER
"""
- Packages from the conan package manager
+ Packages from the Conan package manager
"""
CONAN
"""
- Packages from the generic package manager
+ Packages from the Debian package manager
+ """
+ DEBIAN
+
+ """
+ Packages from the Generic package manager
"""
GENERIC
"""
- Packages from the golang package manager
+ Packages from the Golang package manager
"""
GOLANG
"""
- Packages from the maven package manager
+ Packages from the Maven package manager
"""
MAVEN
"""
- Packages from the npm package manager
+ Packages from the Npm package manager
"""
NPM
"""
- Packages from the nuget package manager
+ Packages from the Nuget package manager
"""
NUGET
"""
- Packages from the pypi package manager
+ Packages from the Pypi package manager
"""
PYPI
}
@@ -18471,6 +18477,51 @@ type UpdateAlertStatusPayload {
}
"""
+Autogenerated input type of UpdateBoardEpicUserPreferences
+"""
+input UpdateBoardEpicUserPreferencesInput {
+ """
+ The board global ID
+ """
+ boardId: BoardID!
+
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ Whether the epic should be collapsed in the board
+ """
+ collapsed: Boolean!
+
+ """
+ ID of an epic to set preferences for
+ """
+ epicId: EpicID!
+}
+
+"""
+Autogenerated return type of UpdateBoardEpicUserPreferences
+"""
+type UpdateBoardEpicUserPreferencesPayload {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ User preferences for the epic in the board after mutation
+ """
+ epicUserPreferences: BoardEpicUserPreferences
+
+ """
+ Errors encountered during execution of the mutation.
+ """
+ errors: [String!]!
+}
+
+"""
Autogenerated input type of UpdateBoard
"""
input UpdateBoardInput {
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 15aa205bed3..f245fe4f04b 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -34110,6 +34110,33 @@
"deprecationReason": null
},
{
+ "name": "updateBoardEpicUserPreferences",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "UpdateBoardEpicUserPreferencesInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "UpdateBoardEpicUserPreferencesPayload",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "updateBoardList",
"description": null,
"args": [
@@ -36318,49 +36345,55 @@
"enumValues": [
{
"name": "MAVEN",
- "description": "Packages from the maven package manager",
+ "description": "Packages from the Maven package manager",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "NPM",
- "description": "Packages from the npm package manager",
+ "description": "Packages from the Npm package manager",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "CONAN",
- "description": "Packages from the conan package manager",
+ "description": "Packages from the Conan package manager",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "NUGET",
- "description": "Packages from the nuget package manager",
+ "description": "Packages from the Nuget package manager",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "PYPI",
- "description": "Packages from the pypi package manager",
+ "description": "Packages from the Pypi package manager",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "COMPOSER",
- "description": "Packages from the composer package manager",
+ "description": "Packages from the Composer package manager",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "GENERIC",
- "description": "Packages from the generic package manager",
+ "description": "Packages from the Generic package manager",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "GOLANG",
- "description": "Packages from the golang package manager",
+ "description": "Packages from the Golang package manager",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "DEBIAN",
+ "description": "Packages from the Debian package manager",
"isDeprecated": false,
"deprecationReason": null
}
@@ -54021,6 +54054,136 @@
},
{
"kind": "INPUT_OBJECT",
+ "name": "UpdateBoardEpicUserPreferencesInput",
+ "description": "Autogenerated input type of UpdateBoardEpicUserPreferences",
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "boardId",
+ "description": "The board global ID",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "BoardID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "epicId",
+ "description": "ID of an epic to set preferences for",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "EpicID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "collapsed",
+ "description": "Whether the epic should be collapsed in the board",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "UpdateBoardEpicUserPreferencesPayload",
+ "description": "Autogenerated return type of UpdateBoardEpicUserPreferences",
+ "fields": [
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "epicUserPreferences",
+ "description": "User preferences for the epic in the board after mutation",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "BoardEpicUserPreferences",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "errors",
+ "description": "Errors encountered during execution of the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
"name": "UpdateBoardInput",
"description": "Autogenerated input type of UpdateBoard",
"fields": null,
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 4a3675df15d..350a1b6c0f3 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -2623,6 +2623,16 @@ Autogenerated return type of UpdateAlertStatus.
| `issue` | Issue | The issue created after mutation |
| `todo` | Todo | The todo after mutation |
+### UpdateBoardEpicUserPreferencesPayload
+
+Autogenerated return type of UpdateBoardEpicUserPreferences.
+
+| Field | Type | Description |
+| ----- | ---- | ----------- |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `epicUserPreferences` | BoardEpicUserPreferences | User preferences for the epic in the board after mutation |
+| `errors` | String! => Array | Errors encountered during execution of the mutation. |
+
### UpdateBoardListPayload
Autogenerated return type of UpdateBoardList.
@@ -3416,14 +3426,15 @@ Values for sorting projects.
| Value | Description |
| ----- | ----------- |
-| `COMPOSER` | Packages from the composer package manager |
-| `CONAN` | Packages from the conan package manager |
-| `GENERIC` | Packages from the generic package manager |
-| `GOLANG` | Packages from the golang package manager |
-| `MAVEN` | Packages from the maven package manager |
-| `NPM` | Packages from the npm package manager |
-| `NUGET` | Packages from the nuget package manager |
-| `PYPI` | Packages from the pypi package manager |
+| `COMPOSER` | Packages from the Composer package manager |
+| `CONAN` | Packages from the Conan package manager |
+| `DEBIAN` | Packages from the Debian package manager |
+| `GENERIC` | Packages from the Generic package manager |
+| `GOLANG` | Packages from the Golang package manager |
+| `MAVEN` | Packages from the Maven package manager |
+| `NPM` | Packages from the Npm package manager |
+| `NUGET` | Packages from the Nuget package manager |
+| `PYPI` | Packages from the Pypi package manager |
### PipelineConfigSourceEnum
diff --git a/doc/api/lint.md b/doc/api/lint.md
index df7198ac5c6..652a5289f13 100644
--- a/doc/api/lint.md
+++ b/doc/api/lint.md
@@ -94,3 +94,51 @@ Example response:
"merged_config": "---\n:another_test:\n :stage: test\n :script: echo 2\n:test:\n :stage: test\n :script: echo 1\n"
}
```
+
+## Validate a project's CI configuration
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/231352) in GitLab 13.5.
+
+Checks if a project's latest (`HEAD` of the project's default branch)
+`.gitlab-ci.yml` configuration is valid. This endpoint uses all namespace
+specific data available, including variables, local includes, and so on.
+
+```plaintext
+GET /projects/:id/ci/lint
+```
+
+| Attribute | Type | Required | Description |
+| ---------- | ------- | -------- | -------- |
+| `dry_run` | boolean | no | Run pipeline creation simulation, or only do static check. |
+
+Example request:
+
+```shell
+curl "https://gitlab.example.com/api/v4/projects/:id/ci/lint"
+```
+
+Example responses:
+
+- Valid config:
+
+```json
+{
+ "valid": true,
+ "merged_yaml": "---\n:test_job:\n :script: echo 1\n",
+ "errors": [],
+ "warnings": []
+}
+```
+
+- Invalid config:
+
+```json
+{
+ "valid": false,
+ "merged_yaml": "---\n:test_job:\n :script: echo 1\n",
+ "errors": [
+ "jobs config should contain at least one visible job"
+ ],
+ "warnings": []
+}
+```
diff --git a/doc/development/internal_api.md b/doc/development/internal_api.md
index 4b46787c2c3..5e74be9e439 100644
--- a/doc/development/internal_api.md
+++ b/doc/development/internal_api.md
@@ -223,6 +223,7 @@ Example response:
- GitLab Geo
- GitLab Shell's `bin/check`
+- Gitaly
## Get new 2FA recovery codes using an SSH key
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index 54375db1831..b0be9b20c12 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -441,7 +441,7 @@ The following are some available Rake tasks:
| [`sudo gitlab-rake gitlab:elastic:index`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Enables Elasticsearch indexing and run `gitlab:elastic:create_empty_index`, `gitlab:elastic:clear_index_status`, `gitlab:elastic:index_projects`, and `gitlab:elastic:index_snippets`. |
| [`sudo gitlab-rake gitlab:elastic:index_projects`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Iterates over all projects and queues Sidekiq jobs to index them in the background. |
| [`sudo gitlab-rake gitlab:elastic:index_projects_status`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Determines the overall status of the indexing. It is done by counting the total number of indexed projects, dividing by a count of the total number of projects, then multiplying by 100. |
-| [`sudo gitlab-rake gitlab:elastic:clear_index_status`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Deletes all instances of IndexStatus for all projects. |
+| [`sudo gitlab-rake gitlab:elastic:clear_index_status`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Deletes all instances of IndexStatus for all projects. Note that this command will result in a complete wipe of the index, and it should be used with caution. |
| [`sudo gitlab-rake gitlab:elastic:create_empty_index[<TARGET_NAME>]`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Generates an empty index and assigns an alias for it on the Elasticsearch side only if it doesn't already exist. |
| [`sudo gitlab-rake gitlab:elastic:delete_index[<TARGET_NAME>]`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Removes the GitLab index and alias (if exists) on the Elasticsearch instance. |
| [`sudo gitlab-rake gitlab:elastic:recreate_index[<TARGET_NAME>]`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Wrapper task for `gitlab:elastic:delete_index[<TARGET_NAME>]` and `gitlab:elastic:create_empty_index[<TARGET_NAME>]`. |
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index c050931ce3b..efef6136485 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -914,7 +914,7 @@ restore:
sudo gitlab-backup restore BACKUP=11493107454_2018_04_25_10.6.4-ce
```
-Users of GitLab 12.1 and earlier should use the command `gitlab-rake gitlab:backup:create` instead.
+Users of GitLab 12.1 and earlier should use the command `gitlab-rake gitlab:backup:restore` instead.
CAUTION: **Warning:**
`gitlab-rake gitlab:backup:restore` doesn't set the correct file system
diff --git a/doc/user/packages/pypi_repository/index.md b/doc/user/packages/pypi_repository/index.md
index 7d79da7d79b..e6148a9f892 100644
--- a/doc/user/packages/pypi_repository/index.md
+++ b/doc/user/packages/pypi_repository/index.md
@@ -4,14 +4,14 @@ group: Package
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
-# GitLab PyPi Repository
+# GitLab PyPI Repository
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/208747) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.10.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/221259) to GitLab Core in 13.3.
-With the GitLab PyPi Repository, every project can have its own space to store PyPi packages.
+With the GitLab PyPI Repository, every project can have its own space to store PyPI packages.
-The GitLab PyPi Repository works with:
+The GitLab PyPI Repository works with:
- [pip](https://pypi.org/project/pip/)
- [twine](https://pypi.org/project/twine/)
@@ -20,13 +20,13 @@ The GitLab PyPi Repository works with:
You need a recent version of [pip](https://pypi.org/project/pip/) and [twine](https://pypi.org/project/twine/).
-## Enabling the PyPi Repository
+## Enabling the PyPI Repository
NOTE: **Note:**
This option is available only if your GitLab administrator has
[enabled support for the Package Registry](../../../administration/packages/index.md).
-After the PyPi Repository is enabled, it is available for all new projects
+After the PyPI Repository is enabled, it is available for all new projects
by default. To enable it for existing projects, or if you want to disable it:
1. Navigate to your project's **Settings > General > Visibility, project features, permissions**.
@@ -37,8 +37,8 @@ You should then be able to see the **Packages & Registries** section on the left
## Getting started
-This section covers creating a new example PyPi package to upload. This is a
-quickstart to test out the **GitLab PyPi Registry**. If you already understand how
+This section covers creating a new example PyPI package to upload. This is a
+quickstart to test out the **GitLab PyPI Registry**. If you already understand how
to build and publish your own packages, move on to the [next section](#adding-the-gitlab-pypi-repository-as-a-source).
### Create a project
@@ -152,10 +152,10 @@ And confirm your output matches the below:
mypypipackage-0.0.1-py3-none-any.whl mypypipackage-0.0.1.tar.gz
```
-Our package is now all set up and ready to be uploaded to the **GitLab PyPi
+Our package is now all set up and ready to be uploaded to the **GitLab PyPI
Package Registry**. Before we do so, we next need to set up authentication.
-## Adding the GitLab PyPi Repository as a source
+## Adding the GitLab PyPI Repository as a source
### Authenticating with a personal access token
@@ -256,7 +256,7 @@ TWINE_PASSWORD=<personal_access_token or deploy_token> TWINE_USERNAME=<username
```
If you did not follow the guide above, then you need to ensure your package
-has been properly built and you [created a PyPi package with `setuptools`](https://packaging.python.org/tutorials/packaging-projects/).
+has been properly built and you [created a PyPI package with `setuptools`](https://packaging.python.org/tutorials/packaging-projects/).
You can then upload your package using the following command:
diff --git a/lib/api/entities/ci/lint/result.rb b/lib/api/entities/ci/lint/result.rb
new file mode 100644
index 00000000000..0e4aa238ba2
--- /dev/null
+++ b/lib/api/entities/ci/lint/result.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ci
+ module Lint
+ class Result < Grape::Entity
+ expose :valid?, as: :valid
+ expose :errors
+ expose :warnings
+ expose :merged_yaml
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/lint.rb b/lib/api/lint.rb
index 51a87f9433c..3f071ef4a91 100644
--- a/lib/api/lint.rb
+++ b/lib/api/lint.rb
@@ -25,5 +25,24 @@ module API
end
end
end
+
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Validation of .gitlab-ci.yml content' do
+ detail 'This feature was introduced in GitLab 13.5.'
+ end
+ params do
+ optional :dry_run, type: Boolean, default: false, desc: 'Run pipeline creation simulation, or only do static check.'
+ end
+ get ':id/ci/lint' do
+ authorize! :download_code, user_project
+
+ content = user_project.repository.gitlab_ci_yml_for(user_project.commit.id, user_project.ci_config_path_or_default)
+ result = Gitlab::Ci::Lint
+ .new(project: user_project, current_user: current_user)
+ .validate(content, dry_run: params[:dry_run])
+
+ present result, with: Entities::Ci::Lint::Result, current_user: current_user
+ end
+ end
end
end
diff --git a/lib/gitlab/ci/lint.rb b/lib/gitlab/ci/lint.rb
index 86a9ebfa451..44f2ac23ce3 100644
--- a/lib/gitlab/ci/lint.rb
+++ b/lib/gitlab/ci/lint.rb
@@ -4,10 +4,11 @@ module Gitlab
module Ci
class Lint
class Result
- attr_reader :jobs, :errors, :warnings
+ attr_reader :jobs, :merged_yaml, :errors, :warnings
- def initialize(jobs:, errors:, warnings:)
+ def initialize(jobs:, merged_yaml:, errors:, warnings:)
@jobs = jobs
+ @merged_yaml = merged_yaml
@errors = errors
@warnings = warnings
end
@@ -39,6 +40,7 @@ module Gitlab
Result.new(
jobs: dry_run_convert_to_jobs(pipeline.stages),
+ merged_yaml: pipeline.merged_yaml,
errors: pipeline.error_messages.map(&:content),
warnings: pipeline.warning_messages(limit: ::Gitlab::Ci::Warnings::MAX_LIMIT).map(&:content)
)
@@ -54,6 +56,7 @@ module Gitlab
Result.new(
jobs: static_validation_convert_to_jobs(result),
+ merged_yaml: result.merged_yaml,
errors: result.errors,
warnings: result.warnings.take(::Gitlab::Ci::Warnings::MAX_LIMIT) # rubocop: disable CodeReuse/ActiveRecord
)
diff --git a/lib/gitlab/ci/pipeline/chain/config/process.rb b/lib/gitlab/ci/pipeline/chain/config/process.rb
index 8ccb33ffd34..c3fbd0c9e24 100644
--- a/lib/gitlab/ci/pipeline/chain/config/process.rb
+++ b/lib/gitlab/ci/pipeline/chain/config/process.rb
@@ -28,6 +28,8 @@ module Gitlab
error(result.errors.first, config_error: true)
end
+ @pipeline.merged_yaml = result.merged_yaml
+
rescue => ex
Gitlab::ErrorTracking.track_exception(ex,
project_id: project.id,
diff --git a/lib/gitlab/ci/reports/test_case.rb b/lib/gitlab/ci/reports/test_case.rb
index 15a3c862c9e..59c058eeebe 100644
--- a/lib/gitlab/ci/reports/test_case.rb
+++ b/lib/gitlab/ci/reports/test_case.rb
@@ -23,7 +23,7 @@ module Gitlab
@attachment = params.fetch(:attachment, nil)
@job = params.fetch(:job, nil)
- @key = sanitize_key_name("#{classname}_#{name}")
+ @key = hash_key("#{classname}_#{name}")
end
def has_attachment?
@@ -42,8 +42,8 @@ module Gitlab
private
- def sanitize_key_name(key)
- key.gsub(/[^0-9A-Za-z]/, '-')
+ def hash_key(key)
+ Digest::SHA256.hexdigest(key)
end
end
end
diff --git a/lib/gitlab/ci/reports/test_suite.rb b/lib/gitlab/ci/reports/test_suite.rb
index e9b78b841e4..00920dfbd54 100644
--- a/lib/gitlab/ci/reports/test_suite.rb
+++ b/lib/gitlab/ci/reports/test_suite.rb
@@ -12,18 +12,24 @@ module Gitlab
def initialize(name = nil)
@name = name
@test_cases = {}
+ @all_test_cases = []
@total_time = 0.0
- @duplicate_cases = []
end
def add_test_case(test_case)
- @duplicate_cases << test_case if existing_key?(test_case)
-
@test_cases[test_case.status] ||= {}
@test_cases[test_case.status][test_case.key] = test_case
@total_time += test_case.execution_time
end
+ def each_test_case
+ @test_cases.each do |status, test_cases|
+ test_cases.values.each do |test_case|
+ yield test_case
+ end
+ end
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def total_count
return 0 if suite_error
@@ -86,10 +92,6 @@ module Gitlab
private
- def existing_key?(test_case)
- @test_cases[test_case.status]&.key?(test_case.key)
- end
-
def sort_by_status
@test_cases = @test_cases.sort_by { |status, _| Gitlab::Ci::Reports::TestCase::STATUS_TYPES.index(status) }.to_h
end
diff --git a/lib/gitlab/redis/hll.rb b/lib/gitlab/redis/hll.rb
index 496018c88cb..010a6b59da5 100644
--- a/lib/gitlab/redis/hll.rb
+++ b/lib/gitlab/redis/hll.rb
@@ -3,6 +3,7 @@
module Gitlab
module Redis
class HLL
+ BATCH_SIZE = 300
KEY_REGEX = %r{\A(\w|-|:)*\{\w*\}(\w|-|:)*\z}.freeze
KeyFormatError = Class.new(StandardError)
@@ -29,17 +30,24 @@ module Gitlab
# 2020-216-{project_action}
# i_{analytics}_dev_ops_score-2020-32
def add(key:, value:, expiry:)
- unless KEY_REGEX.match?(key)
- raise KeyFormatError.new("Invalid key format. #{key} key should have changeable parts in curly braces. See https://docs.gitlab.com/ee/development/redis.html#multi-key-commands")
- end
+ validate_key!(key)
Gitlab::Redis::SharedState.with do |redis|
redis.multi do |multi|
- multi.pfadd(key, value)
+ Array.wrap(value).each_slice(BATCH_SIZE) { |batch| multi.pfadd(key, batch) }
+
multi.expire(key, expiry)
end
end
end
+
+ private
+
+ def validate_key!(key)
+ return if KEY_REGEX.match?(key)
+
+ raise KeyFormatError.new("Invalid key format. #{key} key should have changeable parts in curly braces. See https://docs.gitlab.com/ee/development/redis.html#multi-key-commands")
+ end
end
end
end
diff --git a/lib/gitlab/usage_data_counters/issue_activity_unique_counter.rb b/lib/gitlab/usage_data_counters/issue_activity_unique_counter.rb
index b1ce46a1ff5..c37fbc47276 100644
--- a/lib/gitlab/usage_data_counters/issue_activity_unique_counter.rb
+++ b/lib/gitlab/usage_data_counters/issue_activity_unique_counter.rb
@@ -17,6 +17,13 @@ module Gitlab
ISSUE_REOPENED = 'g_project_management_issue_reopened'
ISSUE_TITLE_CHANGED = 'g_project_management_issue_title_changed'
ISSUE_WEIGHT_CHANGED = 'g_project_management_issue_weight_changed'
+ ISSUE_CROSS_REFERENCED = 'g_project_management_issue_cross_referenced'
+ ISSUE_MOVED = 'g_project_management_issue_moved'
+ ISSUE_RELATED = 'g_project_management_issue_related'
+ ISSUE_UNRELATED = 'g_project_management_issue_unrelated'
+ ISSUE_MARKED_AS_DUPLICATE = 'g_project_management_issue_marked_as_duplicate'
+ ISSUE_LOCKED = 'g_project_management_issue_locked'
+ ISSUE_UNLOCKED = 'g_project_management_issue_unlocked'
class << self
def track_issue_created_action(author:, time: Time.zone.now)
@@ -67,6 +74,34 @@ module Gitlab
track_unique_action(ISSUE_WEIGHT_CHANGED, author, time)
end
+ def track_issue_cross_referenced_action(author:, time: Time.zone.now)
+ track_unique_action(ISSUE_CROSS_REFERENCED, author, time)
+ end
+
+ def track_issue_moved_action(author:, time: Time.zone.now)
+ track_unique_action(ISSUE_MOVED, author, time)
+ end
+
+ def track_issue_related_action(author:, time: Time.zone.now)
+ track_unique_action(ISSUE_RELATED, author, time)
+ end
+
+ def track_issue_unrelated_action(author:, time: Time.zone.now)
+ track_unique_action(ISSUE_UNRELATED, author, time)
+ end
+
+ def track_issue_marked_as_duplicate_action(author:, time: Time.zone.now)
+ track_unique_action(ISSUE_MARKED_AS_DUPLICATE, author, time)
+ end
+
+ def track_issue_locked_action(author:, time: Time.zone.now)
+ track_unique_action(ISSUE_LOCKED, author, time)
+ end
+
+ def track_issue_unlocked_action(author:, time: Time.zone.now)
+ track_unique_action(ISSUE_UNLOCKED, author, time)
+ end
+
private
def track_unique_action(action, author, time)
diff --git a/lib/gitlab/usage_data_counters/known_events.yml b/lib/gitlab/usage_data_counters/known_events.yml
index 9bbf804d87c..d91fa6ba926 100644
--- a/lib/gitlab/usage_data_counters/known_events.yml
+++ b/lib/gitlab/usage_data_counters/known_events.yml
@@ -185,6 +185,11 @@
redis_slot: incident_management
category: incident_management
aggregation: weekly
+# Testing category
+- name: i_testing_test_case_parsed
+ category: testing
+ redis_slot: testing
+ aggregation: weekly
# Project Management group
- name: g_project_management_issue_title_changed
category: issues_edit
@@ -234,3 +239,31 @@
category: issues_edit
redis_slot: project_management
aggregation: daily
+- name: g_project_management_issue_cross_referenced
+ category: issues_edit
+ redis_slot: project_management
+ aggregation: daily
+- name: g_project_management_issue_moved
+ category: issues_edit
+ redis_slot: project_management
+ aggregation: daily
+- name: g_project_management_issue_related
+ category: issues_edit
+ redis_slot: project_management
+ aggregation: daily
+- name: g_project_management_issue_unrelated
+ category: issues_edit
+ redis_slot: project_management
+ aggregation: daily
+- name: g_project_management_issue_marked_as_duplicate
+ category: issues_edit
+ redis_slot: project_management
+ aggregation: daily
+- name: g_project_management_issue_locked
+ category: issues_edit
+ redis_slot: project_management
+ aggregation: daily
+- name: g_project_management_issue_unlocked
+ category: issues_edit
+ redis_slot: project_management
+ aggregation: daily
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 8fdea2bcb15..a2e425f43a0 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -18305,7 +18305,7 @@ msgstr ""
msgid "PackageRegistry|Published to the %{project} Package Registry %{datetime}"
msgstr ""
-msgid "PackageRegistry|PyPi"
+msgid "PackageRegistry|PyPI"
msgstr ""
msgid "PackageRegistry|Recipe: %{recipe}"
@@ -18389,7 +18389,7 @@ msgstr ""
msgid "PackageType|NuGet"
msgstr ""
-msgid "PackageType|PyPi"
+msgid "PackageType|PyPI"
msgstr ""
msgid "Packages"
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index 1a1cfbbd5f6..b8cbe625e5b 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -137,7 +137,7 @@ function run_task() {
local ruby_cmd="${1}"
local task_runner_pod=$(get_pod "task-runner")
- kubectl exec -it --namespace "${namespace}" "${task_runner_pod}" -- gitlab-rails runner "${ruby_cmd}"
+ kubectl exec --namespace "${namespace}" "${task_runner_pod}" -- gitlab-rails runner "${ruby_cmd}"
}
function disable_sign_ups() {
@@ -150,7 +150,7 @@ function disable_sign_ups() {
# Create the root token
local ruby_cmd="token = User.find_by_username('root').personal_access_tokens.create(scopes: [:api], name: 'Token to disable sign-ups'); token.set_token('${REVIEW_APPS_ROOT_TOKEN}'); begin; token.save!; rescue(ActiveRecord::RecordNotUnique); end"
- run_task "${ruby_cmd}"
+ retry "run_task \"${ruby_cmd}\""
# Disable sign-ups
local signup_enabled=$(retry 'curl --silent --show-error --request PUT --header "PRIVATE-TOKEN: ${REVIEW_APPS_ROOT_TOKEN}" "${CI_ENVIRONMENT_URL}/api/v4/application/settings?signup_enabled=false" | jq ".signup_enabled"')
diff --git a/spec/controllers/every_controller_spec.rb b/spec/controllers/every_controller_spec.rb
index d333c98ccf7..7a7b17ae1f0 100644
--- a/spec/controllers/every_controller_spec.rb
+++ b/spec/controllers/every_controller_spec.rb
@@ -24,19 +24,19 @@ RSpec.describe "Every controller" do
let_it_be(:routes_without_category) do
controller_actions.map do |controller, action|
next if controller.feature_category_for_action(action)
- next unless controller.to_s.start_with?('B', 'C', 'D', 'E', 'F', 'Projects::MergeRequestsController')
+
+ next unless controller.to_s.start_with?('B', 'C', 'D', 'E', 'F',
+ 'H', 'I', 'J', 'K', 'L',
+ 'M', 'N', 'O', 'Q', 'R',
+ 'S', 'T', 'U', 'V', 'W',
+ 'X', 'Y', 'Z',
+ 'Projects::MergeRequestsController')
"#{controller}##{action}"
end.compact
end
it "has feature categories" do
- routes_without_category.map { |x| x.split('#') }.group_by(&:first).each do |controller, actions|
- puts controller
- puts actions.map { |x| ":#{x.last}" }.sort.join(', ')
- puts ''
- end
-
expect(routes_without_category).to be_empty, "#{routes_without_category} did not have a category"
end
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index d7c22d46e90..eff98ab65a6 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -197,16 +197,40 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'with not expiry date' do
let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
- it 'exposes needed information' do
- get_show_json
+ context 'when artifacts are unlocked' do
+ before do
+ job.pipeline.unlocked!
+ end
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('job/job_details')
- expect(json_response['artifact']['download_path']).to match(%r{artifacts/download})
- expect(json_response['artifact']['browse_path']).to match(%r{artifacts/browse})
- expect(json_response['artifact']).not_to have_key('keep_path')
- expect(json_response['artifact']).not_to have_key('expired')
- expect(json_response['artifact']).not_to have_key('expired_at')
+ it 'exposes needed information' do
+ get_show_json
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('job/job_details')
+ expect(json_response['artifact']['download_path']).to match(%r{artifacts/download})
+ expect(json_response['artifact']['browse_path']).to match(%r{artifacts/browse})
+ expect(json_response['artifact']).not_to have_key('keep_path')
+ expect(json_response['artifact']).not_to have_key('expired')
+ expect(json_response['artifact']).not_to have_key('expired_at')
+ end
+ end
+
+ context 'when artifacts are locked' do
+ before do
+ job.pipeline.artifacts_locked!
+ end
+
+ it 'exposes needed information' do
+ get_show_json
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('job/job_details')
+ expect(json_response['artifact']['download_path']).to match(%r{artifacts/download})
+ expect(json_response['artifact']['browse_path']).to match(%r{artifacts/browse})
+ expect(json_response['artifact']).not_to have_key('keep_path')
+ expect(json_response['artifact']).not_to have_key('expired')
+ expect(json_response['artifact']).not_to have_key('expired_at')
+ end
end
end
diff --git a/spec/factories/packages.rb b/spec/factories/packages.rb
index 3562f57654e..e2c5b000988 100644
--- a/spec/factories/packages.rb
+++ b/spec/factories/packages.rb
@@ -21,6 +21,10 @@ FactoryBot.define do
end
end
+ factory :debian_package do
+ package_type { :debian }
+ end
+
factory :npm_package do
sequence(:name) { |n| "@#{project.root_namespace.path}/package-#{n}"}
version { '1.0.0' }
diff --git a/spec/frontend/design_management/components/__snapshots__/design_note_pin_spec.js.snap b/spec/frontend/design_management/components/__snapshots__/design_note_pin_spec.js.snap
index 62a0f675cff..ed8ed3254ba 100644
--- a/spec/frontend/design_management/components/__snapshots__/design_note_pin_spec.js.snap
+++ b/spec/frontend/design_management/components/__snapshots__/design_note_pin_spec.js.snap
@@ -1,23 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Design note pin component should match the snapshot of note when repositioning 1`] = `
-<button
- aria-label="Comment form position"
- class="design-pin gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-0 btn-transparent comment-indicator"
- style="left: 10px; top: 10px; cursor: move;"
- type="button"
->
- <gl-icon-stub
- name="image-comment-dark"
- size="24"
- />
-</button>
-`;
-
exports[`Design note pin component should match the snapshot of note with index 1`] = `
<button
aria-label="Comment '1' position"
- class="design-pin gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-0 js-image-badge badge badge-pill"
+ class="gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-font-lg gl-outline-0! js-image-badge badge badge-pill"
style="left: 10px; top: 10px;"
type="button"
>
@@ -30,7 +16,7 @@ exports[`Design note pin component should match the snapshot of note with index
exports[`Design note pin component should match the snapshot of note without index 1`] = `
<button
aria-label="Comment form position"
- class="design-pin gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-0 btn-transparent comment-indicator"
+ class="gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-font-lg gl-outline-0! btn-transparent comment-indicator gl-p-0"
style="left: 10px; top: 10px;"
type="button"
>
diff --git a/spec/frontend/design_management/components/design_note_pin_spec.js b/spec/frontend/design_management/components/design_note_pin_spec.js
index 4e045b58a35..a6219923aca 100644
--- a/spec/frontend/design_management/components/design_note_pin_spec.js
+++ b/spec/frontend/design_management/components/design_note_pin_spec.js
@@ -29,21 +29,4 @@ describe('Design note pin component', () => {
createComponent({ label: 1 });
expect(wrapper.element).toMatchSnapshot();
});
-
- it('should match the snapshot of note when repositioning', () => {
- createComponent({ repositioning: true });
- expect(wrapper.element).toMatchSnapshot();
- });
-
- describe('pinStyle', () => {
- it('sets cursor to `move` when repositioning = true', () => {
- createComponent({ repositioning: true });
- expect(wrapper.vm.pinStyle.cursor).toBe('move');
- });
-
- it('does not set cursor when repositioning = false', () => {
- createComponent();
- expect(wrapper.vm.pinStyle.cursor).toBe(undefined);
- });
- });
});
diff --git a/spec/frontend/design_management/components/design_overlay_spec.js b/spec/frontend/design_management/components/design_overlay_spec.js
index 673a09320e5..4ef067e3f5e 100644
--- a/spec/frontend/design_management/components/design_overlay_spec.js
+++ b/spec/frontend/design_management/components/design_overlay_spec.js
@@ -202,7 +202,7 @@ describe('Design overlay component', () => {
{ x: position.x, y: position.y },
{ x: 20, y: 20 },
).then(() => {
- expect(findFirstBadge().attributes().style).toBe('left: 20px; top: 20px; cursor: move;');
+ expect(findFirstBadge().attributes().style).toBe('left: 20px; top: 20px;');
});
});
@@ -300,9 +300,7 @@ describe('Design overlay component', () => {
{ x: position.x, y: position.y },
{ x: 20, y: 20 },
).then(() => {
- expect(findCommentBadge().attributes().style).toBe(
- 'left: 20px; top: 20px; cursor: move;',
- );
+ expect(findCommentBadge().attributes().style).toBe('left: 20px; top: 20px;');
});
});
diff --git a/spec/frontend/packages/details/store/getters_spec.js b/spec/frontend/packages/details/store/getters_spec.js
index 06e5950eb5d..cb164a426c8 100644
--- a/spec/frontend/packages/details/store/getters_spec.js
+++ b/spec/frontend/packages/details/store/getters_spec.js
@@ -101,7 +101,7 @@ describe('Getters PackageDetails Store', () => {
${packageWithoutBuildInfo} | ${'Maven'}
${npmPackage} | ${'NPM'}
${nugetPackage} | ${'NuGet'}
- ${pypiPackage} | ${'PyPi'}
+ ${pypiPackage} | ${'PyPI'}
`(`package type`, ({ packageEntity, expectedResult }) => {
beforeEach(() => setupState({ packageEntity }));
diff --git a/spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap b/spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap
index 794e583a487..9a52531ae8d 100644
--- a/spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap
+++ b/spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap
@@ -385,7 +385,7 @@ exports[`packages_list_app renders 1`] = `
</b-tab-stub>
<b-tab-stub
tag="div"
- title="PyPi"
+ title="PyPI"
titlelinkclass="gl-tab-nav-item"
>
<template>
@@ -400,7 +400,7 @@ exports[`packages_list_app renders 1`] = `
class="svg-250 svg-content"
>
<img
- alt="There are no PyPi packages yet"
+ alt="There are no PyPI packages yet"
class="gl-max-w-full"
src="helpSvg"
/>
@@ -416,7 +416,7 @@ exports[`packages_list_app renders 1`] = `
<h1
class="h4"
>
- There are no PyPi packages yet
+ There are no PyPI packages yet
</h1>
<p>
diff --git a/spec/frontend/packages/shared/utils_spec.js b/spec/frontend/packages/shared/utils_spec.js
index 1fe90a4827f..3e4ce8eb323 100644
--- a/spec/frontend/packages/shared/utils_spec.js
+++ b/spec/frontend/packages/shared/utils_spec.js
@@ -37,7 +37,7 @@ describe('Packages shared utils', () => {
${'maven'} | ${'Maven'}
${'npm'} | ${'NPM'}
${'nuget'} | ${'NuGet'}
- ${'pypi'} | ${'PyPi'}
+ ${'pypi'} | ${'PyPI'}
${'composer'} | ${'Composer'}
${'foo'} | ${null}
`(`package type`, ({ packageType, expectedResult }) => {
diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb
index 46aebbdabeb..1bad9f8c627 100644
--- a/spec/graphql/types/merge_request_type_spec.rb
+++ b/spec/graphql/types/merge_request_type_spec.rb
@@ -27,14 +27,13 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do
upvotes downvotes head_pipeline pipelines task_completion_status
milestone assignees participants subscribed labels discussion_locked time_estimate
total_time_spent reference author merged_at commit_count current_user_todos
- conflicts auto_merge_enabled
+ conflicts auto_merge_enabled approved_by
]
if Gitlab.ee?
expected_fields << 'approved'
expected_fields << 'approvals_left'
expected_fields << 'approvals_required'
- expected_fields << 'approved_by'
end
expect(described_class).to have_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/package_type_enum_spec.rb b/spec/graphql/types/package_type_enum_spec.rb
index 638f8ccbaee..407d5786f65 100644
--- a/spec/graphql/types/package_type_enum_spec.rb
+++ b/spec/graphql/types/package_type_enum_spec.rb
@@ -4,6 +4,6 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['PackageTypeEnum'] do
it 'exposes all package types' do
- expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG])
+ expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG DEBIAN])
end
end
diff --git a/spec/lib/gitlab/ci/lint_spec.rb b/spec/lib/gitlab/ci/lint_spec.rb
index 077c0fd3162..c67f8464123 100644
--- a/spec/lib/gitlab/ci/lint_spec.rb
+++ b/spec/lib/gitlab/ci/lint_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Lint do
- let_it_be(:project) { create(:project, :repository) }
+ let(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:lint) { described_class.new(project: project, current_user: user) }
@@ -61,6 +61,43 @@ RSpec.describe Gitlab::Ci::Lint do
end
end
+ shared_examples 'sets merged yaml' do
+ let(:content) do
+ <<~YAML
+ :include:
+ :local: another-gitlab-ci.yml
+ :test_job:
+ :stage: test
+ :script: echo
+ YAML
+ end
+
+ let(:included_content) do
+ <<~YAML
+ :another_job:
+ :script: echo
+ YAML
+ end
+
+ before do
+ project.repository.create_file(
+ project.creator,
+ 'another-gitlab-ci.yml',
+ included_content,
+ message: 'Automatically created another-gitlab-ci.yml',
+ branch_name: 'master'
+ )
+ end
+
+ it 'sets merged_config' do
+ root_config = YAML.safe_load(content, [Symbol])
+ included_config = YAML.safe_load(included_content, [Symbol])
+ expected_config = included_config.merge(root_config).except(:include)
+
+ expect(subject.merged_yaml).to eq(expected_config.to_yaml)
+ end
+ end
+
shared_examples 'content with errors and warnings' do
context 'when content has errors' do
let(:content) do
@@ -173,6 +210,8 @@ RSpec.describe Gitlab::Ci::Lint do
end
end
+ it_behaves_like 'sets merged yaml'
+
include_context 'advanced validations' do
it 'does not catch advanced logical errors' do
expect(subject).to be_valid
@@ -203,6 +242,8 @@ RSpec.describe Gitlab::Ci::Lint do
end
end
+ it_behaves_like 'sets merged yaml'
+
include_context 'advanced validations' do
it 'runs advanced logical validations' do
expect(subject).not_to be_valid
diff --git a/spec/lib/gitlab/ci/reports/test_suite_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_spec.rb
index 15fa78444e5..50d1595da73 100644
--- a/spec/lib/gitlab/ci/reports/test_suite_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_suite_spec.rb
@@ -229,6 +229,20 @@ RSpec.describe Gitlab::Ci::Reports::TestSuite do
end
end
+ describe '#each_test_case' do
+ before do
+ test_suite.add_test_case(test_case_success)
+ test_suite.add_test_case(test_case_failed)
+ test_suite.add_test_case(test_case_skipped)
+ test_suite.add_test_case(test_case_error)
+ end
+
+ it 'yields each test case to given block' do
+ expect { |b| test_suite.each_test_case(&b) }
+ .to yield_successive_args(test_case_success, test_case_failed, test_case_skipped, test_case_error)
+ end
+ end
+
Gitlab::Ci::Reports::TestCase::STATUS_TYPES.each do |status_type|
describe "##{status_type}_count" do
subject { test_suite.public_send("#{status_type}_count") }
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index f2bc6390032..37349c30224 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -3,18 +3,16 @@
require 'spec_helper'
RSpec.describe Gitlab::ClosingIssueExtractor do
- let(:project) { create(:project) }
- let(:project2) { create(:project) }
- let(:forked_project) { Projects::ForkService.new(project, project2.creator).execute }
- let(:issue) { create(:issue, project: project) }
- let(:issue2) { create(:issue, project: project2) }
+ let_it_be_with_reload(:project) { create(:project) }
+ let_it_be(:project2) { create(:project) }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:issue2) { create(:issue, project: project2) }
let(:reference) { issue.to_reference }
let(:cross_reference) { issue2.to_reference(project) }
- let(:fork_cross_reference) { issue.to_reference(forked_project) }
subject { described_class.new(project, project.creator) }
- before do
+ before_all do
project.add_developer(project.creator)
project.add_developer(project2.creator)
project2.add_maintainer(project.creator)
@@ -325,6 +323,9 @@ RSpec.describe Gitlab::ClosingIssueExtractor do
end
context "with a cross-project fork reference" do
+ let(:forked_project) { Projects::ForkService.new(project, project2.creator).execute }
+ let(:fork_cross_reference) { issue.to_reference(forked_project) }
+
subject { described_class.new(forked_project, forked_project.creator) }
it do
@@ -348,8 +349,8 @@ RSpec.describe Gitlab::ClosingIssueExtractor do
end
context 'with multiple references' do
- let(:other_issue) { create(:issue, project: project) }
- let(:third_issue) { create(:issue, project: project) }
+ let_it_be(:other_issue) { create(:issue, project: project) }
+ let_it_be(:third_issue) { create(:issue, project: project) }
let(:reference2) { other_issue.to_reference }
let(:reference3) { third_issue.to_reference }
diff --git a/spec/lib/gitlab/redis/hll_spec.rb b/spec/lib/gitlab/redis/hll_spec.rb
index cbf78f23036..e452e5b2f52 100644
--- a/spec/lib/gitlab/redis/hll_spec.rb
+++ b/spec/lib/gitlab/redis/hll_spec.rb
@@ -39,6 +39,24 @@ RSpec.describe Gitlab::Redis::HLL, :clean_gitlab_redis_shared_state do
end
end
end
+
+ context 'when adding entries' do
+ let(:metric) { 'test-{metric}' }
+
+ it 'supports single value' do
+ track_event(metric, 1)
+
+ expect(count_unique_events([metric])).to eq(1)
+ end
+
+ it 'supports multiple values' do
+ stub_const("#{described_class.name}::HLL_BATCH_SIZE", 2)
+
+ track_event(metric, [1, 2, 3, 4, 5])
+
+ expect(count_unique_events([metric])).to eq(5)
+ end
+ end
end
describe '.count' do
@@ -94,13 +112,13 @@ RSpec.describe Gitlab::Redis::HLL, :clean_gitlab_redis_shared_state do
expect(unique_counts).to eq(4)
end
+ end
- def track_event(key, value, expiry = 1.day)
- described_class.add(key: key, value: value, expiry: expiry)
- end
+ def track_event(key, value, expiry = 1.day)
+ described_class.add(key: key, value: value, expiry: expiry)
+ end
- def count_unique_events(keys)
- described_class.count(keys: keys)
- end
+ def count_unique_events(keys)
+ described_class.count(keys: keys)
end
end
diff --git a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
index 3255e3616b2..e84c3c17274 100644
--- a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
describe '.categories' do
it 'gets all unique category names' do
- expect(described_class.categories).to contain_exactly('analytics', 'compliance', 'ide_edit', 'search', 'source_code', 'incident_management', 'issues_edit')
+ expect(described_class.categories).to contain_exactly('analytics', 'compliance', 'ide_edit', 'search', 'source_code', 'incident_management', 'issues_edit', 'testing')
end
end
diff --git a/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb
index 4cc85c86de1..bd3c8024215 100644
--- a/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb
@@ -132,6 +132,76 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
end
+ context 'for Issue cross-referenced actions' do
+ it_behaves_like 'tracks and counts action' do
+ let(:action) { described_class::ISSUE_CROSS_REFERENCED }
+
+ def track_action(params)
+ described_class.track_issue_cross_referenced_action(**params)
+ end
+ end
+ end
+
+ context 'for Issue moved actions' do
+ it_behaves_like 'tracks and counts action' do
+ let(:action) { described_class::ISSUE_MOVED }
+
+ def track_action(params)
+ described_class.track_issue_moved_action(**params)
+ end
+ end
+ end
+
+ context 'for Issue relate actions' do
+ it_behaves_like 'tracks and counts action' do
+ let(:action) { described_class::ISSUE_RELATED }
+
+ def track_action(params)
+ described_class.track_issue_related_action(**params)
+ end
+ end
+ end
+
+ context 'for Issue unrelate actions' do
+ it_behaves_like 'tracks and counts action' do
+ let(:action) { described_class::ISSUE_UNRELATED }
+
+ def track_action(params)
+ described_class.track_issue_unrelated_action(**params)
+ end
+ end
+ end
+
+ context 'for Issue marked as duplicate actions' do
+ it_behaves_like 'tracks and counts action' do
+ let(:action) { described_class::ISSUE_MARKED_AS_DUPLICATE }
+
+ def track_action(params)
+ described_class.track_issue_marked_as_duplicate_action(**params)
+ end
+ end
+ end
+
+ context 'for Issue locked actions' do
+ it_behaves_like 'tracks and counts action' do
+ let(:action) { described_class::ISSUE_LOCKED }
+
+ def track_action(params)
+ described_class.track_issue_locked_action(**params)
+ end
+ end
+ end
+
+ context 'for Issue unlocked actions' do
+ it_behaves_like 'tracks and counts action' do
+ let(:action) { described_class::ISSUE_UNLOCKED }
+
+ def track_action(params)
+ described_class.track_issue_unlocked_action(**params)
+ end
+ end
+ end
+
it 'can return the count of actions per user deduplicated', :aggregate_failures do
described_class.track_issue_title_changed_action(author: user1)
described_class.track_issue_description_changed_action(author: user1)
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 984fd4c08e6..09728539499 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -1182,9 +1182,9 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
subject { described_class.redis_hll_counters }
let(:categories) { ::Gitlab::UsageDataCounters::HLLRedisCounter.categories }
- let(:ineligible_total_categories) { ['source_code'] }
+ let(:ineligible_total_categories) { %w[source_code testing] }
- it 'has all know_events' do
+ it 'has all known_events' do
expect(subject).to have_key(:redis_hll_counters)
expect(subject[:redis_hll_counters].keys).to match_array(categories)
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 49731757593..ab22a203d06 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -2307,6 +2307,54 @@ RSpec.describe Ci::Build do
end
end
+ describe '#has_expired_locked_archive_artifacts?' do
+ subject { build.has_expired_locked_archive_artifacts? }
+
+ context 'when build does not have artifacts' do
+ it { is_expected.to eq(nil) }
+ end
+
+ context 'when build has artifacts' do
+ before do
+ create(:ci_job_artifact, :archive, job: build)
+ end
+
+ context 'when artifacts are unlocked' do
+ before do
+ build.pipeline.unlocked!
+ end
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when artifacts are locked' do
+ before do
+ build.pipeline.artifacts_locked!
+ end
+
+ context 'when artifacts do not expire' do
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when artifacts expire in the future' do
+ before do
+ build.update!(artifacts_expire_at: 1.day.from_now)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when artifacts expired in the past' do
+ before do
+ build.update!(artifacts_expire_at: 1.day.ago)
+ end
+
+ it { is_expected.to eq(true) }
+ end
+ end
+ end
+ end
+
describe '#has_expiring_archive_artifacts?' do
context 'when artifacts have expiration date set' do
before do
diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb
index dfce1e12dd5..40e5e1d5a84 100644
--- a/spec/requests/api/graphql/project/merge_requests_spec.rb
+++ b/spec/requests/api/graphql/project/merge_requests_spec.rb
@@ -173,6 +173,28 @@ RSpec.describe 'getting merge request listings nested in a project' do
it_behaves_like 'searching with parameters'
end
+ context 'when requesting `approved_by`' do
+ let(:search_params) { { iids: [merge_request_a.iid.to_s, merge_request_b.iid.to_s] } }
+ let(:extra_iid_for_second_query) { merge_request_c.iid.to_s }
+ let(:requested_fields) { query_graphql_field(:approved_by, nil, query_graphql_field(:nodes, nil, [:username])) }
+
+ def execute_query
+ query = query_merge_requests(requested_fields)
+ post_graphql(query, current_user: current_user)
+ end
+
+ it 'exposes approver username' do
+ merge_request_a.approved_by_users << current_user
+
+ execute_query
+
+ user_data = { 'username' => current_user.username }
+ expect(results).to include(a_hash_including('approvedBy' => { 'nodes' => array_including(user_data) }))
+ end
+
+ include_examples 'N+1 query check'
+ end
+
describe 'fields' do
let(:requested_fields) { nil }
let(:extra_iid_for_second_query) { merge_request_c.iid.to_s }
diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb
index 6b5a4b6436a..3892df273bd 100644
--- a/spec/requests/api/lint_spec.rb
+++ b/spec/requests/api/lint_spec.rb
@@ -75,4 +75,115 @@ RSpec.describe API::Lint do
end
end
end
+
+ describe 'GET /projects/:id/ci/lint' do
+ subject(:ci_lint) { get api("/projects/#{project.id}/ci/lint", api_user), params: { dry_run: dry_run } }
+
+ let_it_be(:api_user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:dry_run) { nil }
+
+ RSpec.shared_examples 'valid config' do
+ it 'passes validation' do
+ ci_lint
+
+ included_config = YAML.safe_load(included_content, [Symbol])
+ root_config = YAML.safe_load(yaml_content, [Symbol])
+ expected_yaml = included_config.merge(root_config).except(:include).to_yaml
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an Hash
+ expect(json_response['merged_yaml']).to eq(expected_yaml)
+ expect(json_response['valid']).to eq(true)
+ expect(json_response['errors']).to eq([])
+ end
+ end
+
+ RSpec.shared_examples 'invalid config' do
+ it 'responds with errors about invalid configuration' do
+ ci_lint
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['merged_yaml']).to eq(yaml_content)
+ expect(json_response['valid']).to eq(false)
+ expect(json_response['errors']).to eq(['jobs config should contain at least one visible job'])
+ end
+ end
+
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ ci_lint
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when authenticated as project member' do
+ before do
+ project.add_developer(api_user)
+ end
+
+ context 'with valid .gitlab-ci.yml content' do
+ let(:yaml_content) do
+ { include: { local: 'another-gitlab-ci.yml' }, test: { stage: 'test', script: 'echo 1' } }.to_yaml
+ end
+
+ let(:included_content) do
+ { another_test: { stage: 'test', script: 'echo 1' } }.to_yaml
+ end
+
+ before do
+ project.repository.create_file(
+ project.creator,
+ '.gitlab-ci.yml',
+ yaml_content,
+ message: 'Automatically created .gitlab-ci.yml',
+ branch_name: 'master'
+ )
+
+ project.repository.create_file(
+ project.creator,
+ 'another-gitlab-ci.yml',
+ included_content,
+ message: 'Automatically created another-gitlab-ci.yml',
+ branch_name: 'master'
+ )
+ end
+
+ context 'when running as dry run' do
+ let(:dry_run) { true }
+
+ it_behaves_like 'valid config'
+ end
+
+ context 'when running static validation' do
+ let(:dry_run) { false }
+
+ it_behaves_like 'valid config'
+ end
+ end
+
+ context 'with invalid .gitlab-ci.yml content' do
+ let(:yaml_content) do
+ { image: 'ruby:2.7', services: ['postgres'] }.to_yaml
+ end
+
+ before do
+ stub_ci_pipeline_yaml_file(yaml_content)
+ end
+
+ context 'when running as dry run' do
+ let(:dry_run) { true }
+
+ it_behaves_like 'invalid config'
+ end
+
+ context 'when running static validation' do
+ let(:dry_run) { false }
+
+ it_behaves_like 'invalid config'
+ end
+ end
+ end
+ end
end
diff --git a/spec/requests/api/pypi_packages_spec.rb b/spec/requests/api/pypi_packages_spec.rb
index e72ac002f6b..de5dea449f8 100644
--- a/spec/requests/api/pypi_packages_spec.rb
+++ b/spec/requests/api/pypi_packages_spec.rb
@@ -23,24 +23,24 @@ RSpec.describe API::PypiPackages do
using RSpec::Parameterized::TableSyntax
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
- 'PUBLIC' | :developer | true | true | 'PyPi package versions' | :success
- 'PUBLIC' | :guest | true | true | 'PyPi package versions' | :success
- 'PUBLIC' | :developer | true | false | 'PyPi package versions' | :success
- 'PUBLIC' | :guest | true | false | 'PyPi package versions' | :success
- 'PUBLIC' | :developer | false | true | 'PyPi package versions' | :success
- 'PUBLIC' | :guest | false | true | 'PyPi package versions' | :success
- 'PUBLIC' | :developer | false | false | 'PyPi package versions' | :success
- 'PUBLIC' | :guest | false | false | 'PyPi package versions' | :success
- 'PUBLIC' | :anonymous | false | true | 'PyPi package versions' | :success
- 'PRIVATE' | :developer | true | true | 'PyPi package versions' | :success
- 'PRIVATE' | :guest | true | true | 'process PyPi api request' | :forbidden
- 'PRIVATE' | :developer | true | false | 'process PyPi api request' | :unauthorized
- 'PRIVATE' | :guest | true | false | 'process PyPi api request' | :unauthorized
- 'PRIVATE' | :developer | false | true | 'process PyPi api request' | :not_found
- 'PRIVATE' | :guest | false | true | 'process PyPi api request' | :not_found
- 'PRIVATE' | :developer | false | false | 'process PyPi api request' | :unauthorized
- 'PRIVATE' | :guest | false | false | 'process PyPi api request' | :unauthorized
- 'PRIVATE' | :anonymous | false | true | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :developer | true | true | 'PyPI package versions' | :success
+ 'PUBLIC' | :guest | true | true | 'PyPI package versions' | :success
+ 'PUBLIC' | :developer | true | false | 'PyPI package versions' | :success
+ 'PUBLIC' | :guest | true | false | 'PyPI package versions' | :success
+ 'PUBLIC' | :developer | false | true | 'PyPI package versions' | :success
+ 'PUBLIC' | :guest | false | true | 'PyPI package versions' | :success
+ 'PUBLIC' | :developer | false | false | 'PyPI package versions' | :success
+ 'PUBLIC' | :guest | false | false | 'PyPI package versions' | :success
+ 'PUBLIC' | :anonymous | false | true | 'PyPI package versions' | :success
+ 'PRIVATE' | :developer | true | true | 'PyPI package versions' | :success
+ 'PRIVATE' | :guest | true | true | 'process PyPI api request' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'process PyPI api request' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'process PyPI api request' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'process PyPI api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process PyPI api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process PyPI api request' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'process PyPI api request' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'process PyPI api request' | :unauthorized
end
with_them do
@@ -76,24 +76,24 @@ RSpec.describe API::PypiPackages do
using RSpec::Parameterized::TableSyntax
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
- 'PUBLIC' | :developer | true | true | 'process PyPi api request' | :success
- 'PUBLIC' | :guest | true | true | 'process PyPi api request' | :forbidden
- 'PUBLIC' | :developer | true | false | 'process PyPi api request' | :unauthorized
- 'PUBLIC' | :guest | true | false | 'process PyPi api request' | :unauthorized
- 'PUBLIC' | :developer | false | true | 'process PyPi api request' | :forbidden
- 'PUBLIC' | :guest | false | true | 'process PyPi api request' | :forbidden
- 'PUBLIC' | :developer | false | false | 'process PyPi api request' | :unauthorized
- 'PUBLIC' | :guest | false | false | 'process PyPi api request' | :unauthorized
- 'PUBLIC' | :anonymous | false | true | 'process PyPi api request' | :unauthorized
- 'PRIVATE' | :developer | true | true | 'process PyPi api request' | :success
- 'PRIVATE' | :guest | true | true | 'process PyPi api request' | :forbidden
- 'PRIVATE' | :developer | true | false | 'process PyPi api request' | :unauthorized
- 'PRIVATE' | :guest | true | false | 'process PyPi api request' | :unauthorized
- 'PRIVATE' | :developer | false | true | 'process PyPi api request' | :not_found
- 'PRIVATE' | :guest | false | true | 'process PyPi api request' | :not_found
- 'PRIVATE' | :developer | false | false | 'process PyPi api request' | :unauthorized
- 'PRIVATE' | :guest | false | false | 'process PyPi api request' | :unauthorized
- 'PRIVATE' | :anonymous | false | true | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :developer | true | true | 'process PyPI api request' | :success
+ 'PUBLIC' | :guest | true | true | 'process PyPI api request' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'process PyPI api request' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'process PyPI api request' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'process PyPI api request' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'process PyPI api request' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'process PyPI api request' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'process PyPI api request' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'process PyPI api request' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'process PyPI api request' | :success
+ 'PRIVATE' | :guest | true | true | 'process PyPI api request' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'process PyPI api request' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'process PyPI api request' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'process PyPI api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process PyPI api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process PyPI api request' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'process PyPI api request' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'process PyPI api request' | :unauthorized
end
with_them do
@@ -142,24 +142,24 @@ RSpec.describe API::PypiPackages do
using RSpec::Parameterized::TableSyntax
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
- 'PUBLIC' | :developer | true | true | 'PyPi package creation' | :created
- 'PUBLIC' | :guest | true | true | 'process PyPi api request' | :forbidden
- 'PUBLIC' | :developer | true | false | 'process PyPi api request' | :unauthorized
- 'PUBLIC' | :guest | true | false | 'process PyPi api request' | :unauthorized
- 'PUBLIC' | :developer | false | true | 'process PyPi api request' | :forbidden
- 'PUBLIC' | :guest | false | true | 'process PyPi api request' | :forbidden
- 'PUBLIC' | :developer | false | false | 'process PyPi api request' | :unauthorized
- 'PUBLIC' | :guest | false | false | 'process PyPi api request' | :unauthorized
- 'PUBLIC' | :anonymous | false | true | 'process PyPi api request' | :unauthorized
- 'PRIVATE' | :developer | true | true | 'process PyPi api request' | :created
- 'PRIVATE' | :guest | true | true | 'process PyPi api request' | :forbidden
- 'PRIVATE' | :developer | true | false | 'process PyPi api request' | :unauthorized
- 'PRIVATE' | :guest | true | false | 'process PyPi api request' | :unauthorized
- 'PRIVATE' | :developer | false | true | 'process PyPi api request' | :not_found
- 'PRIVATE' | :guest | false | true | 'process PyPi api request' | :not_found
- 'PRIVATE' | :developer | false | false | 'process PyPi api request' | :unauthorized
- 'PRIVATE' | :guest | false | false | 'process PyPi api request' | :unauthorized
- 'PRIVATE' | :anonymous | false | true | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :developer | true | true | 'PyPI package creation' | :created
+ 'PUBLIC' | :guest | true | true | 'process PyPI api request' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'process PyPI api request' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'process PyPI api request' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'process PyPI api request' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'process PyPI api request' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'process PyPI api request' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'process PyPI api request' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'process PyPI api request' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'process PyPI api request' | :created
+ 'PRIVATE' | :guest | true | true | 'process PyPI api request' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'process PyPI api request' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'process PyPI api request' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'process PyPI api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process PyPI api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process PyPI api request' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'process PyPI api request' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'process PyPI api request' | :unauthorized
end
with_them do
@@ -185,7 +185,7 @@ RSpec.describe API::PypiPackages do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
- it_behaves_like 'process PyPi api request', :developer, :bad_request, true
+ it_behaves_like 'process PyPI api request', :developer, :bad_request, true
end
context 'with an invalid package' do
@@ -232,24 +232,24 @@ RSpec.describe API::PypiPackages do
using RSpec::Parameterized::TableSyntax
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
- 'PUBLIC' | :developer | true | true | 'PyPi package download' | :success
- 'PUBLIC' | :guest | true | true | 'PyPi package download' | :success
- 'PUBLIC' | :developer | true | false | 'PyPi package download' | :success
- 'PUBLIC' | :guest | true | false | 'PyPi package download' | :success
- 'PUBLIC' | :developer | false | true | 'PyPi package download' | :success
- 'PUBLIC' | :guest | false | true | 'PyPi package download' | :success
- 'PUBLIC' | :developer | false | false | 'PyPi package download' | :success
- 'PUBLIC' | :guest | false | false | 'PyPi package download' | :success
- 'PUBLIC' | :anonymous | false | true | 'PyPi package download' | :success
- 'PRIVATE' | :developer | true | true | 'PyPi package download' | :success
- 'PRIVATE' | :guest | true | true | 'PyPi package download' | :success
- 'PRIVATE' | :developer | true | false | 'PyPi package download' | :success
- 'PRIVATE' | :guest | true | false | 'PyPi package download' | :success
- 'PRIVATE' | :developer | false | true | 'PyPi package download' | :success
- 'PRIVATE' | :guest | false | true | 'PyPi package download' | :success
- 'PRIVATE' | :developer | false | false | 'PyPi package download' | :success
- 'PRIVATE' | :guest | false | false | 'PyPi package download' | :success
- 'PRIVATE' | :anonymous | false | true | 'PyPi package download' | :success
+ 'PUBLIC' | :developer | true | true | 'PyPI package download' | :success
+ 'PUBLIC' | :guest | true | true | 'PyPI package download' | :success
+ 'PUBLIC' | :developer | true | false | 'PyPI package download' | :success
+ 'PUBLIC' | :guest | true | false | 'PyPI package download' | :success
+ 'PUBLIC' | :developer | false | true | 'PyPI package download' | :success
+ 'PUBLIC' | :guest | false | true | 'PyPI package download' | :success
+ 'PUBLIC' | :developer | false | false | 'PyPI package download' | :success
+ 'PUBLIC' | :guest | false | false | 'PyPI package download' | :success
+ 'PUBLIC' | :anonymous | false | true | 'PyPI package download' | :success
+ 'PRIVATE' | :developer | true | true | 'PyPI package download' | :success
+ 'PRIVATE' | :guest | true | true | 'PyPI package download' | :success
+ 'PRIVATE' | :developer | true | false | 'PyPI package download' | :success
+ 'PRIVATE' | :guest | true | false | 'PyPI package download' | :success
+ 'PRIVATE' | :developer | false | true | 'PyPI package download' | :success
+ 'PRIVATE' | :guest | false | true | 'PyPI package download' | :success
+ 'PRIVATE' | :developer | false | false | 'PyPI package download' | :success
+ 'PRIVATE' | :guest | false | false | 'PyPI package download' | :success
+ 'PRIVATE' | :anonymous | false | true | 'PyPI package download' | :success
end
with_them do
diff --git a/spec/services/ci/build_report_result_service_spec.rb b/spec/services/ci/build_report_result_service_spec.rb
index 70bcf74ba43..134b662a72a 100644
--- a/spec/services/ci/build_report_result_service_spec.rb
+++ b/spec/services/ci/build_report_result_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Ci::BuildReportResultService do
- describe "#execute" do
+ describe '#execute', :clean_gitlab_redis_shared_state do
subject(:build_report_result) { described_class.new.execute(build) }
context 'when build is finished' do
@@ -17,6 +17,25 @@ RSpec.describe Ci::BuildReportResultService do
expect(build_report_result.tests_skipped).to eq(0)
expect(build_report_result.tests_duration).to eq(0.010284)
expect(Ci::BuildReportResult.count).to eq(1)
+
+ unique_test_cases_parsed = Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(
+ event_names: described_class::EVENT_NAME,
+ start_date: 2.weeks.ago,
+ end_date: 2.weeks.from_now
+ )
+ expect(unique_test_cases_parsed).to eq(4)
+ end
+
+ context 'when feature flag for tracking is disabled' do
+ before do
+ stub_feature_flags(track_unique_test_cases_parsed: false)
+ end
+
+ it 'creates the report but does not track the event' do
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
+ expect(build_report_result.tests_name).to eq("test")
+ expect(Ci::BuildReportResult.count).to eq(1)
+ end
end
context 'when data has already been persisted' do
diff --git a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
index 40bedc84366..bbcf856350d 100644
--- a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples 'PyPi package creation' do |user_type, status, add_member = true|
+RSpec.shared_examples 'PyPI package creation' do |user_type, status, add_member = true|
RSpec.shared_examples 'creating pypi package files' do
it 'creates package files' do
expect { subject }
@@ -106,7 +106,7 @@ RSpec.shared_examples 'PyPi package creation' do |user_type, status, add_member
end
end
-RSpec.shared_examples 'PyPi package versions' do |user_type, status, add_member = true|
+RSpec.shared_examples 'PyPI package versions' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
@@ -123,7 +123,7 @@ RSpec.shared_examples 'PyPi package versions' do |user_type, status, add_member
end
end
-RSpec.shared_examples 'PyPi package download' do |user_type, status, add_member = true|
+RSpec.shared_examples 'PyPI package download' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
@@ -140,7 +140,7 @@ RSpec.shared_examples 'PyPi package download' do |user_type, status, add_member
end
end
-RSpec.shared_examples 'process PyPi api request' do |user_type, status, add_member = true|
+RSpec.shared_examples 'process PyPI api request' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
@@ -155,13 +155,13 @@ RSpec.shared_examples 'rejects PyPI access with unknown project id' do
let(:project) { OpenStruct.new(id: 1234567890) }
context 'as anonymous' do
- it_behaves_like 'process PyPi api request', :anonymous, :not_found
+ it_behaves_like 'process PyPI api request', :anonymous, :not_found
end
context 'as authenticated user' do
subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) }
- it_behaves_like 'process PyPi api request', :anonymous, :not_found
+ it_behaves_like 'process PyPI api request', :anonymous, :not_found
end
end
end
diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb
index c00a087311c..65f4b3b5513 100644
--- a/spec/support/shared_examples/services/packages_shared_examples.rb
+++ b/spec/support/shared_examples/services/packages_shared_examples.rb
@@ -171,6 +171,7 @@ RSpec.shared_examples 'filters on each package_type' do |is_project: false|
let_it_be(:package6) { create(:composer_package, project: project) }
let_it_be(:package7) { create(:generic_package, project: project) }
let_it_be(:package8) { create(:golang_package, project: project) }
+ let_it_be(:package9) { create(:debian_package, project: project) }
Packages::Package.package_types.keys.each do |package_type|
context "for package type #{package_type}" do