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:
-rw-r--r--.gitlab/CODEOWNERS7
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue116
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/queries/permissions.query.graphql10
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/queries/states/conflicts.query.graphql8
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/pages/issuable.scss4
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss8
-rw-r--r--app/controllers/whats_new_controller.rb21
-rw-r--r--app/graphql/types/merge_request_type.rb6
-rw-r--r--app/helpers/whats_new_helper.rb18
-rw-r--r--app/models/release_highlight.rb67
-rw-r--r--app/presenters/gitlab/whats_new/item_presenter.rb22
-rw-r--r--changelogs/unreleased/284602-remove-issue-box-css.yml5
-rw-r--r--changelogs/unreleased/feat-add-packages_size-to-project-statistics.yml5
-rw-r--r--db/migrate/20201117153333_add_index_on_package_size_and_project_id_to_project_statistics.rb19
-rw-r--r--db/schema_migrations/202011171533331
-rw-r--r--db/structure.sql2
-rw-r--r--doc/administration/audit_events.md36
-rw-r--r--doc/administration/img/audit_log.pngbin25767 -> 0 bytes
-rw-r--r--doc/administration/img/audit_log_v13_6.pngbin0 -> 72215 bytes
-rw-r--r--doc/administration/img/export_audit_log_v13_4.pngbin46643 -> 0 bytes
-rw-r--r--doc/administration/sidekiq.md5
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql5
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json18
-rw-r--r--doc/api/graphql/reference/index.md1
-rw-r--r--doc/api/projects.md2
-rw-r--r--doc/api/runners.md4
-rw-r--r--doc/ci/docker/using_docker_build.md141
-rw-r--r--doc/development/bulk_import.md53
-rw-r--r--doc/development/feature_flags/process.md3
-rw-r--r--doc/development/img/bulk_imports_overview_v13_7.pngbin0 -> 106650 bytes
-rw-r--r--doc/user/clusters/agent/index.md20
-rw-r--r--doc/user/clusters/agent/repository.md93
-rw-r--r--doc/user/packages/maven_repository/index.md8
-rw-r--r--lib/api/entities/project_statistics.rb1
-rw-r--r--lib/api/helpers/projects_helpers.rb2
-rw-r--r--lib/gitlab/whats_new.rb40
-rw-r--r--spec/features/markdown/mermaid_spec.rb8
-rw-r--r--spec/fixtures/whats_new/20201225_01_01.yml3
-rw-r--r--spec/fixtures/whats_new/20201225_01_02.yml3
-rw-r--r--spec/fixtures/whats_new/20201225_01_05.yml7
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js402
-rw-r--r--spec/graphql/types/merge_request_type_spec.rb2
-rw-r--r--spec/helpers/whats_new_helper_spec.rb31
-rw-r--r--spec/models/release_highlight_spec.rb135
-rw-r--r--spec/presenters/gitlab/whats_new/item_presenter_spec.rb29
-rw-r--r--spec/requests/api/projects_spec.rb4
-rw-r--r--spec/requests/whats_new_controller_spec.rb26
48 files changed, 1044 insertions, 358 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index a24fef5e44d..7aa3bb6fd4a 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -194,12 +194,17 @@ Dangerfile @gl-quality/eng-prod
# Secure & Threat Management ownership delineation
# https://about.gitlab.com/handbook/engineering/development/threat-management/delineate-secure-threat-management.html#technical-boundaries
-[Secure]
+[Threat Insights]
/ee/app/finders/security/ @gitlab-org/secure/threat-insights-backend-team
/ee/app/models/security/ @gitlab-org/secure/threat-insights-backend-team
/ee/app/models/vulnerabilities/ @gitlab-org/secure/threat-insights-backend-team
/ee/app/models/vulnerability.rb @gitlab-org/secure/threat-insights-backend-team
+/ee/app/policies/vulnerabilities/ @gitlab-org/secure/threat-insights-backend-team
+/ee/app/policies/vulnerability*.rb @gitlab-org/secure/threat-insights-backend-team
/ee/lib/api/vulnerabilit*.rb @gitlab-org/secure/threat-insights-backend-team
+/ee/spec/policies/vulnerabilities/ @gitlab-org/secure/threat-insights-backend-team
+/ee/spec/policies/vulnerabilities/vulnerability*.rb @gitlab-org/secure/threat-insights-backend-team
+[Secure]
/ee/lib/gitlab/ci/parsers/license_compliance/ @gitlab-org/secure/composition-analysis-be
/ee/lib/gitlab/ci/parsers/security/ @gitlab-org/secure/composition-analysis-be @gitlab-org/secure/dynamic-analysis-be @gitlab-org/secure/static-analysis-be @gitlab-org/secure/fuzzing-be
/ee/lib/gitlab/ci/reports/coverage_fuzzing/ @gitlab-org/secure/fuzzing-be
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
index 2df03fbc679..87c59e5ece9 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
@@ -1,20 +1,48 @@
<script>
import $ from 'jquery';
import { escape } from 'lodash';
-import { GlButton, GlModalDirective } from '@gitlab/ui';
+import { GlButton, GlModalDirective, GlSkeletonLoader } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
import { mouseenter, debouncedMouseleave, togglePopover } from '~/shared/popover';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
import StatusIcon from '../mr_widget_status_icon.vue';
+import userPermissionsQuery from '../../queries/permissions.query.graphql';
+import conflictsStateQuery from '../../queries/states/conflicts.query.graphql';
export default {
name: 'MRWidgetConflicts',
components: {
+ GlSkeletonLoader,
StatusIcon,
GlButton,
},
directives: {
GlModalDirective,
},
+ mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin],
+ apollo: {
+ userPermissions: {
+ query: userPermissionsQuery,
+ skip() {
+ return !this.glFeatures.mergeRequestWidgetGraphql;
+ },
+ variables() {
+ return this.mergeRequestQueryVariables;
+ },
+ update: data => data.project.mergeRequest.userPermissions,
+ },
+ stateData: {
+ query: conflictsStateQuery,
+ skip() {
+ return !this.glFeatures.mergeRequestWidgetGraphql;
+ },
+ variables() {
+ return this.mergeRequestQueryVariables;
+ },
+ update: data => data.project.mergeRequest,
+ },
+ },
props: {
/* TODO: This is providing all store and service down when it
only needs a few props */
@@ -24,21 +52,72 @@ export default {
default: () => ({}),
},
},
+ data() {
+ return {
+ userPermissions: {},
+ stateData: {},
+ };
+ },
computed: {
+ isLoading() {
+ return (
+ this.glFeatures.mergeRequestWidgetGraphql &&
+ this.$apollo.queries.userPermissions.loading &&
+ this.$apollo.queries.stateData.loading
+ );
+ },
+ canPushToSourceBranch() {
+ if (this.glFeatures.mergeRequestWidgetGraphql) {
+ return this.userPermissions.pushToSourceBranch;
+ }
+
+ return this.mr.canPushToSourceBranch;
+ },
+ canMerge() {
+ if (this.glFeatures.mergeRequestWidgetGraphql) {
+ return this.userPermissions.canMerge;
+ }
+
+ return this.mr.canMerge;
+ },
+ shouldBeRebased() {
+ if (this.glFeatures.mergeRequestWidgetGraphql) {
+ return this.stateData.shouldBeRebased;
+ }
+
+ return this.mr.shouldBeRebased;
+ },
+ sourceBranchProtected() {
+ if (this.glFeatures.mergeRequestWidgetGraphql) {
+ return this.stateData.sourceBranchProtected;
+ }
+
+ return this.mr.sourceBranchProtected;
+ },
popoverTitle() {
return s__(
'mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected.',
);
},
showResolveButton() {
- return this.mr.conflictResolutionPath && this.mr.canPushToSourceBranch;
+ return this.mr.conflictResolutionPath && this.canPushToSourceBranch;
},
showPopover() {
- return this.showResolveButton && this.mr.sourceBranchProtected;
+ return this.showResolveButton && this.sourceBranchProtected;
},
},
- mounted() {
- if (this.showPopover) {
+ watch: {
+ showPopover: {
+ handler(newVal) {
+ if (newVal) {
+ this.$nextTick(this.initPopover);
+ }
+ },
+ immediate: true,
+ },
+ },
+ methods: {
+ initPopover() {
const $el = $(this.$refs.popover);
$el
@@ -68,7 +147,7 @@ export default {
.on('show.bs.popover', () => {
window.addEventListener('scroll', togglePopover.bind($el, false), { once: true });
});
- }
+ },
},
};
</script>
@@ -76,34 +155,41 @@ export default {
<div class="mr-widget-body media">
<status-icon :show-disabled-button="true" status="warning" />
- <div class="media-body space-children">
- <span v-if="mr.shouldBeRebased" class="bold">
+ <div v-if="isLoading" class="gl-ml-4 gl-w-full mr-conflict-loader">
+ <gl-skeleton-loader :width="334" :height="30">
+ <rect x="0" y="7" width="150" height="16" rx="4" />
+ <rect x="158" y="7" width="84" height="16" rx="4" />
+ <rect x="250" y="7" width="84" height="16" rx="4" />
+ </gl-skeleton-loader>
+ </div>
+ <div v-else class="media-body space-children">
+ <span v-if="shouldBeRebased" class="bold">
{{
s__(`mrWidget|Fast-forward merge is not possible.
-To merge this request, first rebase locally.`)
+ To merge this request, first rebase locally.`)
}}
</span>
<template v-else>
<span class="bold">
- {{ s__('mrWidget|There are merge conflicts') }}<span v-if="!mr.canMerge">.</span>
- <span v-if="!mr.canMerge">
+ {{ s__('mrWidget|There are merge conflicts') }}<span v-if="!canMerge">.</span>
+ <span v-if="!canMerge">
{{
s__(`mrWidget|Resolve these conflicts or ask someone
- with write access to this repository to merge it locally`)
+ with write access to this repository to merge it locally`)
}}
</span>
</span>
<span v-if="showResolveButton" ref="popover">
<gl-button
- :href="mr.conflictResolutionPath"
- :disabled="mr.sourceBranchProtected"
+ :href="!sourceBranchProtected && mr.conflictResolutionPath"
+ :disabled="sourceBranchProtected"
class="js-resolve-conflicts-button"
>
{{ s__('mrWidget|Resolve conflicts') }}
</gl-button>
</span>
<gl-button
- v-if="mr.canMerge"
+ v-if="canMerge"
v-gl-modal-directive="'modal-merge-info'"
class="js-merge-locally-button"
>
diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/permissions.query.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/permissions.query.graphql
new file mode 100644
index 00000000000..ae2a67440fe
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/queries/permissions.query.graphql
@@ -0,0 +1,10 @@
+query userPermissionsQuery($projectPath: ID!, $iid: String!) {
+ project(fullPath: $projectPath) {
+ mergeRequest(iid: $iid) {
+ userPermissions {
+ canMerge
+ pushToSourceBranch
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/states/conflicts.query.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/states/conflicts.query.graphql
new file mode 100644
index 00000000000..186c0e64561
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/queries/states/conflicts.query.graphql
@@ -0,0 +1,8 @@
+query workInProgressQuery($projectPath: ID!, $iid: String!) {
+ project(fullPath: $projectPath) {
+ mergeRequest(iid: $iid) {
+ shouldBeRebased
+ sourceBranchProtected
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index f0b1e859139..808813599c5 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -468,7 +468,6 @@ $gl-line-height-20: 20px;
$gl-line-height-24: 24px;
$gl-line-height-14: 14px;
-$issue-box-upcoming-bg: #8f8f8f;
$pages-group-name-color: #4c4e54;
/*
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index aa849e1b17b..3893962cd48 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -693,10 +693,6 @@
.issuable-list {
li {
- .issue-box {
- display: flex;
- }
-
.issuable-info-container {
flex: 1;
display: flex;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index a0ac55e4c6c..98eee2c3a02 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -1039,3 +1039,11 @@ $mr-widget-min-height: 69px;
.diff-file-row.is-active {
background-color: $gray-50;
}
+
+.mr-conflict-loader {
+ max-width: 334px;
+
+ > svg {
+ vertical-align: middle;
+ }
+}
diff --git a/app/controllers/whats_new_controller.rb b/app/controllers/whats_new_controller.rb
index 384c984089a..6ed15d9b127 100644
--- a/app/controllers/whats_new_controller.rb
+++ b/app/controllers/whats_new_controller.rb
@@ -1,8 +1,6 @@
# frozen_string_literal: true
class WhatsNewController < ApplicationController
- include Gitlab::WhatsNew
-
skip_before_action :authenticate_user!
before_action :check_feature_flag, :check_valid_page_param, :set_pagination_headers
@@ -12,7 +10,7 @@ class WhatsNewController < ApplicationController
def index
respond_to do |format|
format.js do
- render json: whats_new_release_items(page: current_page)
+ render json: most_recent_items
end
end
end
@@ -27,18 +25,19 @@ class WhatsNewController < ApplicationController
render_404 if current_page < 1
end
- def set_pagination_headers
- response.set_header('X-Next-Page', next_page)
- end
-
def current_page
params[:page]&.to_i || 1
end
- def next_page
- next_page = current_page + 1
- next_index = next_page - 1
+ def most_recent
+ @most_recent ||= ReleaseHighlight.paginated(page: current_page)
+ end
+
+ def most_recent_items
+ most_recent[:items].map {|item| Gitlab::WhatsNew::ItemPresenter.present(item) }
+ end
- next_page if whats_new_file_paths[next_index]
+ def set_pagination_headers
+ response.set_header('X-Next-Page', most_recent[:next_page])
end
end
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index e68d6706c43..6d1e15742e7 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -49,6 +49,8 @@ module Types
description: 'ID of the merge request target project'
field :source_branch, GraphQL::STRING_TYPE, null: false,
description: 'Source branch of the merge request'
+ field :source_branch_protected, GraphQL::BOOLEAN_TYPE, null: false, calls_gitaly: true,
+ description: 'Indicates if the source branch is protected'
field :target_branch, GraphQL::STRING_TYPE, null: false,
description: 'Target branch of the merge request'
field :work_in_progress, GraphQL::BOOLEAN_TYPE, method: :work_in_progress?, null: false,
@@ -194,6 +196,10 @@ module Types
def commit_count
object&.metrics&.commits_count
end
+
+ def source_branch_protected
+ object.source_project.present? && ProtectedBranch.protected?(object.source_project, object.source_branch)
+ end
end
end
Types::MergeRequestType.prepend_if_ee('::EE::Types::MergeRequestType')
diff --git a/app/helpers/whats_new_helper.rb b/app/helpers/whats_new_helper.rb
index 283d443f51b..f267ede3153 100644
--- a/app/helpers/whats_new_helper.rb
+++ b/app/helpers/whats_new_helper.rb
@@ -1,25 +1,15 @@
# frozen_string_literal: true
module WhatsNewHelper
- include Gitlab::WhatsNew
-
def whats_new_most_recent_release_items_count
- Gitlab::ProcessMemoryCache.cache_backend.fetch('whats_new:release_items_count', expires_in: CACHE_DURATION) do
- whats_new_release_items&.count
- end
+ ReleaseHighlight.most_recent_item_count
end
def whats_new_storage_key
- return unless whats_new_most_recent_version
-
- ['display-whats-new-notification', whats_new_most_recent_version].join('-')
- end
+ most_recent_version = ReleaseHighlight.most_recent_version
- private
+ return unless most_recent_version
- def whats_new_most_recent_version
- Gitlab::ProcessMemoryCache.cache_backend.fetch('whats_new:release_version', expires_in: CACHE_DURATION) do
- whats_new_release_items&.first&.[]('release')
- end
+ ['display-whats-new-notification', most_recent_version].join('-')
end
end
diff --git a/app/models/release_highlight.rb b/app/models/release_highlight.rb
new file mode 100644
index 00000000000..436314de3a3
--- /dev/null
+++ b/app/models/release_highlight.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+class ReleaseHighlight
+ CACHE_DURATION = 1.hour
+ FILES_PATH = Rails.root.join('data', 'whats_new', '*.yml')
+
+ def self.paginated(page: 1)
+ Rails.cache.fetch(cache_key(page), expires_in: CACHE_DURATION) do
+ items = self.load_items(page: page)
+
+ next if items.nil?
+
+ {
+ items: items,
+ next_page: next_page(current_page: page)
+ }
+ end
+ end
+
+ def self.load_items(page:)
+ index = page - 1
+ file_path = file_paths[index]
+
+ return if file_path.nil?
+
+ file = File.read(file_path)
+
+ items = YAML.safe_load(file, permitted_classes: [Date])
+
+ platform = Gitlab.com? ? 'gitlab-com' : 'self-managed'
+ items&.select {|item| item[platform] }
+ rescue Psych::Exception => e
+ Gitlab::ErrorTracking.track_exception(e, file_path: file_path)
+
+ nil
+ end
+
+ def self.file_paths
+ @file_paths ||= Rails.cache.fetch('release_highlight:file_paths', expires_in: CACHE_DURATION) do
+ Dir.glob(FILES_PATH).sort.reverse
+ end
+ end
+
+ def self.cache_key(page)
+ filename = /\d*\_\d*\_\d*/.match(self.file_paths&.first)
+ "release_highlight:items:file-#{filename}:page-#{page}"
+ end
+
+ def self.next_page(current_page: 1)
+ next_page = current_page + 1
+ next_index = next_page - 1
+
+ next_page if self.file_paths[next_index]
+ end
+
+ def self.most_recent_version
+ Gitlab::ProcessMemoryCache.cache_backend.fetch('release_highlight:release_version', expires_in: CACHE_DURATION) do
+ self.paginated&.[](:items)&.first&.[]('release')
+ end
+ end
+
+ def self.most_recent_item_count
+ Gitlab::ProcessMemoryCache.cache_backend.fetch('release_highlight:recent_item_count', expires_in: CACHE_DURATION) do
+ self.paginated&.[](:items)&.count
+ end
+ end
+end
diff --git a/app/presenters/gitlab/whats_new/item_presenter.rb b/app/presenters/gitlab/whats_new/item_presenter.rb
new file mode 100644
index 00000000000..26a5b9aab02
--- /dev/null
+++ b/app/presenters/gitlab/whats_new/item_presenter.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module WhatsNew
+ class ItemPresenter
+ DICTIONARY = {
+ free: 'Free',
+ starter: 'Bronze',
+ premium: 'Silver',
+ ultimate: 'Gold'
+ }.freeze
+
+ def self.present(item)
+ if Gitlab.com?
+ item['packages'] = item['packages'].map { |p| DICTIONARY[p.downcase.to_sym] }
+ end
+
+ item
+ end
+ end
+ end
+end
diff --git a/changelogs/unreleased/284602-remove-issue-box-css.yml b/changelogs/unreleased/284602-remove-issue-box-css.yml
new file mode 100644
index 00000000000..8bfa9283724
--- /dev/null
+++ b/changelogs/unreleased/284602-remove-issue-box-css.yml
@@ -0,0 +1,5 @@
+---
+title: Remove unused .issue-box CSS
+merge_request: 48002
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/feat-add-packages_size-to-project-statistics.yml b/changelogs/unreleased/feat-add-packages_size-to-project-statistics.yml
new file mode 100644
index 00000000000..d488b455651
--- /dev/null
+++ b/changelogs/unreleased/feat-add-packages_size-to-project-statistics.yml
@@ -0,0 +1,5 @@
+---
+title: Add packages_size to ProjectStatistics API entity
+merge_request: 47156
+author: Roger Meier
+type: added
diff --git a/db/migrate/20201117153333_add_index_on_package_size_and_project_id_to_project_statistics.rb b/db/migrate/20201117153333_add_index_on_package_size_and_project_id_to_project_statistics.rb
new file mode 100644
index 00000000000..efb5cf14d3c
--- /dev/null
+++ b/db/migrate/20201117153333_add_index_on_package_size_and_project_id_to_project_statistics.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddIndexOnPackageSizeAndProjectIdToProjectStatistics < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_project_statistics_on_packages_size_and_project_id'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :project_statistics, [:packages_size, :project_id],
+ name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name :project_statistics, INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20201117153333 b/db/schema_migrations/20201117153333
new file mode 100644
index 00000000000..ee496f91777
--- /dev/null
+++ b/db/schema_migrations/20201117153333
@@ -0,0 +1 @@
+008f3a69d23abbd513336c5a48b2448e470a9413920beeb6a1684d0c6840d6a4 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 5cb98ddbd19..a579f2aacd7 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -21779,6 +21779,8 @@ CREATE UNIQUE INDEX index_project_settings_on_push_rule_id ON project_settings U
CREATE INDEX index_project_statistics_on_namespace_id ON project_statistics USING btree (namespace_id);
+CREATE INDEX index_project_statistics_on_packages_size_and_project_id ON project_statistics USING btree (packages_size, project_id);
+
CREATE UNIQUE INDEX index_project_statistics_on_project_id ON project_statistics USING btree (project_id);
CREATE INDEX index_project_statistics_on_repository_size_and_project_id ON project_statistics USING btree (repository_size, project_id);
diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md
index fa7fa3666b3..44fa9a93420 100644
--- a/doc/administration/audit_events.md
+++ b/doc/administration/audit_events.md
@@ -133,12 +133,6 @@ recorded:
- A user's personal access token was successfully created or revoked ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276921) in GitLab 13.6)
- A failed attempt to create or revoke a user's personal access token ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276921) in GitLab 13.6)
-It's possible to filter particular actions by choosing an audit data type from
-the filter dropdown box. You can further filter by specific group, project, or user
-(for authentication events).
-
-![audit log](img/audit_log.png)
-
Instance events can also be accessed via the [Instance Audit Events API](../api/audit_events.md#instance-audit-events).
### Missing events
@@ -180,6 +174,19 @@ the steps bellow.
Feature.enable(:repository_push_audit_event)
```
+## Search
+
+The search filters you can see depends on which audit level you are at.
+
+| Filter | Available options |
+| ------ | ----------------- |
+| Scope (Project level) | A specific user who performed the action. |
+| Scope (Group level) | A specific user (in a group) who performed the action. |
+| Scope (Instance level) | A specific group, project, or user that the action was scoped to. |
+| Date range | Either via the date range buttons or pickers (maximum range of 31 days). Default is from the first day of the month to today's date. |
+
+![audit log](img/audit_log_v13_6.png)
+
## Export to CSV **(PREMIUM ONLY)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1449) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.4.
@@ -193,23 +200,18 @@ This feature might not be available to you. Check the **version history** note a
If available, you can enable it with a [feature flag](#enable-or-disable-audit-log-export-to-csv).
Export to CSV allows customers to export the current filter view of your audit log as a
-CSV file,
-which stores tabular data in plain text. The data provides a comprehensive view with respect to
+CSV file, which stores tabular data in plain text. The data provides a comprehensive view with respect to
audit events.
To export the Audit Log to CSV, navigate to
**{monitor}** **Admin Area > Monitoring > Audit Log**
-1. Click in the field **Search**.
-1. In the dropdown menu that appears, select the event type that you want to filter by.
-1. Select the preferred date range.
+1. Select the available search [filters](#search).
1. Click **Export as CSV**.
-![Export Audit Log](img/export_audit_log_v13_4.png)
-
### Sort
-Exported events are always sorted by `ID` in ascending order.
+Exported events are always sorted by `created_at` in ascending order.
### Format
@@ -222,8 +224,8 @@ The first row contains the headers, which are listed in the following table alon
| Author ID | ID of the author |
| Author Name | Full name of the author |
| Entity ID | ID of the scope |
-| Entity Type | Type of the entity (`Project`/`Group`/`User`) |
-| Entity Path | Path of the entity |
+| Entity Type | Type of the scope (`Project`/`Group`/`User`) |
+| Entity Path | Path of the scope |
| Target ID | ID of the target |
| Target Type | Type of the target |
| Target Details | Details of the target |
@@ -233,7 +235,7 @@ The first row contains the headers, which are listed in the following table alon
### Limitation
-The Audit Log CSV file size is limited to a maximum of `100,000` events.
+The Audit Log CSV file is limited to a maximum of `100,000` events.
The remaining records are truncated when this limit is reached.
### Enable or disable Audit Log Export to CSV
diff --git a/doc/administration/img/audit_log.png b/doc/administration/img/audit_log.png
deleted file mode 100644
index d4f4c2abf38..00000000000
--- a/doc/administration/img/audit_log.png
+++ /dev/null
Binary files differ
diff --git a/doc/administration/img/audit_log_v13_6.png b/doc/administration/img/audit_log_v13_6.png
new file mode 100644
index 00000000000..3268f864e81
--- /dev/null
+++ b/doc/administration/img/audit_log_v13_6.png
Binary files differ
diff --git a/doc/administration/img/export_audit_log_v13_4.png b/doc/administration/img/export_audit_log_v13_4.png
deleted file mode 100644
index e4ba330b8a9..00000000000
--- a/doc/administration/img/export_audit_log_v13_4.png
+++ /dev/null
Binary files differ
diff --git a/doc/administration/sidekiq.md b/doc/administration/sidekiq.md
index 2cc6acc8c87..c56b3e77dd7 100644
--- a/doc/administration/sidekiq.md
+++ b/doc/administration/sidekiq.md
@@ -25,8 +25,9 @@ you want using steps 1 and 2 from the GitLab downloads page.
## Optional: Enable extra Sidekiq processes
sidekiq_cluster['enable'] = true
- sidekiq_cluster['enable'] = true
- "elastic_indexer"
+ sidekiq['queue_groups'] = [
+ "elastic_indexer",
+ "*"
]
```
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index d6c3967e4d5..63270d5cc4a 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -12781,6 +12781,11 @@ type MergeRequest implements CurrentUserTodos & Noteable {
sourceBranchExists: Boolean!
"""
+ Indicates if the source branch is protected
+ """
+ sourceBranchProtected: Boolean!
+
+ """
Source project of the merge request
"""
sourceProject: Project
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index a0f342764f3..2f4181df554 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -35043,6 +35043,24 @@
"deprecationReason": null
},
{
+ "name": "sourceBranchProtected",
+ "description": "Indicates if the source branch is protected",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "sourceProject",
"description": "Source project of the merge request",
"args": [
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index aa59a638c22..90c1a5a0ffd 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -1950,6 +1950,7 @@ Autogenerated return type of MarkAsSpamSnippet.
| `shouldRemoveSourceBranch` | Boolean | Indicates if the source branch of the merge request will be deleted after merge |
| `sourceBranch` | String! | Source branch of the merge request |
| `sourceBranchExists` | Boolean! | Indicates if the source branch of the merge request exists |
+| `sourceBranchProtected` | Boolean! | Indicates if the source branch is protected |
| `sourceProject` | Project | Source project of the merge request |
| `sourceProjectId` | Int | ID of the merge request source project |
| `state` | MergeRequestState! | State of the merge request |
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 03440f0c143..419ae5ce423 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -49,7 +49,7 @@ GET /projects
| `last_activity_before` | datetime | **{dotted-circle}** No | Limit results to projects with last_activity before specified time. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ |
| `membership` | boolean | **{dotted-circle}** No | Limit by projects that the current user is a member of. |
| `min_access_level` | integer | **{dotted-circle}** No | Limit by current user minimal [access level](members.md#valid-access-levels). |
-| `order_by` | string | **{dotted-circle}** No | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. `repository_size`, `storage_size`, or `wiki_size` fields are only allowed for admins. Default is `created_at`. |
+| `order_by` | string | **{dotted-circle}** No | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. `repository_size`, `storage_size`, `packages_size` or `wiki_size` fields are only allowed for admins. Default is `created_at`. |
| `owned` | boolean | **{dotted-circle}** No | Limit by projects explicitly owned by the current user. |
| `repository_checksum_failed` **(PREMIUM)** | boolean | **{dotted-circle}** No | Limit projects where the repository checksum calculation has failed ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6137) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.2). |
| `repository_storage` | string | **{dotted-circle}** No | Limit results to projects stored on `repository_storage`. _(admins only)_ |
diff --git a/doc/api/runners.md b/doc/api/runners.md
index 16ecdebcd4f..1b47f2ea98d 100644
--- a/doc/api/runners.md
+++ b/doc/api/runners.md
@@ -224,7 +224,7 @@ PUT /runners/:id
| `run_untagged`| boolean | no | Flag indicating the runner can execute untagged jobs |
| `locked` | boolean | no | Flag indicating the runner is locked |
| `access_level` | string | no | The access_level of the runner; `not_protected` or `ref_protected` |
-| `maximum_timeout` | integer | no | Maximum timeout set when this runner will handle the job |
+| `maximum_timeout` | integer | no | Maximum timeout set when this runner handles the job |
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/6" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2"
@@ -559,7 +559,7 @@ POST /runners
| `run_untagged` | boolean | no | Whether the runner should handle untagged jobs |
| `tag_list` | string array | no | List of runner's tags |
| `access_level` | string | no | The access_level of the runner; `not_protected` or `ref_protected` |
-| `maximum_timeout` | integer | no | Maximum timeout set when this runner will handle the job |
+| `maximum_timeout` | integer | no | Maximum timeout set when this runner handles the job |
```shell
curl --request POST "https://gitlab.example.com/api/v4/runners" --form "token=<registration_token>" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2"
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index c8eeb5c222a..cf558de0925 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -589,6 +589,147 @@ The configuration is picked up by the `dind` service.
sub_path = "daemon.json"
```
+## Authenticating with registry in Docker-in-Docker
+
+When you use Docker-in-Docker, the [normal authentication
+methods](using_docker_images.html#define-an-image-from-a-private-container-registry)
+won't work because a fresh Docker daemon is started with the service.
+
+### Option 1: Run `docker login`
+
+In [`before_script`](../yaml/README.md#before_script) run `docker
+login`:
+
+```yaml
+image: docker:19.03.13
+
+variables:
+ DOCKER_TLS_CERTDIR: "/certs"
+
+services:
+ - docker:19.03.13-dind
+
+build:
+ stage: build
+ before_script:
+ - echo "$DOCKER_REGISTRY_PASS" | docker login $DOCKER_REGISTRY --username $DOCKER_REGISTRY_USER --password-stdin
+ script:
+ - docker build -t my-docker-image .
+ - docker run my-docker-image /script/to/run/tests
+```
+
+To log in to Docker Hub, leave `$DOCKER_REGISTRY`
+empty or remove it.
+
+### Option 2: Mount `~/.docker/config.json` on each job
+
+If you are an administrator for GitLab Runner, you can mount a file
+with the authentication configuration to `~/.docker/config.json`.
+Then every job that the runner picks up will be authenticated already. If you
+are using the official `docker:19.03.13` image, the home directory is
+under `/root`.
+
+If you mount the config file, any `docker` command
+that modifies the `~/.docker/config.json` (for example, `docker login`)
+fails, because the file is mounted as read-only. Do not change it from
+read-only, because other problems will occur.
+
+Here is an example of `/opt/.docker/config.json` that follows the
+[`DOCKER_AUTH_CONFIG`](using_docker_images.md#determining-your-docker_auth_config-data)
+documentation:
+
+```json
+{
+ "auths": {
+ "https://index.docker.io/v1/": {
+ "auth": "bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ="
+ }
+ }
+}
+```
+
+#### Docker
+
+Update the [volume
+mounts](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#volumes-in-the-runnersdocker-section)
+to include the file.
+
+```toml
+[[runners]]
+ ...
+ executor = "docker"
+ [runners.docker]
+ ...
+ privileged = true
+ volumes = ["/opt/.docker/config.json:/root/.docker/config.json:ro"]
+```
+
+#### Kubernetes
+
+Create a [ConfigMap](https://kubernetes.io/docs/concepts/configuration/configmap/) with the content
+of this file. You can do this with a command like:
+
+```shell
+kubectl create configmap docker-client-config --namespace gitlab-runner --from-file /opt/.docker/config.json
+```
+
+Update the [volume
+mounts](https://docs.gitlab.com/runner/executors/kubernetes.html#using-volumes)
+to include the file.
+
+```toml
+[[runners]]
+ ...
+ executor = "kubernetes"
+ [runners.kubernetes]
+ image = "alpine:3.12"
+ privileged = true
+ [[runners.kubernetes.volumes.config_map]]
+ name = "docker-client-config"
+ mount_path = "/root/.docker/config.json"
+ # If you are running GitLab Runner 13.5
+ # or lower you can remove this
+ sub_path = "config.json"
+```
+
+### Option 3: Use `DOCKER_ATUH_CONFIG`
+
+If you already have
+[`DOCKER_AUTH_CONFIG`](using_docker_images.md#determining-your-docker_auth_config-data)
+defined, you can use the variable and save it in
+`~/.docker/config.json`.
+
+There are multiple ways to define this. For example:
+
+- Inside
+ [`pre_build_script`](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section)
+ inside of the runner config file.
+- Inside [`before_script`](../yaml/README.md#before_script).
+- Inside of [`script`](../yaml/README.md#script).
+
+Below is an example of
+[`before_script`](../yaml/README.md#before_script). The same commands
+apply for any solution you implement.
+
+```yaml
+image: docker:19.03.13
+
+variables:
+ DOCKER_TLS_CERTDIR: "/certs"
+
+services:
+ - docker:19.03.13-dind
+
+build:
+ stage: build
+ before_script:
+ - mkdir -p $HOME/.docker
+ - echo $DOCKER_AUTH_CONFIG > $HOME/.docker/config.json
+ script:
+ - docker build -t my-docker-image .
+ - docker run my-docker-image /script/to/run/tests
+```
+
## Making Docker-in-Docker builds faster with Docker layer caching
When using Docker-in-Docker, Docker downloads all layers of your image every
diff --git a/doc/development/bulk_import.md b/doc/development/bulk_import.md
new file mode 100644
index 00000000000..eb9106daa04
--- /dev/null
+++ b/doc/development/bulk_import.md
@@ -0,0 +1,53 @@
+---
+stage: Manage
+group: Import
+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 Group Migration
+
+[Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2771) in GitLab 13.7.
+
+CAUTION: **Caution:**
+This feature is [under construction](https://gitlab.com/groups/gitlab-org/-/epics/2771) and its API/Architecture might change in the future.
+
+GitLab Group Migration is the evolution of Project and Group Import functionality. The
+goal is to have an easier way to the user migrate a whole Group, including
+Projects, from one GitLab instance to another.
+
+## Design decisions
+
+### Overview
+
+The following architectural diagram illustrates how the Group Migration
+works with a set of [ETL](#etl) Pipelines leveraging from the current [GitLab APIs](#api).
+
+![Simplified Component Overview](img/bulk_imports_overview_v13_7.png)
+
+### [ETL](https://www.ibm.com/cloud/learn/etl)
+
+<!-- Direct quote from the IBM URL link -->
+
+> ETL, for extract, transform and load, is a data integration process that
+> combines data from multiple data sources into a single, consistent data store
+> that is loaded into a data warehouse or other target system.
+
+Using ETL architecture makes the code more explicit and easier to follow, test and extend. The
+idea is to have one ETL pipeline for each relation to be imported.
+
+### API
+
+The current [Project](../user/project/settings/import_export.md) and [Group](../user/group/settings/import_export.md) Import are file based, so they require an export
+step to generate the file to be imported.
+
+GitLab Group migration leverages on [GitLab API](../api/README.md) to speed the migration.
+
+And, because we're on the road to [GraphQL](../api/README.md#road-to-graphql),
+GitLab Group Migration will be contributing towards to expand the GraphQL API coverage, which benefits both GitLab
+and its users.
+
+### Namespace
+
+The migration process starts with the creation of a [`BulkImport`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/models/bulk_import.rb)
+record to keep track of the migration. From there all the code related to the
+GitLab Group Migration can be found under the new `BulkImports` namespace in all the application layers.
diff --git a/doc/development/feature_flags/process.md b/doc/development/feature_flags/process.md
index b282424f59a..72dad4f9b6a 100644
--- a/doc/development/feature_flags/process.md
+++ b/doc/development/feature_flags/process.md
@@ -32,7 +32,8 @@ should be leveraged:
requests, you can use the following workflow:
1. [Create a new feature flag](development.md#create-a-new-feature-flag)
- which is **off** by default, in the first merge request.
+ which is **off** by default, in the first merge request which uses the flag.
+ Flags [should not be added separately](development.md#risk-of-a-broken-master-main-branch).
1. Submit incremental changes via one or more merge requests, ensuring that any
new code added can only be reached if the feature flag is **on**.
You can keep the feature flag enabled on your local GDK during development.
diff --git a/doc/development/img/bulk_imports_overview_v13_7.png b/doc/development/img/bulk_imports_overview_v13_7.png
new file mode 100644
index 00000000000..405ab7b1815
--- /dev/null
+++ b/doc/development/img/bulk_imports_overview_v13_7.png
Binary files differ
diff --git a/doc/user/clusters/agent/index.md b/doc/user/clusters/agent/index.md
index 74c679d9bb9..321de69fabe 100644
--- a/doc/user/clusters/agent/index.md
+++ b/doc/user/clusters/agent/index.md
@@ -161,23 +161,9 @@ gitops:
```
GitLab [versions 13.7 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/259669) also
-supports manifest projects containing multiple directories (or subdirectories)
-of YAML files. To use multiple YAML files, specify a `paths` attribute:
-
-```yaml
-gitops:
- manifest_projects:
- - id: "path-to/your-manifest-project-number1"
- paths:
- # Read all .yaml files from team1/app1 directory.
- # See https://github.com/bmatcuk/doublestar#about and
- # https://pkg.go.dev/github.com/bmatcuk/doublestar/v2#Match for globbing rules.
- - glob: '/team1/app1/*.yaml'
- # Read all .yaml files from team2/apps and all subdirectories
- - glob: '/team2/apps/**/*.yaml'
- # If 'paths' is not specified or is an empty list, the configuration below is used
- - glob: '/**/*.{yaml,yml,json}'
-```
+supports manifest projects containing
+multiple directories (or subdirectories) of YAML files. For more information see our
+documentation on the [Kubernetes Agent configuration respository](repository.md).
### Create an Agent record in GitLab
diff --git a/doc/user/clusters/agent/repository.md b/doc/user/clusters/agent/repository.md
new file mode 100644
index 00000000000..d160e6556e3
--- /dev/null
+++ b/doc/user/clusters/agent/repository.md
@@ -0,0 +1,93 @@
+---
+stage: Configure
+group: Configure
+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
+---
+
+# Kubernetes Agent configuration repository **(PREMIUM ONLY)**
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259669) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.7.
+> - It's disabled on GitLab.com. Rolling this feature out to GitLab.com is [planned](https://gitlab.com/groups/gitlab-org/-/epics/3834).
+
+CAUTION: **Warning:**
+This feature might not be available to you. Check the **version history** note above for details.
+
+The [GitLab Kubernetes Agent integration](index.md) supports hosting your configuration for
+multiple GitLab Kubernetes Agents in a single repository. These agents can be running
+in the same cluster or in multiple clusters, and potentially with more than one Agent per cluster.
+
+The Agent bootstraps with the GitLab installation URL and an authentication token,
+and you provide the rest of the configuration in your repository, following
+Infrastructure as Code (IaaC) best practices.
+
+A minimal repository layout looks like this, with `my_agent_1` as the name
+of your Agent:
+
+```plaintext
+|- .gitlab
+ |- agents
+ |- my_agent_1
+ |- config.yaml
+```
+
+## Synchronize manifest projects
+
+Your `config.yaml` file contains a `gitops` section, which contains a `manifest_projects`
+section. Each `id` in the `manifest_projects` section is the path to a Git repository
+with Kubernetes resource definitions in YAML or JSON format. The Agent monitors
+each project you declare, and when the project changes, GitLab deploys the changes
+using the Agent.
+
+To use multiple YAML files, specify a `paths` attribute in the `gitops` section.
+
+By default, the Agent monitors all types of resources. You can exclude some types of resources
+from monitoring. This enables you to reduce the permissions needed by the GitOps feature,
+through `resource_exclusions`.
+
+To enable a specific named resource, first use `resource_inclusions` to enable desired resources.
+The following file excerpt includes specific `api_groups` and `kinds`. The `resource_exclusions`
+which follow excludes all other `api_groups` and `kinds`:
+
+```yaml
+gitops:
+ # Manifest projects are watched by the agent. Whenever a project changes,
+ # GitLab deploys the changes using the agent.
+ manifest_projects:
+ # No authentication mechanisms are currently supported.
+ # The `id` is a path to a Git repository with Kubernetes resource definitions
+ # in YAML or JSON format.
+ - id: gitlab-org/cluster-integration/gitlab-agent
+ # Holds the only API groups and kinds of resources that gitops will monitor.
+ # Inclusion rules are evaluated first, then exclusion rules.
+ # If there is still no match, resource is monitored.
+ resource_inclusions:
+ - api_groups:
+ - apps
+ kinds:
+ - '*'
+ - api_groups:
+ - ''
+ kinds:
+ - 'ConfigMap'
+ # Holds the API groups and kinds of resources to exclude from gitops watch.
+ # Inclusion rules are evaluated first, then exclusion rules.
+ # If there is still no match, resource is monitored.
+ resource_exclusions:
+ - api_groups:
+ - '*'
+ kinds:
+ - '*'
+ # Namespace to use if not set explicitly in object manifest.
+ default_namespace: my-ns
+ # Paths inside of the repository to scan for manifest files.
+ # Directories with names starting with a dot are ignored.
+ paths:
+ # Read all .yaml files from team1/app1 directory.
+ # See https://github.com/bmatcuk/doublestar#about and
+ # https://pkg.go.dev/github.com/bmatcuk/doublestar/v2#Match for globbing rules.
+ - glob: '/team1/app1/*.yaml'
+ # Read all .yaml files from team2/apps and all subdirectories
+ - glob: '/team2/apps/**/*.yaml'
+ # If 'paths' is not specified or is an empty list, the configuration below is used
+ - glob: '/**/*.{yaml,yml,json}'
+```
diff --git a/doc/user/packages/maven_repository/index.md b/doc/user/packages/maven_repository/index.md
index 5d0d64b310d..fcfc8724205 100644
--- a/doc/user/packages/maven_repository/index.md
+++ b/doc/user/packages/maven_repository/index.md
@@ -707,23 +707,23 @@ You can create a new package each time the `master` branch is updated.
1. Make sure your `pom.xml` file includes the following.
You can either let Maven use the CI environment variables, as shown in this example,
- or you can hard code your project's ID.
+ or you can hard code your server's hostname and project's ID.
```xml
<repositories>
<repository>
<id>gitlab-maven</id>
- <url>https://gitlab.example.com/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
+ <url>${env.CI_SERVER_URL}/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>gitlab-maven</id>
- <url>https://gitlab.example.com/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
+ <url>${env.CI_SERVER_URL}/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
</repository>
<snapshotRepository>
<id>gitlab-maven</id>
- <url>https://gitlab.example.com/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
+ <url>${env.CI_SERVER_URL}/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
</snapshotRepository>
</distributionManagement>
```
diff --git a/lib/api/entities/project_statistics.rb b/lib/api/entities/project_statistics.rb
index 32201e88eaf..70980e670b0 100644
--- a/lib/api/entities/project_statistics.rb
+++ b/lib/api/entities/project_statistics.rb
@@ -10,6 +10,7 @@ module API
expose :lfs_objects_size
expose :build_artifacts_size, as: :job_artifacts_size
expose :snippets_size
+ expose :packages_size
end
end
end
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index 0364ba2ad9e..2fdbbce8606 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -6,7 +6,7 @@ module API
extend ActiveSupport::Concern
extend Grape::API::Helpers
- STATISTICS_SORT_PARAMS = %w[storage_size repository_size wiki_size].freeze
+ STATISTICS_SORT_PARAMS = %w[storage_size repository_size wiki_size packages_size].freeze
params :optional_project_params_ce do
optional :description, type: String, desc: 'The description of the project'
diff --git a/lib/gitlab/whats_new.rb b/lib/gitlab/whats_new.rb
deleted file mode 100644
index 69ccb48c544..00000000000
--- a/lib/gitlab/whats_new.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module WhatsNew
- CACHE_DURATION = 1.hour
- WHATS_NEW_FILES_PATH = Rails.root.join('data', 'whats_new', '*.yml')
-
- private
-
- def whats_new_release_items(page: 1)
- Rails.cache.fetch(whats_new_items_cache_key(page), expires_in: CACHE_DURATION) do
- index = page - 1
- file_path = whats_new_file_paths[index]
-
- next if file_path.nil?
-
- file = File.read(file_path)
-
- items = YAML.safe_load(file, permitted_classes: [Date])
-
- items if items.is_a?(Array)
- end
- rescue => e
- Gitlab::ErrorTracking.track_exception(e, page: page)
-
- nil
- end
-
- def whats_new_file_paths
- @whats_new_file_paths ||= Rails.cache.fetch('whats_new:file_paths', expires_in: CACHE_DURATION) do
- Dir.glob(WHATS_NEW_FILES_PATH).sort.reverse
- end
- end
-
- def whats_new_items_cache_key(page)
- filename = /\d*\_\d*\_\d*/.match(whats_new_file_paths&.first)
- "whats_new:release_items:file-#{filename}:page-#{page}"
- end
- end
-end
diff --git a/spec/features/markdown/mermaid_spec.rb b/spec/features/markdown/mermaid_spec.rb
index bdb549326fa..9875fda17a9 100644
--- a/spec/features/markdown/mermaid_spec.rb
+++ b/spec/features/markdown/mermaid_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe 'Mermaid rendering', :js do
expect(page.html.scan(expected).count).to be(4)
end
- it 'renders only 2 Mermaid blocks and ', :js, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/234081' } do
+ it 'renders only 2 Mermaid blocks and ', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/234081' do
description = <<~MERMAID
```mermaid
graph LR
@@ -71,7 +71,7 @@ RSpec.describe 'Mermaid rendering', :js do
end
end
- it 'correctly sizes mermaid diagram inside <details> block', :js do
+ it 'correctly sizes mermaid diagram inside <details> block' do
description = <<~MERMAID
<details>
<summary>Click to show diagram</summary>
@@ -102,7 +102,7 @@ RSpec.describe 'Mermaid rendering', :js do
end
end
- it 'correctly sizes mermaid diagram block', :js do
+ it 'correctly sizes mermaid diagram block' do
description = <<~MERMAID
```mermaid
graph TD;
@@ -121,7 +121,7 @@ RSpec.describe 'Mermaid rendering', :js do
expect(page).to have_css('svg.mermaid[style*="max-width"][width="100%"]')
end
- it 'display button when diagram exceeds length', :js do
+ it 'display button when diagram exceeds length', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/287806' do
graph_edges = "A-->B;B-->A;" * 420
description = <<~MERMAID
diff --git a/spec/fixtures/whats_new/20201225_01_01.yml b/spec/fixtures/whats_new/20201225_01_01.yml
index 06db95be44f..48248757b9a 100644
--- a/spec/fixtures/whats_new/20201225_01_01.yml
+++ b/spec/fixtures/whats_new/20201225_01_01.yml
@@ -1,2 +1,5 @@
---
- title: It's gonna be a bright
+ self-managed: true
+ gitlab-com: false
+ packages: ["Premium", "Ultimate"]
diff --git a/spec/fixtures/whats_new/20201225_01_02.yml b/spec/fixtures/whats_new/20201225_01_02.yml
index 91b0bd7036e..f0fbc036698 100644
--- a/spec/fixtures/whats_new/20201225_01_02.yml
+++ b/spec/fixtures/whats_new/20201225_01_02.yml
@@ -1,2 +1,5 @@
---
- title: bright
+ self-managed: true
+ gitlab-com: false
+ packages: ["Premium", "Ultimate"]
diff --git a/spec/fixtures/whats_new/20201225_01_05.yml b/spec/fixtures/whats_new/20201225_01_05.yml
index 7c95e386f00..152609296c9 100644
--- a/spec/fixtures/whats_new/20201225_01_05.yml
+++ b/spec/fixtures/whats_new/20201225_01_05.yml
@@ -1,3 +1,10 @@
---
- title: bright and sunshinin' day
+ self-managed: true
+ gitlab-com: false
+ packages: ["Premium", "Ultimate"]
release: '01.05'
+- title: I think I can make it now the pain is gone
+ self-managed: false
+ gitlab-com: true
+ packages: ["Premium", "Ultimate"]
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
index b8cd1469179..ad21e6e6f4f 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
@@ -6,6 +6,7 @@ import ConflictsComponent from '~/vue_merge_request_widget/components/states/mr_
describe('MRWidgetConflicts', () => {
let vm;
+ let mergeRequestWidgetGraphql = null;
const path = '/conflicts';
function createComponent(propsData = {}) {
@@ -13,7 +14,35 @@ describe('MRWidgetConflicts', () => {
vm = shallowMount(localVue.extend(ConflictsComponent), {
propsData,
+ provide: {
+ glFeatures: {
+ mergeRequestWidgetGraphql,
+ },
+ },
+ mocks: {
+ $apollo: {
+ queries: {
+ userPermissions: { loading: false },
+ stateData: { loading: false },
+ },
+ },
+ },
});
+
+ if (mergeRequestWidgetGraphql) {
+ vm.setData({
+ userPermissions: {
+ canMerge: propsData.mr.canMerge,
+ pushToSourceBranch: propsData.mr.canPushToSourceBranch,
+ },
+ stateData: {
+ shouldBeRebased: propsData.mr.shouldBeRebased,
+ sourceBranchProtected: propsData.mr.sourceBranchProtected,
+ },
+ });
+ }
+
+ return vm.vm.$nextTick();
}
beforeEach(() => {
@@ -21,206 +50,215 @@ describe('MRWidgetConflicts', () => {
});
afterEach(() => {
+ mergeRequestWidgetGraphql = null;
vm.destroy();
});
- // There are two permissions we need to consider:
- //
- // 1. Is the user allowed to merge to the target branch?
- // 2. Is the user allowed to push to the source branch?
- //
- // This yields 4 possible permutations that we need to test, and
- // we test them below. A user who can push to the source
- // branch should be allowed to resolve conflicts. This is
- // consistent with what the backend does.
- describe('when allowed to merge but not allowed to push to source branch', () => {
- beforeEach(() => {
- createComponent({
- mr: {
- canMerge: true,
- canPushToSourceBranch: false,
- conflictResolutionPath: path,
- conflictsDocsPath: '',
- },
+ [false, true].forEach(featureEnabled => {
+ describe(`with GraphQL feature flag ${featureEnabled ? 'enabled' : 'disabled'}`, () => {
+ beforeEach(() => {
+ mergeRequestWidgetGraphql = featureEnabled;
});
- });
-
- it('should tell you about conflicts without bothering other people', () => {
- expect(vm.text()).toContain('There are merge conflicts');
- expect(vm.text()).not.toContain('ask someone with write access');
- });
-
- it('should not allow you to resolve the conflicts', () => {
- expect(vm.text()).not.toContain('Resolve conflicts');
- });
-
- it('should have merge buttons', () => {
- const mergeLocallyButton = vm.find('.js-merge-locally-button');
-
- expect(mergeLocallyButton.text()).toContain('Merge locally');
- });
- });
- describe('when not allowed to merge but allowed to push to source branch', () => {
- beforeEach(() => {
- createComponent({
- mr: {
- canMerge: false,
- canPushToSourceBranch: true,
- conflictResolutionPath: path,
- conflictsDocsPath: '',
- },
- });
- });
-
- it('should tell you about conflicts', () => {
- expect(vm.text()).toContain('There are merge conflicts');
- expect(vm.text()).toContain('ask someone with write access');
- });
-
- it('should allow you to resolve the conflicts', () => {
- const resolveButton = vm.find('.js-resolve-conflicts-button');
-
- expect(resolveButton.text()).toContain('Resolve conflicts');
- expect(resolveButton.attributes('href')).toEqual(path);
- });
-
- it('should not have merge buttons', () => {
- expect(vm.text()).not.toContain('Merge locally');
- });
- });
-
- describe('when allowed to merge and push to source branch', () => {
- beforeEach(() => {
- createComponent({
- mr: {
- canMerge: true,
- canPushToSourceBranch: true,
- conflictResolutionPath: path,
- conflictsDocsPath: '',
- },
+ // There are two permissions we need to consider:
+ //
+ // 1. Is the user allowed to merge to the target branch?
+ // 2. Is the user allowed to push to the source branch?
+ //
+ // This yields 4 possible permutations that we need to test, and
+ // we test them below. A user who can push to the source
+ // branch should be allowed to resolve conflicts. This is
+ // consistent with what the backend does.
+ describe('when allowed to merge but not allowed to push to source branch', () => {
+ beforeEach(async () => {
+ await createComponent({
+ mr: {
+ canMerge: true,
+ canPushToSourceBranch: false,
+ conflictResolutionPath: path,
+ conflictsDocsPath: '',
+ },
+ });
+ });
+
+ it('should tell you about conflicts without bothering other people', () => {
+ expect(vm.text()).toContain('There are merge conflicts');
+ expect(vm.text()).not.toContain('ask someone with write access');
+ });
+
+ it('should not allow you to resolve the conflicts', () => {
+ expect(vm.text()).not.toContain('Resolve conflicts');
+ });
+
+ it('should have merge buttons', () => {
+ const mergeLocallyButton = vm.find('.js-merge-locally-button');
+
+ expect(mergeLocallyButton.text()).toContain('Merge locally');
+ });
});
- });
-
- it('should tell you about conflicts without bothering other people', () => {
- expect(vm.text()).toContain('There are merge conflicts');
- expect(vm.text()).not.toContain('ask someone with write access');
- });
- it('should allow you to resolve the conflicts', () => {
- const resolveButton = vm.find('.js-resolve-conflicts-button');
-
- expect(resolveButton.text()).toContain('Resolve conflicts');
- expect(resolveButton.attributes('href')).toEqual(path);
- });
-
- it('should have merge buttons', () => {
- const mergeLocallyButton = vm.find('.js-merge-locally-button');
-
- expect(mergeLocallyButton.text()).toContain('Merge locally');
- });
- });
-
- describe('when user does not have permission to push to source branch', () => {
- it('should show proper message', () => {
- createComponent({
- mr: {
- canMerge: false,
- canPushToSourceBranch: false,
- conflictsDocsPath: '',
- },
+ describe('when not allowed to merge but allowed to push to source branch', () => {
+ beforeEach(async () => {
+ await createComponent({
+ mr: {
+ canMerge: false,
+ canPushToSourceBranch: true,
+ conflictResolutionPath: path,
+ conflictsDocsPath: '',
+ },
+ });
+ });
+
+ it('should tell you about conflicts', () => {
+ expect(vm.text()).toContain('There are merge conflicts');
+ expect(vm.text()).toContain('ask someone with write access');
+ });
+
+ it('should allow you to resolve the conflicts', () => {
+ const resolveButton = vm.find('.js-resolve-conflicts-button');
+
+ expect(resolveButton.text()).toContain('Resolve conflicts');
+ expect(resolveButton.attributes('href')).toEqual(path);
+ });
+
+ it('should not have merge buttons', () => {
+ expect(vm.text()).not.toContain('Merge locally');
+ });
});
- expect(
- vm
- .text()
- .trim()
- .replace(/\s\s+/g, ' '),
- ).toContain('ask someone with write access');
- });
-
- it('should not have action buttons', () => {
- createComponent({
- mr: {
- canMerge: false,
- canPushToSourceBranch: false,
- conflictsDocsPath: '',
- },
+ describe('when allowed to merge and push to source branch', () => {
+ beforeEach(async () => {
+ await createComponent({
+ mr: {
+ canMerge: true,
+ canPushToSourceBranch: true,
+ conflictResolutionPath: path,
+ conflictsDocsPath: '',
+ },
+ });
+ });
+
+ it('should tell you about conflicts without bothering other people', () => {
+ expect(vm.text()).toContain('There are merge conflicts');
+ expect(vm.text()).not.toContain('ask someone with write access');
+ });
+
+ it('should allow you to resolve the conflicts', () => {
+ const resolveButton = vm.find('.js-resolve-conflicts-button');
+
+ expect(resolveButton.text()).toContain('Resolve conflicts');
+ expect(resolveButton.attributes('href')).toEqual(path);
+ });
+
+ it('should have merge buttons', () => {
+ const mergeLocallyButton = vm.find('.js-merge-locally-button');
+
+ expect(mergeLocallyButton.text()).toContain('Merge locally');
+ });
});
- expect(vm.find('.js-resolve-conflicts-button').exists()).toBe(false);
- expect(vm.find('.js-merge-locally-button').exists()).toBe(false);
- });
-
- it('should not have resolve button when no conflict resolution path', () => {
- createComponent({
- mr: {
- canMerge: true,
- conflictResolutionPath: null,
- conflictsDocsPath: '',
- },
+ describe('when user does not have permission to push to source branch', () => {
+ it('should show proper message', async () => {
+ await createComponent({
+ mr: {
+ canMerge: false,
+ canPushToSourceBranch: false,
+ conflictsDocsPath: '',
+ },
+ });
+
+ expect(
+ vm
+ .text()
+ .trim()
+ .replace(/\s\s+/g, ' '),
+ ).toContain('ask someone with write access');
+ });
+
+ it('should not have action buttons', async () => {
+ await createComponent({
+ mr: {
+ canMerge: false,
+ canPushToSourceBranch: false,
+ conflictsDocsPath: '',
+ },
+ });
+
+ expect(vm.find('.js-resolve-conflicts-button').exists()).toBe(false);
+ expect(vm.find('.js-merge-locally-button').exists()).toBe(false);
+ });
+
+ it('should not have resolve button when no conflict resolution path', async () => {
+ await createComponent({
+ mr: {
+ canMerge: true,
+ conflictResolutionPath: null,
+ conflictsDocsPath: '',
+ },
+ });
+
+ expect(vm.find('.js-resolve-conflicts-button').exists()).toBe(false);
+ });
});
- expect(vm.find('.js-resolve-conflicts-button').exists()).toBe(false);
- });
- });
-
- describe('when fast-forward or semi-linear merge enabled', () => {
- it('should tell you to rebase locally', () => {
- createComponent({
- mr: {
- shouldBeRebased: true,
- conflictsDocsPath: '',
- },
+ describe('when fast-forward or semi-linear merge enabled', () => {
+ it('should tell you to rebase locally', async () => {
+ await createComponent({
+ mr: {
+ shouldBeRebased: true,
+ conflictsDocsPath: '',
+ },
+ });
+
+ expect(removeBreakLine(vm.text()).trim()).toContain(
+ 'Fast-forward merge is not possible. To merge this request, first rebase locally.',
+ );
+ });
});
- expect(removeBreakLine(vm.text()).trim()).toContain(
- 'Fast-forward merge is not possible. To merge this request, first rebase locally.',
- );
- });
- });
-
- describe('when source branch protected', () => {
- beforeEach(() => {
- createComponent({
- mr: {
- canMerge: true,
- canPushToSourceBranch: true,
- conflictResolutionPath: TEST_HOST,
- sourceBranchProtected: true,
- conflictsDocsPath: '',
- },
+ describe('when source branch protected', () => {
+ beforeEach(async () => {
+ await createComponent({
+ mr: {
+ canMerge: true,
+ canPushToSourceBranch: true,
+ conflictResolutionPath: TEST_HOST,
+ sourceBranchProtected: true,
+ conflictsDocsPath: '',
+ },
+ });
+ });
+
+ it('sets resolve button as disabled', () => {
+ expect(vm.find('.js-resolve-conflicts-button').attributes('disabled')).toBe('true');
+ });
+
+ it('renders popover', () => {
+ expect($.fn.popover).toHaveBeenCalled();
+ });
});
- });
-
- it('sets resolve button as disabled', () => {
- expect(vm.find('.js-resolve-conflicts-button').attributes('disabled')).toBe('true');
- });
- it('renders popover', () => {
- expect($.fn.popover).toHaveBeenCalled();
- });
- });
-
- describe('when source branch not protected', () => {
- beforeEach(() => {
- createComponent({
- mr: {
- canMerge: true,
- canPushToSourceBranch: true,
- conflictResolutionPath: TEST_HOST,
- sourceBranchProtected: false,
- conflictsDocsPath: '',
- },
+ describe('when source branch not protected', () => {
+ beforeEach(async () => {
+ await createComponent({
+ mr: {
+ canMerge: true,
+ canPushToSourceBranch: true,
+ conflictResolutionPath: TEST_HOST,
+ sourceBranchProtected: false,
+ conflictsDocsPath: '',
+ },
+ });
+ });
+
+ it('sets resolve button as disabled', () => {
+ expect(vm.find('.js-resolve-conflicts-button').attributes('disabled')).toBe(undefined);
+ });
+
+ it('renders popover', () => {
+ expect($.fn.popover).not.toHaveBeenCalled();
+ });
});
});
-
- it('sets resolve button as disabled', () => {
- expect(vm.find('.js-resolve-conflicts-button').attributes('disabled')).toBe(undefined);
- });
-
- it('renders popover', () => {
- expect($.fn.popover).not.toHaveBeenCalled();
- });
});
});
diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb
index 8800250b103..c929a93a9eb 100644
--- a/spec/graphql/types/merge_request_type_spec.rb
+++ b/spec/graphql/types/merge_request_type_spec.rb
@@ -27,7 +27,7 @@ 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 approved_by
+ conflicts auto_merge_enabled approved_by source_branch_protected
]
if Gitlab.ee?
diff --git a/spec/helpers/whats_new_helper_spec.rb b/spec/helpers/whats_new_helper_spec.rb
index 1c8684de75c..cdb4fc60629 100644
--- a/spec/helpers/whats_new_helper_spec.rb
+++ b/spec/helpers/whats_new_helper_spec.rb
@@ -3,22 +3,22 @@
require 'spec_helper'
RSpec.describe WhatsNewHelper do
- let(:fixture_dir_glob) { Dir.glob(File.join('spec', 'fixtures', 'whats_new', '*.yml')) }
-
describe '#whats_new_storage_key' do
subject { helper.whats_new_storage_key }
context 'when version exist' do
+ let(:release_item) { double(:item) }
+
before do
- allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
+ allow(ReleaseHighlight).to receive(:most_recent_version).and_return(84.0)
end
- it { is_expected.to eq('display-whats-new-notification-01.05') }
+ it { is_expected.to eq('display-whats-new-notification-84.0') }
end
- context 'when recent release items do NOT exist' do
+ context 'when most recent release highlights do NOT exist' do
before do
- allow(helper).to receive(:whats_new_release_items).and_return(nil)
+ allow(ReleaseHighlight).to receive(:most_recent_version).and_return(nil)
end
it { is_expected.to be_nil }
@@ -30,31 +30,18 @@ RSpec.describe WhatsNewHelper do
context 'when recent release items exist' do
it 'returns the count from the most recent file' do
- expect(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
+ allow(ReleaseHighlight).to receive(:most_recent_item_count).and_return(1)
expect(subject).to eq(1)
end
end
context 'when recent release items do NOT exist' do
- before do
- allow(YAML).to receive(:safe_load).and_raise
-
- expect(Gitlab::ErrorTracking).to receive(:track_exception)
- end
+ it 'returns nil' do
+ allow(ReleaseHighlight).to receive(:most_recent_item_count).and_return(nil)
- it 'fails gracefully and logs an error' do
expect(subject).to be_nil
end
end
end
-
- # Testing this important private method here because the request spec required multiple confusing mocks and felt wrong and overcomplicated
- describe '#whats_new_items_cache_key' do
- it 'returns a key containing the most recent file name and page parameter' do
- allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
-
- expect(helper.send(:whats_new_items_cache_key, 2)).to eq('whats_new:release_items:file-20201225_01_05:page-2')
- end
- end
end
diff --git a/spec/models/release_highlight_spec.rb b/spec/models/release_highlight_spec.rb
new file mode 100644
index 00000000000..b7817a04134
--- /dev/null
+++ b/spec/models/release_highlight_spec.rb
@@ -0,0 +1,135 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ReleaseHighlight do
+ describe '#paginated' do
+ let(:fixture_dir_glob) { Dir.glob(File.join('spec', 'fixtures', 'whats_new', '*.yml')) }
+ let(:cache_mock) { double(:cache_mock) }
+ let(:dot_com) { false }
+
+ before do
+ allow(Gitlab).to receive(:com?).and_return(dot_com)
+ allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
+
+ expect(Rails).to receive(:cache).twice.and_return(cache_mock)
+ expect(cache_mock).to receive(:fetch).with('release_highlight:file_paths', expires_in: 1.hour).and_yield
+ end
+
+ after do
+ ReleaseHighlight.instance_variable_set(:@file_paths, nil)
+ end
+
+ context 'with page param' do
+ subject { ReleaseHighlight.paginated(page: page) }
+
+ before do
+ allow(cache_mock).to receive(:fetch).and_yield
+ end
+
+ context 'when there is another page of results' do
+ let(:page) { 2 }
+
+ it 'responds with paginated results' do
+ expect(subject[:items].first['title']).to eq('bright')
+ expect(subject[:next_page]).to eq(3)
+ end
+ end
+
+ context 'when there is NOT another page of results' do
+ let(:page) { 3 }
+
+ it 'responds with paginated results and no next_page' do
+ expect(subject[:items].first['title']).to eq("It's gonna be a bright")
+ expect(subject[:next_page]).to eq(nil)
+ end
+ end
+
+ context 'when that specific page does not exist' do
+ let(:page) { 84 }
+
+ it 'returns nil' do
+ expect(subject).to be_nil
+ end
+ end
+ end
+
+ context 'with no page param' do
+ subject { ReleaseHighlight.paginated }
+
+ before do
+ expect(cache_mock).to receive(:fetch).with('release_highlight:items:file-20201225_01_05:page-1', expires_in: 1.hour).and_yield
+ end
+
+ it 'returns platform specific items and uses a cache key' do
+ expect(subject[:items].count).to eq(1)
+ expect(subject[:items].first['title']).to eq("bright and sunshinin' day")
+ expect(subject[:next_page]).to eq(2)
+ end
+
+ context 'when Gitlab.com' do
+ let(:dot_com) { true }
+
+ it 'responds with a different set of data' do
+ expect(subject[:items].count).to eq(1)
+ expect(subject[:items].first['title']).to eq("I think I can make it now the pain is gone")
+ end
+ end
+
+ context 'when recent release items do NOT exist' do
+ before do
+ allow(YAML).to receive(:safe_load).and_raise(Psych::Exception)
+
+ expect(Gitlab::ErrorTracking).to receive(:track_exception)
+ end
+
+ it 'fails gracefully and logs an error' do
+ expect(subject).to be_nil
+ end
+ end
+ end
+ end
+
+ describe '.most_recent_version' do
+ subject { ReleaseHighlight.most_recent_version }
+
+ context 'when version exist' do
+ let(:release_item) { double(:item) }
+
+ before do
+ allow(ReleaseHighlight).to receive(:paginated).and_return({ items: [release_item] })
+ allow(release_item).to receive(:[]).with('release').and_return(84.0)
+ end
+
+ it { is_expected.to eq(84.0) }
+ end
+
+ context 'when most recent release highlights do NOT exist' do
+ before do
+ allow(ReleaseHighlight).to receive(:paginated).and_return(nil)
+ end
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#most_recent_item_count' do
+ subject { ReleaseHighlight.most_recent_item_count }
+
+ context 'when recent release items exist' do
+ it 'returns the count from the most recent file' do
+ allow(ReleaseHighlight).to receive(:paginated).and_return({ items: [double(:item)] })
+
+ expect(subject).to eq(1)
+ end
+ end
+
+ context 'when recent release items do NOT exist' do
+ it 'returns nil' do
+ allow(ReleaseHighlight).to receive(:paginated).and_return(nil)
+
+ expect(subject).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/presenters/gitlab/whats_new/item_presenter_spec.rb b/spec/presenters/gitlab/whats_new/item_presenter_spec.rb
new file mode 100644
index 00000000000..b7b711e04c7
--- /dev/null
+++ b/spec/presenters/gitlab/whats_new/item_presenter_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::WhatsNew::ItemPresenter do
+ let(:present) { Gitlab::WhatsNew::ItemPresenter.present(item) }
+ let(:item) { { "packages" => %w(Premium Ultimate) } }
+ let(:gitlab_com) { true }
+
+ before do
+ allow(Gitlab).to receive(:com?).and_return(gitlab_com)
+ end
+
+ describe '.present' do
+ context 'when on Gitlab.com' do
+ it 'transforms package names to gitlab.com friendly package names' do
+ expect(present).to eq({ "packages" => %w(Silver Gold) })
+ end
+ end
+
+ context 'when not on Gitlab.com' do
+ let(:gitlab_com) { false }
+
+ it 'does not transform package names' do
+ expect(present).to eq({ "packages" => %w(Premium Ultimate) })
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 4a792fc218d..eb3e610934d 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -254,7 +254,7 @@ RSpec.describe API::Projects do
statistics = json_response.find { |p| p['id'] == project.id }['statistics']
expect(statistics).to be_present
- expect(statistics).to include('commit_count', 'storage_size', 'repository_size', 'wiki_size', 'lfs_objects_size', 'job_artifacts_size', 'snippets_size')
+ expect(statistics).to include('commit_count', 'storage_size', 'repository_size', 'wiki_size', 'lfs_objects_size', 'job_artifacts_size', 'snippets_size', 'packages_size')
end
it "does not include license by default" do
@@ -619,7 +619,7 @@ RSpec.describe API::Projects do
end
context 'sorting by project statistics' do
- %w(repository_size storage_size wiki_size).each do |order_by|
+ %w(repository_size storage_size wiki_size packages_size).each do |order_by|
context "sorting by #{order_by}" do
before do
ProjectStatistics.update_all(order_by => 100)
diff --git a/spec/requests/whats_new_controller_spec.rb b/spec/requests/whats_new_controller_spec.rb
index c04a6b00a93..30d741ee0f0 100644
--- a/spec/requests/whats_new_controller_spec.rb
+++ b/spec/requests/whats_new_controller_spec.rb
@@ -5,28 +5,30 @@ require 'spec_helper'
RSpec.describe WhatsNewController do
describe 'whats_new_path' do
context 'with whats_new_drawer feature enabled' do
- let(:fixture_dir_glob) { Dir.glob(File.join('spec', 'fixtures', 'whats_new', '*.yml')) }
-
before do
stub_feature_flags(whats_new_drawer: true)
- allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
end
context 'with no page param' do
+ let(:most_recent) { { items: [item], next_page: 2 } }
+ let(:item) { double(:item) }
+
it 'responds with paginated data and headers' do
+ allow(ReleaseHighlight).to receive(:paginated).with(page: 1).and_return(most_recent)
+ allow(Gitlab::WhatsNew::ItemPresenter).to receive(:present).with(item).and_return(item)
+
get whats_new_path, xhr: true
- expect(response.body).to eq([{ title: "bright and sunshinin' day", release: "01.05" }].to_json)
+ expect(response.body).to eq(most_recent[:items].to_json)
expect(response.headers['X-Next-Page']).to eq(2)
end
end
context 'with page param' do
- it 'responds with paginated data and headers' do
- get whats_new_path(page: 2), xhr: true
+ it 'passes the page parameter' do
+ expect(ReleaseHighlight).to receive(:paginated).with(page: 2).and_call_original
- expect(response.body).to eq([{ title: 'bright' }].to_json)
- expect(response.headers['X-Next-Page']).to eq(3)
+ get whats_new_path(page: 2), xhr: true
end
it 'returns a 404 if page param is negative' do
@@ -34,14 +36,6 @@ RSpec.describe WhatsNewController do
expect(response).to have_gitlab_http_status(:not_found)
end
-
- context 'when there are no more paginated results' do
- it 'responds with nil X-Next-Page header' do
- get whats_new_path(page: 3), xhr: true
- expect(response.body).to eq([{ title: "It's gonna be a bright" }].to_json)
- expect(response.headers['X-Next-Page']).to be nil
- end
- end
end
end