Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-09-14 21:12:06 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-09-14 21:12:06 +0300
commit30b01621d3e9e83f9f2d8a94dba6888eba7e8cc1 (patch)
tree309eb7898d9d73ab1d1c0cd65e43757a7201edfe
parentb119503b7039d1e79b87300a145afdcd1145c2d6 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/CODEOWNERS10
-rw-r--r--Gemfile.lock2
-rw-r--r--app/assets/javascripts/batch_comments/components/review_bar.vue7
-rw-r--r--app/assets/javascripts/batch_comments/constants.js2
-rw-r--r--app/assets/javascripts/diffs/components/app.vue2
-rw-r--r--app/assets/javascripts/lib/logger/hello.js16
-rw-r--r--app/assets/javascripts/lib/logger/hello_deferred.js5
-rw-r--r--app/assets/javascripts/main.js3
-rw-r--r--app/assets/javascripts/security_configuration/components/constants.js15
-rw-r--r--app/assets/javascripts/vue_shared/security_reports/constants.js1
-rw-r--r--app/assets/stylesheets/pages/issuable.scss4
-rw-r--r--app/finders/branches_finder.rb4
-rw-r--r--app/finders/repositories/tree_finder.rb61
-rw-r--r--app/models/application_setting.rb101
-rw-r--r--app/services/ci/stuck_builds/drop_helpers.rb58
-rw-r--r--app/services/ci/stuck_builds/drop_service.rb53
-rw-r--r--config/feature_flags/development/repository_tree_gitaly_pagination.yml (renamed from config/feature_flags/development/npm_finder_query_avoid_duplicated_conditions.yml)8
-rw-r--r--data/deprecations/templates/_deprecation_template.md.erb2
-rw-r--r--doc/ci/examples/index.md1
-rw-r--r--doc/development/database/efficient_in_operator_queries.md7
-rw-r--r--doc/development/documentation/styleguide/index.md11
-rw-r--r--doc/development/documentation/styleguide/word_list.md17
-rw-r--r--doc/user/admin_area/analytics/dev_ops_report.md1
-rw-r--r--doc/user/clusters/management_project_template.md2
-rw-r--r--doc/user/group/devops_adoption/index.md1
-rw-r--r--lib/api/helpers/packages/npm.rb2
-rw-r--r--lib/api/npm_project_packages.rb3
-rw-r--r--lib/api/repositories.rb18
-rw-r--r--lib/gitlab/cycle_analytics/summary/base.rb4
-rw-r--r--lib/gitlab/cycle_analytics/summary/deploy.rb2
-rw-r--r--lib/gitlab/database/load_balancing/load_balancer.rb2
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb1
-rw-r--r--lib/gitlab/pagination/gitaly_keyset_pager.rb34
-rw-r--r--locale/gitlab.pot9
-rw-r--r--spec/factories/ci/builds.rb8
-rw-r--r--spec/finders/branches_finder_spec.rb7
-rw-r--r--spec/finders/repositories/tree_finder_spec.rb95
-rw-r--r--spec/frontend/batch_comments/components/review_bar_spec.js42
-rw-r--r--spec/frontend/batch_comments/create_batch_comments_store.js15
-rw-r--r--spec/frontend/diffs/components/app_spec.js20
-rw-r--r--spec/frontend/lib/logger/__snapshots__/hello_spec.js.snap16
-rw-r--r--spec/frontend/lib/logger/hello_deferred_spec.js17
-rw-r--r--spec/frontend/lib/logger/hello_spec.js20
-rw-r--r--spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb117
-rw-r--r--spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb3
-rw-r--r--spec/requests/api/npm_instance_packages_spec.rb40
-rw-r--r--spec/requests/api/npm_project_packages_spec.rb34
-rw-r--r--spec/requests/api/repositories_spec.rb21
-rw-r--r--spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb124
-rw-r--r--spec/support/shared_examples/requests/api/packages_shared_examples.rb12
50 files changed, 707 insertions, 353 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index af1cf88b176..7dd676e6a47 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -13,7 +13,6 @@
/doc/.vale/ @marcel.amirault @eread @aqualls @cnorris
/doc/administration/geo/ @axil
/doc/administration/gitaly/ @eread
-/doc/administration/integration/ @aqualls
/doc/administration/lfs/ @aqualls
/doc/administration/monitoring/ @ngaskill
/doc/administration/operations/ @axil @eread @marcia
@@ -36,7 +35,6 @@
/doc/development/value_stream_analytics.md @msedlakjakubowski
/doc/gitlab-basics/ @aqualls
/doc/install/ @axil
-/doc/integration/ @aqualls @eread
/doc/operations/ @ngaskill @axil
/doc/push_rules/ @aqualls
/doc/ssh/ @eread
@@ -65,8 +63,6 @@
/doc/user/project/ @aqualls @axil @eread @msedlakjakubowski @ngaskill
/doc/user/project/clusters/ @marcia
/doc/user/project/import/ @ngaskill @msedlakjakubowski
-/doc/user/project/integrations/ @aqualls
-/doc/user/project/integrations/prometheus_library/ @ngaskill
/doc/user/project/issues/ @msedlakjakubowski
/doc/user/project/merge_requests/ @aqualls @eread
/doc/user/project/milestones/ @msedlakjakubowski
@@ -142,6 +138,12 @@
/doc/user/project/settings/import_export.md @aqualls
/doc/user/snippets.md @aqualls
+[Docs Ecosystem]
+/doc/administration/integration/ @kpaizee
+/doc/integration/ @kpaizee
+/doc/user/project/integrations/ @kpaizee
+/doc/user/project/integrations/prometheus_library/ @ngaskill
+
[Docs Growth]
/doc/administration/instance_review.md @kpaizee
/doc/api/invitations.md @kpaizee
diff --git a/Gemfile.lock b/Gemfile.lock
index 50f396a5743..dcc34698053 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -900,7 +900,7 @@ GEM
orm_adapter (0.5.0)
os (1.1.1)
parallel (1.20.1)
- parser (3.0.0.0)
+ parser (3.0.2.0)
ast (~> 2.4.1)
parslet (1.8.2)
pastel (0.8.0)
diff --git a/app/assets/javascripts/batch_comments/components/review_bar.vue b/app/assets/javascripts/batch_comments/components/review_bar.vue
index 158b5f45d1c..bce13751448 100644
--- a/app/assets/javascripts/batch_comments/components/review_bar.vue
+++ b/app/assets/javascripts/batch_comments/components/review_bar.vue
@@ -1,5 +1,6 @@
<script>
import { mapActions, mapGetters } from 'vuex';
+import { REVIEW_BAR_VISIBLE_CLASS_NAME } from '../constants';
import PreviewDropdown from './preview_dropdown.vue';
import PublishButton from './publish_button.vue';
@@ -18,6 +19,12 @@ export default {
}
},
},
+ mounted() {
+ document.body.classList.add(REVIEW_BAR_VISIBLE_CLASS_NAME);
+ },
+ beforeDestroy() {
+ document.body.classList.remove(REVIEW_BAR_VISIBLE_CLASS_NAME);
+ },
methods: {
...mapActions('batchComments', ['expandAllDiscussions']),
},
diff --git a/app/assets/javascripts/batch_comments/constants.js b/app/assets/javascripts/batch_comments/constants.js
index b309c339fc8..5e026251e0b 100644
--- a/app/assets/javascripts/batch_comments/constants.js
+++ b/app/assets/javascripts/batch_comments/constants.js
@@ -1,3 +1,5 @@
export const CHANGES_TAB = 'diffs';
export const DISCUSSION_TAB = 'notes';
export const SHOW_TAB = 'show';
+
+export const REVIEW_BAR_VISIBLE_CLASS_NAME = 'review-bar-visible';
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 57162c46002..a2ea42e963c 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -229,7 +229,6 @@ export default {
'isBatchLoading',
'isBatchLoadingError',
]),
- ...mapGetters('batchComments', ['draftsCount']),
...mapGetters(['isNotesFetched', 'getNoteableData']),
diffs() {
if (!this.viewDiffsFileByFile) {
@@ -664,7 +663,6 @@ export default {
<div
v-if="renderFileTree"
:style="{ width: `${treeWidth}px` }"
- :class="{ 'review-bar-visible': draftsCount > 0 }"
class="diff-tree-list js-diff-tree-list px-3 pr-md-0"
>
<panel-resizer
diff --git a/app/assets/javascripts/lib/logger/hello.js b/app/assets/javascripts/lib/logger/hello.js
new file mode 100644
index 00000000000..18fa35ab55b
--- /dev/null
+++ b/app/assets/javascripts/lib/logger/hello.js
@@ -0,0 +1,16 @@
+const HANDSHAKE = String.fromCodePoint(0x1f91d);
+const MAG = String.fromCodePoint(0x1f50e);
+
+export const logHello = () => {
+ // eslint-disable-next-line no-console
+ console.log(
+ `%cWelcome to GitLab!%c
+
+Does this page need fixes or improvements? Open an issue or contribute a merge request to help make GitLab more lovable. At GitLab, everyone can contribute!
+
+${HANDSHAKE} Contribute to GitLab: https://about.gitlab.com/community/contribute/
+${MAG} Create a new GitLab issue: https://gitlab.com/gitlab-org/gitlab/-/issues/new`,
+ `padding-top: 0.5em; font-size: 2em;`,
+ 'padding-bottom: 0.5em;',
+ );
+};
diff --git a/app/assets/javascripts/lib/logger/hello_deferred.js b/app/assets/javascripts/lib/logger/hello_deferred.js
new file mode 100644
index 00000000000..ce1dd91cb37
--- /dev/null
+++ b/app/assets/javascripts/lib/logger/hello_deferred.js
@@ -0,0 +1,5 @@
+export const logHelloDeferred = async () => {
+ const { logHello } = await import(/* webpackChunkName: 'hello' */ './hello');
+
+ logHello();
+};
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 15c483485f1..b96a2607552 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -19,6 +19,7 @@ import initAlertHandler from './alert_handler';
import { removeFlashClickListener } from './flash';
import initTodoToggle from './header';
import initLayoutNav from './layout_nav';
+import { logHelloDeferred } from './lib/logger/hello_deferred';
import { handleLocationHash, addSelectOnFocusBehaviour } from './lib/utils/common_utils';
import { localTimeAgo } from './lib/utils/datetime/timeago_utility';
import { getLocationHash, visitUrl } from './lib/utils/url_utility';
@@ -40,6 +41,8 @@ import { initHeaderSearchApp } from '~/header_search';
import 'ee_else_ce/main_ee';
import 'jh_else_ce/main_jh';
+logHelloDeferred();
+
applyGitLabUIConfig();
// expose jQuery as global (TODO: remove these)
diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js
index ebe0138f046..6a282df99bf 100644
--- a/app/assets/javascripts/security_configuration/components/constants.js
+++ b/app/assets/javascripts/security_configuration/components/constants.js
@@ -10,6 +10,7 @@ import {
REPORT_TYPE_CONTAINER_SCANNING,
REPORT_TYPE_CLUSTER_IMAGE_SCANNING,
REPORT_TYPE_COVERAGE_FUZZING,
+ REPORT_TYPE_CORPUS_MANAGEMENT,
REPORT_TYPE_API_FUZZING,
REPORT_TYPE_LICENSE_COMPLIANCE,
} from '~/vue_shared/security_reports/constants';
@@ -104,6 +105,12 @@ export const COVERAGE_FUZZING_CONFIG_HELP_PATH = helpPagePath(
{ anchor: 'configuration' },
);
+export const CORPUS_MANAGEMENT_NAME = __('Corpus Management');
+export const CORPUS_MANAGEMENT_DESCRIPTION = s__(
+ 'SecurityConfiguration|Manage corpus files used as mutation sources in coverage fuzzing.',
+);
+export const CORPUS_MANAGEMENT_CONFIG_TEXT = s__('SecurityConfiguration|Manage corpus');
+
export const API_FUZZING_NAME = __('API Fuzzing');
export const API_FUZZING_DESCRIPTION = __('Find bugs in your code with API fuzzing.');
export const API_FUZZING_HELP_PATH = helpPagePath('user/application_security/api_fuzzing/index');
@@ -202,6 +209,14 @@ export const securityFeatures = [
helpPath: COVERAGE_FUZZING_HELP_PATH,
configurationHelpPath: COVERAGE_FUZZING_CONFIG_HELP_PATH,
type: REPORT_TYPE_COVERAGE_FUZZING,
+ secondary: gon?.features?.corpusManagement
+ ? {
+ type: REPORT_TYPE_CORPUS_MANAGEMENT,
+ name: CORPUS_MANAGEMENT_NAME,
+ description: CORPUS_MANAGEMENT_DESCRIPTION,
+ configurationText: CORPUS_MANAGEMENT_CONFIG_TEXT,
+ }
+ : {},
},
];
diff --git a/app/assets/javascripts/vue_shared/security_reports/constants.js b/app/assets/javascripts/vue_shared/security_reports/constants.js
index 4a50dfbd82f..b024e92bd0e 100644
--- a/app/assets/javascripts/vue_shared/security_reports/constants.js
+++ b/app/assets/javascripts/vue_shared/security_reports/constants.js
@@ -24,6 +24,7 @@ export const REPORT_TYPE_DEPENDENCY_SCANNING = 'dependency_scanning';
export const REPORT_TYPE_CONTAINER_SCANNING = 'container_scanning';
export const REPORT_TYPE_CLUSTER_IMAGE_SCANNING = 'cluster_image_scanning';
export const REPORT_TYPE_COVERAGE_FUZZING = 'coverage_fuzzing';
+export const REPORT_TYPE_CORPUS_MANAGEMENT = 'corpus_management';
export const REPORT_TYPE_LICENSE_COMPLIANCE = 'license_scanning';
export const REPORT_TYPE_API_FUZZING = 'api_fuzzing';
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 4e05775fca6..94912b1c641 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -122,7 +122,9 @@
.right-sidebar {
position: fixed;
top: $header-height;
- bottom: 0;
+ // Default value for CSS var must contain a unit
+ // stylelint-disable-next-line length-zero-no-unit
+ bottom: var(--review-bar-height, 0px);
right: 0;
transition: width $sidebar-transition-duration;
background: $gray-light;
diff --git a/app/finders/branches_finder.rb b/app/finders/branches_finder.rb
index 157c454183a..a62d47071d4 100644
--- a/app/finders/branches_finder.rb
+++ b/app/finders/branches_finder.rb
@@ -15,6 +15,10 @@ class BranchesFinder < GitRefsFinder
end
end
+ def total
+ repository.branch_count
+ end
+
private
def names
diff --git a/app/finders/repositories/tree_finder.rb b/app/finders/repositories/tree_finder.rb
new file mode 100644
index 00000000000..2ea5a8856ec
--- /dev/null
+++ b/app/finders/repositories/tree_finder.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+module Repositories
+ class TreeFinder < GitRefsFinder
+ attr_reader :user_project
+
+ CommitMissingError = Class.new(StandardError)
+
+ def initialize(user_project, params = {})
+ super(user_project.repository, params)
+
+ @user_project = user_project
+ end
+
+ def execute(gitaly_pagination: false)
+ raise CommitMissingError unless commit_exists?
+
+ request_params = { recursive: recursive }
+ request_params[:pagination_params] = pagination_params if gitaly_pagination
+ tree = user_project.repository.tree(commit.id, path, **request_params)
+
+ tree.sorted_entries
+ end
+
+ def total
+ # This is inefficient and we'll look at replacing this implementation
+ Gitlab::Cache.fetch_once([user_project, repository.commit, :tree_size, commit.id, path, recursive]) do
+ user_project.repository.tree(commit.id, path, recursive: recursive).entries.size
+ end
+ end
+
+ def commit_exists?
+ commit.present?
+ end
+
+ private
+
+ def commit
+ @commit ||= user_project.commit(ref)
+ end
+
+ def ref
+ params[:ref] || user_project.default_branch
+ end
+
+ def path
+ params[:path]
+ end
+
+ def recursive
+ params[:recursive]
+ end
+
+ def pagination_params
+ {
+ limit: params[:per_page] || Kaminari.config.default_per_page,
+ page_token: params[:page_token]
+ }
+ end
+ end
+end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 719b1215c90..ebb4ba39dd6 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -468,85 +468,28 @@ class ApplicationSetting < ApplicationRecord
length: { maximum: 255, message: _('is too long (maximum is %{count} characters)') },
allow_blank: true
- validates :throttle_unauthenticated_api_requests_per_period,
- presence: true,
- numericality: { only_integer: true, greater_than: 0 }
-
- validates :throttle_unauthenticated_api_period_in_seconds,
- presence: true,
- numericality: { only_integer: true, greater_than: 0 }
-
- validates :throttle_unauthenticated_requests_per_period,
- presence: true,
- numericality: { only_integer: true, greater_than: 0 }
-
- validates :throttle_unauthenticated_period_in_seconds,
- presence: true,
- numericality: { only_integer: true, greater_than: 0 }
-
- validates :throttle_unauthenticated_packages_api_requests_per_period,
- presence: true,
- numericality: { only_integer: true, greater_than: 0 }
-
- validates :throttle_unauthenticated_packages_api_period_in_seconds,
- presence: true,
- numericality: { only_integer: true, greater_than: 0 }
-
- validates :throttle_unauthenticated_files_api_requests_per_period,
- presence: true,
- numericality: { only_integer: true, greater_than: 0 }
-
- validates :throttle_unauthenticated_files_api_period_in_seconds,
- presence: true,
- numericality: { only_integer: true, greater_than: 0 }
-
- validates :throttle_authenticated_api_requests_per_period,
- presence: true,
- numericality: { only_integer: true, greater_than: 0 }
-
- validates :throttle_authenticated_api_period_in_seconds,
- presence: true,
- numericality: { only_integer: true, greater_than: 0 }
-
- validates :throttle_authenticated_git_lfs_requests_per_period,
- presence: true,
- numericality: { only_integer: true, greater_than: 0 }
-
- validates :throttle_authenticated_git_lfs_period_in_seconds,
- presence: true,
- numericality: { only_integer: true, greater_than: 0 }
-
- validates :throttle_authenticated_web_requests_per_period,
- presence: true,
- numericality: { only_integer: true, greater_than: 0 }
-
- validates :throttle_authenticated_web_period_in_seconds,
- presence: true,
- numericality: { only_integer: true, greater_than: 0 }
-
- validates :throttle_authenticated_packages_api_requests_per_period,
- presence: true,
- numericality: { only_integer: true, greater_than: 0 }
-
- validates :throttle_authenticated_packages_api_period_in_seconds,
- presence: true,
- numericality: { only_integer: true, greater_than: 0 }
-
- validates :throttle_authenticated_files_api_requests_per_period,
- presence: true,
- numericality: { only_integer: true, greater_than: 0 }
-
- validates :throttle_authenticated_files_api_period_in_seconds,
- presence: true,
- numericality: { only_integer: true, greater_than: 0 }
-
- validates :throttle_protected_paths_requests_per_period,
- presence: true,
- numericality: { only_integer: true, greater_than: 0 }
-
- validates :throttle_protected_paths_period_in_seconds,
- presence: true,
- numericality: { only_integer: true, greater_than: 0 }
+ with_options(presence: true, numericality: { only_integer: true, greater_than: 0 }) do
+ validates :throttle_unauthenticated_api_requests_per_period
+ validates :throttle_unauthenticated_api_period_in_seconds
+ validates :throttle_unauthenticated_requests_per_period
+ validates :throttle_unauthenticated_period_in_seconds
+ validates :throttle_unauthenticated_packages_api_requests_per_period
+ validates :throttle_unauthenticated_packages_api_period_in_seconds
+ validates :throttle_unauthenticated_files_api_requests_per_period
+ validates :throttle_unauthenticated_files_api_period_in_seconds
+ validates :throttle_authenticated_api_requests_per_period
+ validates :throttle_authenticated_api_period_in_seconds
+ validates :throttle_authenticated_git_lfs_requests_per_period
+ validates :throttle_authenticated_git_lfs_period_in_seconds
+ validates :throttle_authenticated_web_requests_per_period
+ validates :throttle_authenticated_web_period_in_seconds
+ validates :throttle_authenticated_packages_api_requests_per_period
+ validates :throttle_authenticated_packages_api_period_in_seconds
+ validates :throttle_authenticated_files_api_requests_per_period
+ validates :throttle_authenticated_files_api_period_in_seconds
+ validates :throttle_protected_paths_requests_per_period
+ validates :throttle_protected_paths_period_in_seconds
+ end
validates :notes_create_limit,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
diff --git a/app/services/ci/stuck_builds/drop_helpers.rb b/app/services/ci/stuck_builds/drop_helpers.rb
new file mode 100644
index 00000000000..f79b805c23d
--- /dev/null
+++ b/app/services/ci/stuck_builds/drop_helpers.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+module Ci
+ module StuckBuilds
+ module DropHelpers
+ def drop(builds, failure_reason:)
+ fetch(builds) do |build|
+ drop_build :outdated, build, failure_reason
+ end
+ end
+
+ def drop_stuck(builds, failure_reason:)
+ fetch(builds) do |build|
+ break unless build.stuck?
+
+ drop_build :stuck, build, failure_reason
+ end
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def fetch(builds)
+ loop do
+ jobs = builds.includes(:tags, :runner, project: [:namespace, :route])
+ .limit(100)
+ .to_a
+
+ break if jobs.empty?
+
+ jobs.each do |job|
+ Gitlab::ApplicationContext.with_context(project: job.project) { yield(job) }
+ end
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def drop_build(type, build, reason)
+ Gitlab::AppLogger.info "#{self.class}: Dropping #{type} build #{build.id} for runner #{build.runner_id} (status: #{build.status}, failure_reason: #{reason})"
+ Gitlab::OptimisticLocking.retry_lock(build, 3, name: 'stuck_ci_jobs_worker_drop_build') do |b|
+ b.drop(reason)
+ end
+ rescue StandardError => ex
+ build.doom!
+
+ track_exception_for_build(ex, build)
+ end
+
+ def track_exception_for_build(ex, build)
+ Gitlab::ErrorTracking.track_exception(ex,
+ build_id: build.id,
+ build_name: build.name,
+ build_stage: build.stage,
+ pipeline_id: build.pipeline_id,
+ project_id: build.project_id
+ )
+ end
+ end
+ end
+end
diff --git a/app/services/ci/stuck_builds/drop_service.rb b/app/services/ci/stuck_builds/drop_service.rb
index fd7293e6ab9..3fee9a94381 100644
--- a/app/services/ci/stuck_builds/drop_service.rb
+++ b/app/services/ci/stuck_builds/drop_service.rb
@@ -3,6 +3,8 @@
module Ci
module StuckBuilds
class DropService
+ include DropHelpers
+
BUILD_RUNNING_OUTDATED_TIMEOUT = 1.hour
BUILD_PENDING_OUTDATED_TIMEOUT = 1.day
BUILD_SCHEDULED_OUTDATED_TIMEOUT = 1.hour
@@ -55,57 +57,6 @@ module Ci
BUILD_RUNNING_OUTDATED_TIMEOUT.ago
)
end
-
- def drop(builds, failure_reason:)
- fetch(builds) do |build|
- drop_build :outdated, build, failure_reason
- end
- end
-
- def drop_stuck(builds, failure_reason:)
- fetch(builds) do |build|
- break unless build.stuck?
-
- drop_build :stuck, build, failure_reason
- end
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def fetch(builds)
- loop do
- jobs = builds.includes(:tags, :runner, project: [:namespace, :route])
- .limit(100)
- .to_a
-
- break if jobs.empty?
-
- jobs.each do |job|
- Gitlab::ApplicationContext.with_context(project: job.project) { yield(job) }
- end
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- def drop_build(type, build, reason)
- Gitlab::AppLogger.info "#{self.class}: Dropping #{type} build #{build.id} for runner #{build.runner_id} (status: #{build.status}, failure_reason: #{reason})"
- Gitlab::OptimisticLocking.retry_lock(build, 3, name: 'stuck_ci_jobs_worker_drop_build') do |b|
- b.drop(reason)
- end
- rescue StandardError => ex
- build.doom!
-
- track_exception_for_build(ex, build)
- end
-
- def track_exception_for_build(ex, build)
- Gitlab::ErrorTracking.track_exception(ex,
- build_id: build.id,
- build_name: build.name,
- build_stage: build.stage,
- pipeline_id: build.pipeline_id,
- project_id: build.project_id
- )
- end
end
end
end
diff --git a/config/feature_flags/development/npm_finder_query_avoid_duplicated_conditions.yml b/config/feature_flags/development/repository_tree_gitaly_pagination.yml
index 4313e64e93e..afae937b62e 100644
--- a/config/feature_flags/development/npm_finder_query_avoid_duplicated_conditions.yml
+++ b/config/feature_flags/development/repository_tree_gitaly_pagination.yml
@@ -1,8 +1,8 @@
---
-name: npm_finder_query_avoid_duplicated_conditions
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69572
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/340099
+name: repository_tree_gitaly_pagination
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67509
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/340419
milestone: '14.3'
type: development
-group: group::package
+group: group::source code
default_enabled: false
diff --git a/data/deprecations/templates/_deprecation_template.md.erb b/data/deprecations/templates/_deprecation_template.md.erb
index 1a77689080e..b6f532147fd 100644
--- a/data/deprecations/templates/_deprecation_template.md.erb
+++ b/data/deprecations/templates/_deprecation_template.md.erb
@@ -15,7 +15,7 @@ Do not edit this page directly.
To add a deprecation, use the example.yml file in `/data/deprecations/templates` as a template,
then run `bin/rake gitlab:docs:compile_deprecations`.
-->
-<% if milestones.any? %>
+<% if milestones.any? -%>
<%- milestones.each do |milestone| %>
## <%= milestone %>
<%- deprecations.select{|d| d["removal_milestone"] == milestone}.each do |deprecation| %>
diff --git a/doc/ci/examples/index.md b/doc/ci/examples/index.md
index bee480917e5..2c2c6ecd30f 100644
--- a/doc/ci/examples/index.md
+++ b/doc/ci/examples/index.md
@@ -121,6 +121,7 @@ For examples of setting up GitLab CI/CD for cloud-based environments, see:
- [How to autoscale continuous deployment with GitLab Runner on DigitalOcean](https://about.gitlab.com/blog/2018/06/19/autoscale-continuous-deployment-gitlab-runner-digital-ocean/)
- [How to create a CI/CD pipeline with Auto Deploy to Kubernetes using GitLab and Helm](https://about.gitlab.com/blog/2017/09/21/how-to-create-ci-cd-pipeline-with-autodeploy-to-kubernetes-using-gitlab-and-helm/)
- Video: [Demo - Deploying from GitLab to OpenShift Container Cluster](https://youtu.be/EwbhA53Jpp4)
+- Tutorial: [Set up a GitLab.com Civo Kubernetes integration with GitPod](https://gitlab.com/k33g_org/k33g_org.gitlab.io/-/issues/82)
See also the following video overviews:
diff --git a/doc/development/database/efficient_in_operator_queries.md b/doc/development/database/efficient_in_operator_queries.md
index 207fa6c3832..bc72bce30bf 100644
--- a/doc/development/database/efficient_in_operator_queries.md
+++ b/doc/development/database/efficient_in_operator_queries.md
@@ -463,7 +463,7 @@ Gitlab::Pagination::Keyset::InOperatorOptimization::QueryBuilder.new(
<details>
<summary>Expand this sentence to see the SQL query.</summary>
-<pre><code lang='sql'>
+<pre><code>
SELECT "issues".*
FROM
(WITH RECURSIVE "array_cte" AS MATERIALIZED
@@ -582,6 +582,7 @@ FROM
WHERE (COUNT <> 0)) issues
LIMIT 20
</code>
+</pre>
</details>
NOTE:
@@ -613,7 +614,7 @@ end
#### Keyset pagination
The optimization works out of the box with GraphQL and the `keyset_paginate` helper method.
-Read more about [keyset pagination](database/keyset_pagination.md).
+Read more about [keyset pagination](keyset_pagination.md).
```ruby
array_scope = Group.find(9970).all_projects.select(:id)
@@ -637,7 +638,7 @@ issues = Issue
#### Offset pagination with Kaminari
The `ActiveRecord` scope produced by the `InOperatorOptimization` class can be used in
-[offset-paginated](database/pagination_guidelines.md#offset-pagination)
+[offset-paginated](pagination_guidelines.md#offset-pagination)
queries.
```ruby
diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md
index 12f0c61a3fb..a1c2bcdbdd7 100644
--- a/doc/development/documentation/styleguide/index.md
+++ b/doc/development/documentation/styleguide/index.md
@@ -1081,10 +1081,15 @@ To select your avatar:
## Images
Images, including screenshots, can help a reader better understand a concept.
-However, they can be hard to maintain, and should be used sparingly.
+However, they should be used sparingly because:
-Before including an image in the documentation, ensure it provides value to the
-reader.
+- They tend to become out-of-date.
+- They are difficult and expensive to localize.
+- They cannot be read by screen readers.
+
+If you do include an image in the documentation, ensure it provides value.
+Don't use `lorem ipsum` text. Try to replicate how the feature would be
+used in a real-world scenario, and [use realistic text](#fake-user-information).
### Capture the image
diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md
index b718fd26d45..08d67b9614f 100644
--- a/doc/development/documentation/styleguide/word_list.md
+++ b/doc/development/documentation/styleguide/word_list.md
@@ -160,14 +160,14 @@ Do not use **Developer permissions**. A user who is assigned the Developer role
See [the Microsoft style guide](https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/d/disable-disabled) for guidance on **disable**.
Use **inactive** or **off** instead. ([Vale](../testing.md#vale) rule: [`InclusionAbleism.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/InclusionAbleism.yml))
-## dropdown, dropdown list
+## dropdown list
-Do not use. Use **list** instead.
+Use **dropdown list** to refer to the UI element. Do not use **dropdown** without **list** after it.
+Do not use **drop-down** (hyphenated), **dropdown menu**, or other variants.
-Include the descriptor when writing about lists. Start with the list name,
-then follow with the item the user should select. For example:
+For example:
-- From the **Visibility** list, select **Public**.
+- From the **Visibility** dropdown list, select **Public**.
## earlier
@@ -331,11 +331,8 @@ Use **later** when talking about version numbers.
## list
-Use instead of **dropdown**, **drop-down** or **dropdown list**. You select an item from a list. For example:
-
-- From the **Availability** list, select **public**.
-
-The list name, and the items you select, should be bold.
+Do not use **list** when referring to a [**dropdown list**](#dropdown-list).
+Use the full phrase **dropdown list** instead.
## log in, log on
diff --git a/doc/user/admin_area/analytics/dev_ops_report.md b/doc/user/admin_area/analytics/dev_ops_report.md
index a4c2ea043cc..9ce012366ef 100644
--- a/doc/user/admin_area/analytics/dev_ops_report.md
+++ b/doc/user/admin_area/analytics/dev_ops_report.md
@@ -45,6 +45,7 @@ feature is available.
> - Fuzz Testing metrics [added](https://gitlab.com/gitlab-org/gitlab/-/issues/330398) in GitLab 14.2.
> - Dependency Scanning metrics [added](https://gitlab.com/gitlab-org/gitlab/-/issues/328034) in GitLab 14.2.
> - Multi-select [added](https://gitlab.com/gitlab-org/gitlab/-/issues/333586) in GitLab 14.2.
+> - Overview table [added](https://gitlab.com/gitlab-org/gitlab/-/issues/335638) in GitLab 14.3.
DevOps Adoption shows you which groups in your organization are using the most essential features of GitLab:
diff --git a/doc/user/clusters/management_project_template.md b/doc/user/clusters/management_project_template.md
index b00fa873b4b..e10d45264f9 100644
--- a/doc/user/clusters/management_project_template.md
+++ b/doc/user/clusters/management_project_template.md
@@ -62,7 +62,7 @@ the pipeline runs, Helmfile tries to either install or update your apps accordin
cluster and Helm releases. If you change this attribute to `installed: false`, Helmfile tries try to uninstall this app
from your cluster. [Read more](https://github.com/roboll/helmfile) about how Helmfile works.
-Furthermore, each app has an `applications/{app}/values.yaml` file. This is the
+Furthermore, each app has an `applications/{app}/values.yaml` file (`applicaton/{app}/values.yaml.gotmpl` in case of GitLab Runner). This is the
place where you can define some default values for your app's Helm chart. Some apps already have defaults
pre-defined by GitLab.
diff --git a/doc/user/group/devops_adoption/index.md b/doc/user/group/devops_adoption/index.md
index 5c84a343da9..554d01039ad 100644
--- a/doc/user/group/devops_adoption/index.md
+++ b/doc/user/group/devops_adoption/index.md
@@ -13,6 +13,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - Fuzz Testing metrics [added](https://gitlab.com/gitlab-org/gitlab/-/issues/330398) in GitLab 14.2.
> - Dependency Scanning metrics [added](https://gitlab.com/gitlab-org/gitlab/-/issues/328034) in GitLab 14.2.
> - Multiselect [added](https://gitlab.com/gitlab-org/gitlab/-/issues/333586) in GitLab 14.2.
+> - Overview table [added](https://gitlab.com/gitlab-org/gitlab/-/issues/335638) in GitLab 14.3.
Prerequisites:
diff --git a/lib/api/helpers/packages/npm.rb b/lib/api/helpers/packages/npm.rb
index 4a7827c1d25..34e126c73fc 100644
--- a/lib/api/helpers/packages/npm.rb
+++ b/lib/api/helpers/packages/npm.rb
@@ -60,7 +60,7 @@ module API
finder = ::Packages::Npm::PackageFinder.new(
package_name,
namespace: namespace,
- last_of_each_version: Feature.disabled?(:npm_finder_query_avoid_duplicated_conditions)
+ last_of_each_version: false
)
finder.last&.project_id
diff --git a/lib/api/npm_project_packages.rb b/lib/api/npm_project_packages.rb
index 7ff4439ce04..dbfc0a61577 100644
--- a/lib/api/npm_project_packages.rb
+++ b/lib/api/npm_project_packages.rb
@@ -48,14 +48,13 @@ module API
put ':package_name', requirements: ::API::Helpers::Packages::Npm::NPM_ENDPOINT_REQUIREMENTS do
authorize_create_package!(project)
- track_package_event('push_package', :npm, category: 'API::NpmPackages', project: project, user: current_user, namespace: project.namespace)
-
created_package = ::Packages::Npm::CreatePackageService
.new(project, current_user, params.merge(build: current_authenticated_job)).execute
if created_package[:status] == :error
render_api_error!(created_package[:message], created_package[:http_status])
else
+ track_package_event('push_package', :npm, category: 'API::NpmPackages', project: project, user: current_user, namespace: project.namespace)
created_package
end
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 20320d1b7ae..3c9255e3117 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -51,18 +51,22 @@ module API
optional :ref, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used'
optional :path, type: String, desc: 'The path of the tree'
optional :recursive, type: Boolean, default: false, desc: 'Used to get a recursive tree'
+
use :pagination
+ optional :pagination, type: String, values: %w(legacy keyset), default: 'legacy', desc: 'Specify the pagination method'
+
+ given pagination: -> (value) { value == 'keyset' } do
+ optional :page_token, type: String, desc: 'Record from which to start the keyset pagination'
+ end
end
get ':id/repository/tree' do
- ref = params[:ref] || user_project.default_branch
- path = params[:path] || nil
+ tree_finder = ::Repositories::TreeFinder.new(user_project, declared_params(include_missing: false))
+
+ not_found!("Tree") unless tree_finder.commit_exists?
- commit = user_project.commit(ref)
- not_found!('Tree') unless commit
+ tree = Gitlab::Pagination::GitalyKeysetPager.new(self, user_project).paginate(tree_finder)
- tree = user_project.repository.tree(commit.id, path, recursive: params[:recursive])
- entries = ::Kaminari.paginate_array(tree.sorted_entries)
- present paginate(entries), with: Entities::TreeObject
+ present tree, with: Entities::TreeObject
end
desc 'Get raw blob contents from the repository'
diff --git a/lib/gitlab/cycle_analytics/summary/base.rb b/lib/gitlab/cycle_analytics/summary/base.rb
index 50a8f189df0..e30e526f017 100644
--- a/lib/gitlab/cycle_analytics/summary/base.rb
+++ b/lib/gitlab/cycle_analytics/summary/base.rb
@@ -16,6 +16,10 @@ module Gitlab
def value
raise NotImplementedError, "Expected #{self.name} to implement value"
end
+
+ private
+
+ attr_reader :project, :options
end
end
end
diff --git a/lib/gitlab/cycle_analytics/summary/deploy.rb b/lib/gitlab/cycle_analytics/summary/deploy.rb
index ea16226a865..403cec5ed19 100644
--- a/lib/gitlab/cycle_analytics/summary/deploy.rb
+++ b/lib/gitlab/cycle_analytics/summary/deploy.rb
@@ -24,3 +24,5 @@ module Gitlab
end
end
end
+
+Gitlab::CycleAnalytics::Summary::Deploy.prepend_mod_with('Gitlab::CycleAnalytics::Summary::Deploy')
diff --git a/lib/gitlab/database/load_balancing/load_balancer.rb b/lib/gitlab/database/load_balancing/load_balancer.rb
index 0fb8faae8ce..9b00b323301 100644
--- a/lib/gitlab/database/load_balancing/load_balancer.rb
+++ b/lib/gitlab/database/load_balancing/load_balancer.rb
@@ -245,7 +245,7 @@ module Gitlab
end
def request_cache
- base = RequestStore[:gitlab_load_balancer] ||= {}
+ base = SafeRequestStore[:gitlab_load_balancer] ||= {}
base[self] ||= {}
end
end
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index ddf04bd8e79..75588ad980c 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -127,6 +127,7 @@ module Gitlab
entries = response.flat_map do |message|
cursor = message.pagination_cursor if message.pagination_cursor
+
message.entries.map do |gitaly_tree_entry|
Gitlab::Git::Tree.new(
id: gitaly_tree_entry.oid,
diff --git a/lib/gitlab/pagination/gitaly_keyset_pager.rb b/lib/gitlab/pagination/gitaly_keyset_pager.rb
index b05891066ac..a16bf7a379c 100644
--- a/lib/gitlab/pagination/gitaly_keyset_pager.rb
+++ b/lib/gitlab/pagination/gitaly_keyset_pager.rb
@@ -14,23 +14,39 @@ module Gitlab
# It is expected that the given finder will respond to `execute` method with `gitaly_pagination: true` option
# and supports pagination via gitaly.
def paginate(finder)
- return paginate_via_gitaly(finder) if keyset_pagination_enabled?
- return paginate_first_page_via_gitaly(finder) if paginate_first_page?
+ return paginate_via_gitaly(finder) if keyset_pagination_enabled?(finder)
+ return paginate_first_page_via_gitaly(finder) if paginate_first_page?(finder)
- branches = ::Kaminari.paginate_array(finder.execute)
+ records = ::Kaminari.paginate_array(finder.execute)
Gitlab::Pagination::OffsetPagination
.new(request_context)
- .paginate(branches)
+ .paginate(records)
end
private
- def keyset_pagination_enabled?
- Feature.enabled?(:branch_list_keyset_pagination, project, default_enabled: :yaml) && params[:pagination] == 'keyset'
+ def keyset_pagination_enabled?(finder)
+ return false unless params[:pagination] == "keyset"
+
+ if finder.is_a?(BranchesFinder)
+ Feature.enabled?(:branch_list_keyset_pagination, project, default_enabled: :yaml)
+ elsif finder.is_a?(::Repositories::TreeFinder)
+ Feature.enabled?(:repository_tree_gitaly_pagination, project, default_enabled: :yaml)
+ else
+ false
+ end
end
- def paginate_first_page?
- Feature.enabled?(:branch_list_keyset_pagination, project, default_enabled: :yaml) && (params[:page].blank? || params[:page].to_i == 1)
+ def paginate_first_page?(finder)
+ return false unless params[:page].blank? || params[:page].to_i == 1
+
+ if finder.is_a?(BranchesFinder)
+ Feature.enabled?(:branch_list_keyset_pagination, project, default_enabled: :yaml)
+ elsif finder.is_a?(::Repositories::TreeFinder)
+ Feature.enabled?(:repository_tree_gitaly_pagination, project, default_enabled: :yaml)
+ else
+ false
+ end
end
def paginate_via_gitaly(finder)
@@ -43,7 +59,7 @@ module Gitlab
# Headers are added to immitate offset pagination, while it is the default option
def paginate_first_page_via_gitaly(finder)
finder.execute(gitaly_pagination: true).tap do |records|
- total = project.repository.branch_count
+ total = finder.total
per_page = params[:per_page].presence || Kaminari.config.default_per_page
Gitlab::Pagination::OffsetHeaderBuilder.new(
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 4f4e2d4122e..b99b4c66722 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -9240,6 +9240,9 @@ msgstr ""
msgid "Copy value"
msgstr ""
+msgid "Corpus Management"
+msgstr ""
+
msgid "Corpus Management|Are you sure you want to delete the corpus?"
msgstr ""
@@ -29782,6 +29785,12 @@ msgstr ""
msgid "SecurityConfiguration|Immediately begin risk analysis and remediation with application security features. Start with SAST and Secret Detection, available to all plans. Upgrade to Ultimate to get all features, including:"
msgstr ""
+msgid "SecurityConfiguration|Manage corpus"
+msgstr ""
+
+msgid "SecurityConfiguration|Manage corpus files used as mutation sources in coverage fuzzing."
+msgstr ""
+
msgid "SecurityConfiguration|Manage profiles for use by DAST scans."
msgstr ""
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index f3500301e22..1108c606df3 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -534,6 +534,14 @@ FactoryBot.define do
end
end
+ trait :coverage_fuzzing do
+ options do
+ {
+ artifacts: { reports: { coverage_fuzzing: 'gl-coverage-fuzzing-report.json' } }
+ }
+ end
+ end
+
trait :license_scanning do
options do
{
diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb
index a62dd3842db..f9d525c33a4 100644
--- a/spec/finders/branches_finder_spec.rb
+++ b/spec/finders/branches_finder_spec.rb
@@ -259,4 +259,11 @@ RSpec.describe BranchesFinder do
end
end
end
+
+ describe '#total' do
+ subject { branch_finder.total }
+
+ it { is_expected.to be_an(Integer) }
+ it { is_expected.to eq(repository.branch_count) }
+ end
end
diff --git a/spec/finders/repositories/tree_finder_spec.rb b/spec/finders/repositories/tree_finder_spec.rb
new file mode 100644
index 00000000000..0d70d5f92d3
--- /dev/null
+++ b/spec/finders/repositories/tree_finder_spec.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Repositories::TreeFinder do
+ include RepoHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :repository, creator: user) }
+
+ let(:repository) { project.repository }
+ let(:tree_finder) { described_class.new(project, params) }
+ let(:params) { {} }
+ let(:first_page_ids) { tree_finder.execute.map(&:id) }
+ let(:second_page_token) { first_page_ids.last }
+
+ describe "#execute" do
+ subject { tree_finder.execute(gitaly_pagination: true) }
+
+ it "returns an array" do
+ is_expected.to be_an(Array)
+ end
+
+ it "includes 20 items by default" do
+ expect(subject.size).to eq(20)
+ end
+
+ it "accepts a gitaly_pagination argument" do
+ expect(repository).to receive(:tree).with(anything, anything, recursive: nil, pagination_params: { limit: 20, page_token: nil }).and_call_original
+ expect(tree_finder.execute(gitaly_pagination: true)).to be_an(Array)
+
+ expect(repository).to receive(:tree).with(anything, anything, recursive: nil).and_call_original
+ expect(tree_finder.execute(gitaly_pagination: false)).to be_an(Array)
+ end
+
+ context "commit doesn't exist" do
+ let(:params) do
+ { ref: "nonesuchref" }
+ end
+
+ it "raises an error" do
+ expect { subject }.to raise_error(described_class::CommitMissingError)
+ end
+ end
+
+ describe "pagination_params" do
+ let(:params) do
+ { per_page: 5, page_token: nil }
+ end
+
+ it "has the per_page number of items" do
+ expect(subject.size).to eq(5)
+ end
+
+ it "doesn't include any of the first page records" do
+ first_page_ids = subject.map(&:id)
+ second_page = described_class.new(project, { per_page: 5, page_token: first_page_ids.last }).execute(gitaly_pagination: true)
+
+ expect(second_page.map(&:id)).not_to include(*first_page_ids)
+ end
+ end
+ end
+
+ describe "#total", :use_clean_rails_memory_store_caching do
+ subject { tree_finder.total }
+
+ it { is_expected.to be_an(Integer) }
+
+ it "only calculates the total once" do
+ expect(repository).to receive(:tree).once.and_call_original
+
+ 2.times { tree_finder.total }
+ end
+ end
+
+ describe "#commit_exists?" do
+ subject { tree_finder.commit_exists? }
+
+ context "ref exists" do
+ let(:params) do
+ { ref: project.default_branch }
+ end
+
+ it { is_expected.to be(true) }
+ end
+
+ context "ref is missing" do
+ let(:params) do
+ { ref: "nonesuchref" }
+ end
+
+ it { is_expected.to be(false) }
+ end
+ end
+end
diff --git a/spec/frontend/batch_comments/components/review_bar_spec.js b/spec/frontend/batch_comments/components/review_bar_spec.js
new file mode 100644
index 00000000000..f50db6ab210
--- /dev/null
+++ b/spec/frontend/batch_comments/components/review_bar_spec.js
@@ -0,0 +1,42 @@
+import { shallowMount } from '@vue/test-utils';
+import ReviewBar from '~/batch_comments/components/review_bar.vue';
+import { REVIEW_BAR_VISIBLE_CLASS_NAME } from '~/batch_comments/constants';
+import createStore from '../create_batch_comments_store';
+
+describe('Batch comments review bar component', () => {
+ let store;
+ let wrapper;
+
+ const createComponent = (propsData = {}) => {
+ store = createStore();
+
+ wrapper = shallowMount(ReviewBar, {
+ store,
+ propsData,
+ });
+ };
+
+ beforeEach(() => {
+ document.body.className = '';
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('it adds review-bar-visible class to body when review bar is mounted', async () => {
+ expect(document.body.classList.contains(REVIEW_BAR_VISIBLE_CLASS_NAME)).toBe(false);
+
+ createComponent();
+
+ expect(document.body.classList.contains(REVIEW_BAR_VISIBLE_CLASS_NAME)).toBe(true);
+ });
+
+ it('it removes review-bar-visible class to body when review bar is destroyed', async () => {
+ createComponent();
+
+ wrapper.destroy();
+
+ expect(document.body.classList.contains(REVIEW_BAR_VISIBLE_CLASS_NAME)).toBe(false);
+ });
+});
diff --git a/spec/frontend/batch_comments/create_batch_comments_store.js b/spec/frontend/batch_comments/create_batch_comments_store.js
new file mode 100644
index 00000000000..10dc6fe196e
--- /dev/null
+++ b/spec/frontend/batch_comments/create_batch_comments_store.js
@@ -0,0 +1,15 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import batchCommentsModule from '~/batch_comments/stores/modules/batch_comments';
+import notesModule from '~/notes/stores/modules';
+
+Vue.use(Vuex);
+
+export default function createDiffsStore() {
+ return new Vuex.Store({
+ modules: {
+ notes: notesModule(),
+ batchComments: batchCommentsModule(),
+ },
+ });
+}
diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js
index 1a9e182ee2b..9dc82bbdc93 100644
--- a/spec/frontend/diffs/components/app_spec.js
+++ b/spec/frontend/diffs/components/app_spec.js
@@ -705,24 +705,4 @@ describe('diffs/components/app', () => {
);
});
});
-
- describe('diff file tree is aware of review bar', () => {
- it('it does not have review-bar-visible class when review bar is not visible', () => {
- createComponent({}, ({ state }) => {
- state.diffs.diffFiles = [{ file_hash: '111', file_path: '111.js' }];
- });
-
- expect(wrapper.find('.js-diff-tree-list').exists()).toBe(true);
- expect(wrapper.find('.js-diff-tree-list.review-bar-visible').exists()).toBe(false);
- });
-
- it('it does have review-bar-visible class when review bar is visible', () => {
- createComponent({}, ({ state }) => {
- state.diffs.diffFiles = [{ file_hash: '111', file_path: '111.js' }];
- state.batchComments.drafts = ['draft message'];
- });
-
- expect(wrapper.find('.js-diff-tree-list.review-bar-visible').exists()).toBe(true);
- });
- });
});
diff --git a/spec/frontend/lib/logger/__snapshots__/hello_spec.js.snap b/spec/frontend/lib/logger/__snapshots__/hello_spec.js.snap
new file mode 100644
index 00000000000..791ec05befd
--- /dev/null
+++ b/spec/frontend/lib/logger/__snapshots__/hello_spec.js.snap
@@ -0,0 +1,16 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`~/lib/logger/hello logHello console logs a friendly hello message 1`] = `
+Array [
+ Array [
+ "%cWelcome to GitLab!%c
+
+Does this page need fixes or improvements? Open an issue or contribute a merge request to help make GitLab more lovable. At GitLab, everyone can contribute!
+
+🤝 Contribute to GitLab: https://about.gitlab.com/community/contribute/
+🔎 Create a new GitLab issue: https://gitlab.com/gitlab-org/gitlab/-/issues/new",
+ "padding-top: 0.5em; font-size: 2em;",
+ "padding-bottom: 0.5em;",
+ ],
+]
+`;
diff --git a/spec/frontend/lib/logger/hello_deferred_spec.js b/spec/frontend/lib/logger/hello_deferred_spec.js
new file mode 100644
index 00000000000..3233cbff0dc
--- /dev/null
+++ b/spec/frontend/lib/logger/hello_deferred_spec.js
@@ -0,0 +1,17 @@
+import waitForPromises from 'helpers/wait_for_promises';
+import { logHello } from '~/lib/logger/hello';
+import { logHelloDeferred } from '~/lib/logger/hello_deferred';
+
+jest.mock('~/lib/logger/hello');
+
+describe('~/lib/logger/hello_deferred', () => {
+ it('dynamically imports and calls logHello', async () => {
+ logHelloDeferred();
+
+ expect(logHello).not.toHaveBeenCalled();
+
+ await waitForPromises();
+
+ expect(logHello).toHaveBeenCalled();
+ });
+});
diff --git a/spec/frontend/lib/logger/hello_spec.js b/spec/frontend/lib/logger/hello_spec.js
new file mode 100644
index 00000000000..39abe0e0dd0
--- /dev/null
+++ b/spec/frontend/lib/logger/hello_spec.js
@@ -0,0 +1,20 @@
+import { logHello } from '~/lib/logger/hello';
+
+describe('~/lib/logger/hello', () => {
+ let consoleLogSpy;
+
+ beforeEach(() => {
+ // We don't `mockImplementation` so we can validate there's no errors thrown
+ consoleLogSpy = jest.spyOn(console, 'log');
+ });
+
+ describe('logHello', () => {
+ it('console logs a friendly hello message', () => {
+ expect(consoleLogSpy).not.toHaveBeenCalled();
+
+ logHello();
+
+ expect(consoleLogSpy.mock.calls).toMatchSnapshot();
+ });
+ });
+});
diff --git a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
index b9e0132badb..8053f5261c0 100644
--- a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
@@ -3,7 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::CycleAnalytics::StageSummary do
- let(:project) { create(:project, :repository) }
+ let_it_be(:project) { create(:project, :repository) }
+
let(:options) { { from: 1.day.ago } }
let(:args) { { options: options, current_user: user } }
let(:user) { create(:user, :admin) }
@@ -62,6 +63,8 @@ RSpec.describe Gitlab::CycleAnalytics::StageSummary do
end
describe "#commits" do
+ let!(:project) { create(:project, :repository) }
+
subject { stage_summary.second }
context 'when from date is given' do
@@ -132,115 +135,5 @@ RSpec.describe Gitlab::CycleAnalytics::StageSummary do
end
end
- describe "#deploys" do
- subject { stage_summary.third }
-
- context 'when from date is given' do
- before do
- Timecop.freeze(5.days.ago) { create(:deployment, :success, project: project) }
- Timecop.freeze(5.days.from_now) { create(:deployment, :success, project: project) }
- end
-
- it "finds the number of deploys made created after the 'from date'" do
- expect(subject[:value]).to eq('1')
- end
-
- it 'returns the localized title' do
- Gitlab::I18n.with_locale(:ru) do
- expect(subject[:title]).to eq(n_('Deploy', 'Deploys', 1))
- end
- end
- end
-
- it "doesn't find commits from other projects" do
- Timecop.freeze(5.days.from_now) do
- create(:deployment, :success, project: create(:project, :repository))
- end
-
- expect(subject[:value]).to eq('-')
- end
-
- context 'when `to` parameter is given' do
- before do
- Timecop.freeze(5.days.ago) { create(:deployment, :success, project: project) }
- Timecop.freeze(5.days.from_now) { create(:deployment, :success, project: project) }
- end
-
- it "doesn't find any record" do
- options[:to] = Time.now
-
- expect(subject[:value]).to eq('-')
- end
-
- it "finds records created between `from` and `to` range" do
- options[:from] = 10.days.ago
- options[:to] = 10.days.from_now
-
- expect(subject[:value]).to eq('2')
- end
- end
- end
-
- describe '#deployment_frequency' do
- subject { stage_summary.fourth[:value] }
-
- it 'includes the unit: `per day`' do
- expect(stage_summary.fourth[:unit]).to eq _('per day')
- end
-
- before do
- Timecop.freeze(5.days.ago) { create(:deployment, :success, project: project) }
- end
-
- it 'returns 0.0 when there were deploys but the frequency was too low' do
- options[:from] = 30.days.ago
-
- # 1 deployment over 30 days
- # frequency of 0.03, rounded off to 0.0
- expect(subject).to eq('0')
- end
-
- it 'returns `-` when there were no deploys' do
- options[:from] = 4.days.ago
-
- # 0 deployment in the last 4 days
- expect(subject).to eq('-')
- end
-
- context 'when `to` is nil' do
- it 'includes range until now' do
- options[:from] = 6.days.ago
- options[:to] = nil
-
- # 1 deployment over 7 days
- expect(subject).to eq('0.1')
- end
- end
-
- context 'when `to` is given' do
- before do
- Timecop.freeze(5.days.from_now) { create(:deployment, :success, project: project, finished_at: Time.zone.now) }
- end
-
- it 'finds records created between `from` and `to` range' do
- options[:from] = 10.days.ago
- options[:to] = 10.days.from_now
-
- # 2 deployments over 20 days
- expect(subject).to eq('0.1')
- end
-
- context 'when `from` and `to` are within a day' do
- it 'returns the number of deployments made on that day' do
- freeze_time do
- create(:deployment, :success, project: project, finished_at: Time.zone.now)
- options[:from] = Time.zone.now.at_beginning_of_day
- options[:to] = Time.zone.now.at_end_of_day
-
- expect(subject).to eq('1')
- end
- end
- end
- end
- end
+ it_behaves_like 'deployment metrics examples'
end
diff --git a/spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb b/spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb
index 8a26e153385..dcb8138bdde 100644
--- a/spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb
+++ b/spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb
@@ -74,7 +74,7 @@ RSpec.describe Gitlab::Pagination::GitalyKeysetPager do
allow(request_context).to receive(:request).and_return(fake_request)
allow(project.repository).to receive(:branch_count).and_return(branches.size)
- expect(finder).to receive(:execute).with(gitaly_pagination: true).and_return(branches)
+ expect(finder).to receive(:execute).and_return(branches)
expect(request_context).to receive(:header).with('X-Per-Page', '2')
expect(request_context).to receive(:header).with('X-Page', '1')
expect(request_context).to receive(:header).with('X-Next-Page', '2')
@@ -99,6 +99,7 @@ RSpec.describe Gitlab::Pagination::GitalyKeysetPager do
before do
allow(request_context).to receive(:request).and_return(fake_request)
+ allow(finder).to receive(:is_a?).with(BranchesFinder) { true }
expect(finder).to receive(:execute).with(gitaly_pagination: true).and_return(branches)
end
diff --git a/spec/requests/api/npm_instance_packages_spec.rb b/spec/requests/api/npm_instance_packages_spec.rb
index 90b39791bf5..698885ddcf4 100644
--- a/spec/requests/api/npm_instance_packages_spec.rb
+++ b/spec/requests/api/npm_instance_packages_spec.rb
@@ -10,39 +10,27 @@ RSpec.describe API::NpmInstancePackages do
include_context 'npm api setup'
- shared_examples 'handling all endpoints' do
- describe 'GET /api/v4/packages/npm/*package_name' do
- it_behaves_like 'handling get metadata requests', scope: :instance do
- let(:url) { api("/packages/npm/#{package_name}") }
- end
- end
-
- describe 'GET /api/v4/packages/npm/-/package/*package_name/dist-tags' do
- it_behaves_like 'handling get dist tags requests', scope: :instance do
- let(:url) { api("/packages/npm/-/package/#{package_name}/dist-tags") }
- end
+ describe 'GET /api/v4/packages/npm/*package_name' do
+ it_behaves_like 'handling get metadata requests', scope: :instance do
+ let(:url) { api("/packages/npm/#{package_name}") }
end
+ end
- describe 'PUT /api/v4/packages/npm/-/package/*package_name/dist-tags/:tag' do
- it_behaves_like 'handling create dist tag requests', scope: :instance do
- let(:url) { api("/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") }
- end
+ describe 'GET /api/v4/packages/npm/-/package/*package_name/dist-tags' do
+ it_behaves_like 'handling get dist tags requests', scope: :instance do
+ let(:url) { api("/packages/npm/-/package/#{package_name}/dist-tags") }
end
+ end
- describe 'DELETE /api/v4/packages/npm/-/package/*package_name/dist-tags/:tag' do
- it_behaves_like 'handling delete dist tag requests', scope: :instance do
- let(:url) { api("/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") }
- end
+ describe 'PUT /api/v4/packages/npm/-/package/*package_name/dist-tags/:tag' do
+ it_behaves_like 'handling create dist tag requests', scope: :instance do
+ let(:url) { api("/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") }
end
end
- it_behaves_like 'handling all endpoints'
-
- context 'with npm_finder_query_avoid_duplicated_conditions disabled' do
- before do
- stub_feature_flags(npm_finder_query_avoid_duplicated_conditions: false)
+ describe 'DELETE /api/v4/packages/npm/-/package/*package_name/dist-tags/:tag' do
+ it_behaves_like 'handling delete dist tag requests', scope: :instance do
+ let(:url) { api("/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") }
end
-
- it_behaves_like 'handling all endpoints'
end
end
diff --git a/spec/requests/api/npm_project_packages_spec.rb b/spec/requests/api/npm_project_packages_spec.rb
index 8c35a1642e2..0d04c2cad5b 100644
--- a/spec/requests/api/npm_project_packages_spec.rb
+++ b/spec/requests/api/npm_project_packages_spec.rb
@@ -120,9 +120,11 @@ RSpec.describe API::NpmProjectPackages do
project.add_developer(user)
end
+ subject(:upload_package_with_token) { upload_with_token(package_name, params) }
+
shared_examples 'handling invalid record with 400 error' do
it 'handles an ActiveRecord::RecordInvalid exception with 400 error' do
- expect { upload_package_with_token(package_name, params) }
+ expect { upload_package_with_token }
.not_to change { project.packages.count }
expect(response).to have_gitlab_http_status(:bad_request)
@@ -136,6 +138,7 @@ RSpec.describe API::NpmProjectPackages do
let(:params) { upload_params(package_name: package_name) }
it_behaves_like 'handling invalid record with 400 error'
+ it_behaves_like 'not a package tracking event'
end
context 'invalid package version' do
@@ -157,6 +160,7 @@ RSpec.describe API::NpmProjectPackages do
let(:params) { upload_params(package_name: package_name, package_version: version) }
it_behaves_like 'handling invalid record with 400 error'
+ it_behaves_like 'not a package tracking event'
end
end
end
@@ -169,8 +173,6 @@ RSpec.describe API::NpmProjectPackages do
shared_examples 'handling upload with different authentications' do
context 'with access token' do
- subject { upload_package_with_token(package_name, params) }
-
it_behaves_like 'a package tracking event', 'API::NpmPackages', 'push_package'
it 'creates npm package with file' do
@@ -184,7 +186,7 @@ RSpec.describe API::NpmProjectPackages do
end
it 'creates npm package with file with job token' do
- expect { upload_package_with_job_token(package_name, params) }
+ expect { upload_with_job_token(package_name, params) }
.to change { project.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1)
@@ -205,7 +207,7 @@ RSpec.describe API::NpmProjectPackages do
end
it 'creates the package metadata' do
- upload_package_with_token(package_name, params)
+ upload_package_with_token
expect(response).to have_gitlab_http_status(:ok)
expect(project.reload.packages.find(json_response['id']).original_build_info.pipeline).to eq job.pipeline
@@ -215,7 +217,7 @@ RSpec.describe API::NpmProjectPackages do
shared_examples 'uploading the package' do
it 'uploads the package' do
- expect { upload_package_with_token(package_name, params) }
+ expect { upload_package_with_token }
.to change { project.packages.count }.by(1)
expect(response).to have_gitlab_http_status(:ok)
@@ -249,6 +251,7 @@ RSpec.describe API::NpmProjectPackages do
let(:package_name) { "@#{group.path}/test" }
it_behaves_like 'handling invalid record with 400 error'
+ it_behaves_like 'not a package tracking event'
context 'with a new version' do
let_it_be(:version) { '4.5.6' }
@@ -271,9 +274,14 @@ RSpec.describe API::NpmProjectPackages do
let(:package_name) { "@#{group.path}/my_package_name" }
let(:params) { upload_params(package_name: package_name) }
- it 'returns an error if the package already exists' do
+ before do
create(:npm_package, project: project, version: '1.0.1', name: "@#{group.path}/my_package_name")
- expect { upload_package_with_token(package_name, params) }
+ end
+
+ it_behaves_like 'not a package tracking event'
+
+ it 'returns an error if the package already exists' do
+ expect { upload_package_with_token }
.not_to change { project.packages.count }
expect(response).to have_gitlab_http_status(:forbidden)
@@ -285,7 +293,7 @@ RSpec.describe API::NpmProjectPackages do
let(:params) { upload_params(package_name: package_name, file: 'npm/payload_with_duplicated_packages.json') }
it 'creates npm package with file and dependencies' do
- expect { upload_package_with_token(package_name, params) }
+ expect { upload_package_with_token }
.to change { project.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1)
.and change { Packages::Dependency.count}.by(4)
@@ -297,11 +305,11 @@ RSpec.describe API::NpmProjectPackages do
context 'with existing dependencies' do
before do
name = "@#{group.path}/existing_package"
- upload_package_with_token(name, upload_params(package_name: name, file: 'npm/payload_with_duplicated_packages.json'))
+ upload_with_token(name, upload_params(package_name: name, file: 'npm/payload_with_duplicated_packages.json'))
end
it 'reuses them' do
- expect { upload_package_with_token(package_name, params) }
+ expect { upload_package_with_token }
.to change { project.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1)
.and not_change { Packages::Dependency.count}
@@ -317,11 +325,11 @@ RSpec.describe API::NpmProjectPackages do
put api("/projects/#{project.id}/packages/npm/#{package_name.sub('/', '%2f')}"), params: params, headers: headers
end
- def upload_package_with_token(package_name, params = {})
+ def upload_with_token(package_name, params = {})
upload_package(package_name, params.merge(access_token: token.token))
end
- def upload_package_with_job_token(package_name, params = {})
+ def upload_with_job_token(package_name, params = {})
upload_package(package_name, params.merge(job_token: job.token))
end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 1b90d6fa47e..a576e1ab1ee 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe API::Repositories do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+ expect(json_response).to be_an(Array)
first_commit = json_response.first
expect(first_commit['name']).to eq('bar')
@@ -73,6 +73,25 @@ RSpec.describe API::Repositories do
end
end
end
+
+ context 'keyset pagination mode' do
+ let(:first_response) do
+ get api(route, current_user), params: { pagination: "keyset" }
+
+ Gitlab::Json.parse(response.body)
+ end
+
+ it 'paginates using keysets' do
+ page_token = first_response.last["id"]
+
+ get api(route, current_user), params: { pagination: "keyset", page_token: page_token }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response).not_to eq(first_response)
+ expect(json_response.map { |t| t["id"] }).not_to include(page_token)
+ end
+ end
end
context 'when unauthenticated', 'and project is public' do
diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb
new file mode 100644
index 00000000000..6342064beb8
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb
@@ -0,0 +1,124 @@
+# frozen_string_literal: true
+
+shared_examples 'deployment metrics examples' do
+ def create_deployment(args)
+ project = args[:project]
+ environment = project.environments.production.first || create(:environment, :production, project: project)
+ create(:deployment, :success, args.merge(environment: environment))
+
+ # this is needed for the dora_deployment_frequency_in_vsa feature flag so we have aggregated data
+ ::Dora::DailyMetrics::RefreshWorker.new.perform(environment.id, Time.current.to_date.to_s) if Gitlab.ee?
+ end
+
+ describe "#deploys" do
+ subject { stage_summary.third }
+
+ context 'when from date is given' do
+ before do
+ travel_to(5.days.ago) { create_deployment(project: project) }
+ create_deployment(project: project)
+ end
+
+ it "finds the number of deploys made created after the 'from date'" do
+ expect(subject[:value]).to eq('1')
+ end
+
+ it 'returns the localized title' do
+ Gitlab::I18n.with_locale(:ru) do
+ expect(subject[:title]).to eq(n_('Deploy', 'Deploys', 1))
+ end
+ end
+ end
+
+ it "doesn't find commits from other projects" do
+ travel_to(5.days.from_now) do
+ create_deployment(project: create(:project, :repository))
+ end
+
+ expect(subject[:value]).to eq('-')
+ end
+
+ context 'when `to` parameter is given' do
+ before do
+ travel_to(5.days.ago) { create_deployment(project: project) }
+ travel_to(5.days.from_now) { create_deployment(project: project) }
+ end
+
+ it "doesn't find any record" do
+ options[:to] = Time.now
+
+ expect(subject[:value]).to eq('-')
+ end
+
+ it "finds records created between `from` and `to` range" do
+ options[:from] = 10.days.ago
+ options[:to] = 10.days.from_now
+
+ expect(subject[:value]).to eq('2')
+ end
+ end
+ end
+
+ describe '#deployment_frequency' do
+ subject { stage_summary.fourth[:value] }
+
+ it 'includes the unit: `per day`' do
+ expect(stage_summary.fourth[:unit]).to eq _('per day')
+ end
+
+ before do
+ travel_to(5.days.ago) { create_deployment(project: project) }
+ end
+
+ it 'returns 0.0 when there were deploys but the frequency was too low' do
+ options[:from] = 30.days.ago
+
+ # 1 deployment over 30 days
+ # frequency of 0.03, rounded off to 0.0
+ expect(subject).to eq('0')
+ end
+
+ it 'returns `-` when there were no deploys' do
+ options[:from] = 4.days.ago
+
+ # 0 deployment in the last 4 days
+ expect(subject).to eq('-')
+ end
+
+ context 'when `to` is nil' do
+ it 'includes range until now' do
+ options[:from] = 6.days.ago
+ options[:to] = nil
+
+ # 1 deployment over 7 days
+ expect(subject).to eq('0.1')
+ end
+ end
+
+ context 'when `to` is given' do
+ before do
+ travel_to(5.days.from_now) { create_deployment(project: project, finished_at: Time.zone.now) }
+ end
+
+ it 'finds records created between `from` and `to` range' do
+ options[:from] = 10.days.ago
+ options[:to] = 10.days.from_now
+
+ # 2 deployments over 20 days
+ expect(subject).to eq('0.1')
+ end
+
+ context 'when `from` and `to` are within a day' do
+ it 'returns the number of deployments made on that day' do
+ freeze_time do
+ create_deployment(project: project, finished_at: Time.current)
+ options[:from] = Time.current.yesterday.beginning_of_day
+ options[:to] = Time.current.end_of_day
+
+ expect(subject).to eq('0.5')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
index ecde4ee8565..eb650b7a09f 100644
--- a/spec/support/shared_examples/requests/api/packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
@@ -153,3 +153,15 @@ RSpec.shared_examples 'a package tracking event' do |category, action|
expect_snowplow_event(category: category, action: action, **snowplow_gitlab_standard_context)
end
end
+
+RSpec.shared_examples 'not a package tracking event' do
+ before do
+ stub_feature_flags(collect_package_events: true)
+ end
+
+ it 'does not create a gitlab tracking event', :snowplow, :aggregate_failures do
+ expect { subject }.not_to change { Packages::Event.count }
+
+ expect_no_snowplow_event
+ end
+end