diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index f24372b80df..be197a50775 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -138,6 +138,7 @@ export default {
snippetsAccessLevel: featureAccessLevel.EVERYONE,
pagesAccessLevel: featureAccessLevel.EVERYONE,
metricsDashboardAccessLevel: featureAccessLevel.PROJECT_MEMBERS,
+ analyticsAccessLevel: featureAccessLevel.EVERYONE,
requirementsAccessLevel: featureAccessLevel.EVERYONE,
containerRegistryEnabled: true,
lfsEnabled: true,
@@ -241,6 +242,10 @@ export default {
featureAccessLevel.PROJECT_MEMBERS,
this.metricsDashboardAccessLevel,
);
+ this.analyticsAccessLevel = Math.min(
+ featureAccessLevel.PROJECT_MEMBERS,
+ this.analyticsAccessLevel,
+ );
this.requirementsAccessLevel = Math.min(
featureAccessLevel.PROJECT_MEMBERS,
this.requirementsAccessLevel,
@@ -266,6 +271,8 @@ export default {
this.snippetsAccessLevel = featureAccessLevel.EVERYONE;
if (this.pagesAccessLevel === featureAccessLevel.PROJECT_MEMBERS)
this.pagesAccessLevel = featureAccessLevel.EVERYONE;
+ if (this.analyticsAccessLevel > featureAccessLevel.NOT_ENABLED)
+ this.analyticsAccessLevel = featureAccessLevel.EVERYONE;
if (this.metricsDashboardAccessLevel === featureAccessLevel.PROJECT_MEMBERS)
this.metricsDashboardAccessLevel = featureAccessLevel.EVERYONE;
if (this.requirementsAccessLevel === featureAccessLevel.PROJECT_MEMBERS)
@@ -494,6 +501,17 @@ export default {
/>
+
+
+
`;
}
- return userSelect.renderRow(options.issuableType, user, selected, username, img);
+ return userSelect.renderRow(
+ options.issuableType,
+ user,
+ selected,
+ username,
+ img,
+ elsClassName,
+ );
},
});
});
@@ -746,8 +754,17 @@ UsersSelect.prototype.users = function(query, options, callback) {
...getAjaxUsersSelectParams(options, AJAX_USERS_SELECT_PARAMS_MAP),
};
- if (options.issuableType === 'merge_request') {
+ const isMergeRequest = options.issuableType === 'merge_request';
+ const isEditMergeRequest = !options.issuableType && (options.iid && options.targetBranch);
+ const isNewMergeRequest = !options.issuableType && (!options.iid && options.targetBranch);
+
+ if (isMergeRequest || isEditMergeRequest || isNewMergeRequest) {
params.merge_request_iid = options.iid || null;
+ params.approval_rules = true;
+ }
+
+ if (isNewMergeRequest) {
+ params.target_branch = options.targetBranch || null;
}
return axios.get(url, { params }).then(({ data }) => {
@@ -762,7 +779,14 @@ UsersSelect.prototype.buildUrl = function(url) {
return url;
};
-UsersSelect.prototype.renderRow = function(issuableType, user, selected, username, img) {
+UsersSelect.prototype.renderRow = function(
+ issuableType,
+ user,
+ selected,
+ username,
+ img,
+ elsClassName,
+) {
const tooltip = issuableType === 'merge_request' && !user.can_merge ? __('Cannot merge') : '';
const tooltipClass = tooltip ? `has-tooltip` : '';
const selectedClass = selected === true ? 'is-active' : '';
@@ -776,10 +800,15 @@ UsersSelect.prototype.renderRow = function(issuableType, user, selected, usernam
@@ -802,4 +831,22 @@ UsersSelect.prototype.renderRowAvatar = function(issuableType, user, img) {
`;
};
+UsersSelect.prototype.renderApprovalRules = function(elsClassName, approvalRules = []) {
+ if (!gon.features?.reviewerApprovalRules || !elsClassName?.includes('reviewer')) {
+ return '';
+ }
+
+ const count = approvalRules.length;
+ const [rule] = approvalRules;
+ const countText = sprintf(__('(+%{count} rules)'), { count });
+ const renderApprovalRulesCount = count > 1 ? `${countText}` : '';
+
+ return count
+ ? `
+ ${rule.name}
+ ${renderApprovalRulesCount}
+
`
+ : '';
+};
+
export default UsersSelect;
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index 2aa7e9038e9..7d3e7759081 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -14,6 +14,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
before_action do
push_frontend_feature_flag(:merge_request_reviewers, @project, default_enabled: true)
push_frontend_feature_flag(:mr_collapsed_approval_rules, @project)
+ push_frontend_feature_flag(:reviewer_approval_rules, @project)
end
def new
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index b48aa02a81b..382fbfaac25 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -53,6 +53,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:vue_issuable_sidebar, @project.group)
push_frontend_feature_flag(:merge_request_reviewers, @project, default_enabled: true)
push_frontend_feature_flag(:mr_collapsed_approval_rules, @project)
+ push_frontend_feature_flag(:reviewer_approval_rules, @project)
end
around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :discussions]
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index ccc1a972a7d..3744517934a 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -387,6 +387,7 @@ class ProjectsController < Projects::ApplicationController
wiki_access_level
pages_access_level
metrics_dashboard_access_level
+ analytics_access_level
operations_access_level
]
end
diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb
index 8a8d708b0b2..d0276c91316 100644
--- a/app/helpers/form_helper.rb
+++ b/app/helpers/form_helper.rb
@@ -55,7 +55,7 @@ module FormHelper
dropdown_data
end
- def reviewers_dropdown_options(issuable_type)
+ def reviewers_dropdown_options(issuable_type, iid = nil, target_branch = nil)
dropdown_data = {
toggle_class: 'js-reviewer-search js-multiselect js-save-user-data',
title: 'Request review from',
@@ -78,6 +78,14 @@ module FormHelper
}
}
+ if iid
+ dropdown_data[:data][:iid] = iid
+ end
+
+ if target_branch
+ dropdown_data[:data][:target_branch] = target_branch
+ end
+
if merge_request_supports_multiple_reviewers?
dropdown_data = multiple_reviewers_dropdown_options(dropdown_data)
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 291ce2b1e8b..80206654cd1 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -463,7 +463,8 @@ module ProjectsHelper
issues: :read_issue,
project_members: :read_project_member,
wiki: :read_wiki,
- feature_flags: :read_feature_flag
+ feature_flags: :read_feature_flag,
+ analytics: :read_analytics
}
end
@@ -625,6 +626,7 @@ module ProjectsHelper
wikiAccessLevel: feature.wiki_access_level,
snippetsAccessLevel: feature.snippets_access_level,
pagesAccessLevel: feature.pages_access_level,
+ analyticsAccessLevel: feature.analytics_access_level,
containerRegistryEnabled: !!project.container_registry_enabled,
lfsEnabled: !!project.lfs_enabled,
emailsDisabled: project.emails_disabled?,
diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb
index 10690de1c37..07bec07e556 100644
--- a/app/models/concerns/project_features_compatibility.rb
+++ b/app/models/concerns/project_features_compatibility.rb
@@ -70,6 +70,10 @@ module ProjectFeaturesCompatibility
write_feature_attribute_string(:metrics_dashboard_access_level, value)
end
+ def analytics_access_level=(value)
+ write_feature_attribute_string(:analytics_access_level, value)
+ end
+
def operations_access_level=(value)
write_feature_attribute_string(:operations_access_level, value)
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 579df1abbe2..daa5605c2e0 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -385,10 +385,10 @@ class Project < ApplicationRecord
delegate :feature_available?, :builds_enabled?, :wiki_enabled?,
:merge_requests_enabled?, :forking_enabled?, :issues_enabled?,
- :pages_enabled?, :snippets_enabled?, :public_pages?, :private_pages?,
+ :pages_enabled?, :analytics_enabled?, :snippets_enabled?, :public_pages?, :private_pages?,
:merge_requests_access_level, :forking_access_level, :issues_access_level,
:wiki_access_level, :snippets_access_level, :builds_access_level,
- :repository_access_level, :pages_access_level, :metrics_dashboard_access_level,
+ :repository_access_level, :pages_access_level, :metrics_dashboard_access_level, :analytics_access_level,
:operations_enabled?, :operations_access_level, to: :project_feature, allow_nil: true
delegate :show_default_award_emojis, :show_default_award_emojis=,
:show_default_award_emojis?,
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index e4cecf70ab4..7b204cfb1c0 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -3,7 +3,7 @@
class ProjectFeature < ApplicationRecord
include Featurable
- FEATURES = %i(issues forking merge_requests wiki snippets builds repository pages metrics_dashboard operations).freeze
+ FEATURES = %i(issues forking merge_requests wiki snippets builds repository pages metrics_dashboard analytics operations).freeze
set_available_features(FEATURES)
@@ -44,6 +44,7 @@ class ProjectFeature < ApplicationRecord
default_value_for :snippets_access_level, value: ENABLED, allows_nil: false
default_value_for :wiki_access_level, value: ENABLED, allows_nil: false
default_value_for :repository_access_level, value: ENABLED, allows_nil: false
+ default_value_for :analytics_access_level, value: ENABLED, allows_nil: false
default_value_for :metrics_dashboard_access_level, value: PRIVATE, allows_nil: false
default_value_for :operations_access_level, value: ENABLED, allows_nil: false
diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb
index c11a7fea1c6..7605ef54d5b 100644
--- a/app/models/project_statistics.rb
+++ b/app/models/project_statistics.rb
@@ -73,8 +73,6 @@ class ProjectStatistics < ApplicationRecord
end
def update_uploads_size
- return uploads_size unless Feature.enabled?(:count_uploads_size_in_storage_stats, project)
-
self.uploads_size = project.uploads.sum(:size)
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 1bb96a4e02b..817f9d014eb 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -44,6 +44,7 @@ class Snippet < ApplicationRecord
has_many :notes, as: :noteable, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :user_mentions, class_name: "SnippetUserMention", dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
has_one :snippet_repository, inverse_of: :snippet
+ has_many :repository_storage_moves, class_name: 'SnippetRepositoryStorageMove', inverse_of: :container
# We need to add the `dependent` in order to call the after_destroy callback
has_one :statistics, class_name: 'SnippetStatistics', dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
diff --git a/app/models/snippet_repository_storage_move.rb b/app/models/snippet_repository_storage_move.rb
new file mode 100644
index 00000000000..a365569bfa8
--- /dev/null
+++ b/app/models/snippet_repository_storage_move.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+# SnippetRepositoryStorageMove are details of repository storage moves for a
+# snippet. For example, moving a snippet to another gitaly node to help
+# balance storage capacity.
+class SnippetRepositoryStorageMove < ApplicationRecord
+ extend ::Gitlab::Utils::Override
+ include RepositoryStorageMovable
+
+ belongs_to :container, class_name: 'Snippet', inverse_of: :repository_storage_moves, foreign_key: :snippet_id
+ alias_attribute :snippet, :container
+
+ override :schedule_repository_storage_update_worker
+ def schedule_repository_storage_update_worker
+ # TODO https://gitlab.com/gitlab-org/gitlab/-/issues/218991
+ end
+
+ private
+
+ override :error_key
+ def error_key
+ :snippet
+ end
+end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 93eb55329b5..403fb34803e 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -151,6 +151,7 @@ class ProjectPolicy < BasePolicy
builds
pages
metrics_dashboard
+ analytics
operations
]
@@ -216,6 +217,7 @@ class ProjectPolicy < BasePolicy
enable :award_emoji
enable :read_pages_content
enable :read_release
+ enable :read_analytics
end
# These abilities are not allowed to admins that are not members of the project,
@@ -442,6 +444,10 @@ class ProjectPolicy < BasePolicy
prevent(*create_read_update_admin_destroy(:snippet))
end
+ rule { analytics_disabled }.policy do
+ prevent(:read_analytics)
+ end
+
rule { wiki_disabled }.policy do
prevent(*create_read_update_admin_destroy(:wiki))
prevent(:download_wiki_code)
@@ -512,6 +518,7 @@ class ProjectPolicy < BasePolicy
enable :download_wiki_code
enable :read_cycle_analytics
enable :read_pages_content
+ enable :read_analytics
# NOTE: may be overridden by IssuePolicy
enable :read_issue
diff --git a/app/views/layouts/nav/sidebar/_analytics_links.html.haml b/app/views/layouts/nav/sidebar/_analytics_links.html.haml
index a99eb8cf457..970a1d5f2c7 100644
--- a/app/views/layouts/nav/sidebar/_analytics_links.html.haml
+++ b/app/views/layouts/nav/sidebar/_analytics_links.html.haml
@@ -4,7 +4,7 @@
- if navbar_links.any?
= nav_link(path: all_paths) do
- = link_to analytics_link.link, { data: { qa_selector: 'analytics_anchor' } } do
+ = link_to analytics_link.link, {class: 'shortcuts-analytics', data: { qa_selector: 'analytics_anchor' } } do
.nav-icon-container
= sprite_icon('chart')
%span.nav-item-name{ data: { qa_selector: 'analytics_link' } }
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 4e39b5a05c0..5cadabd5f90 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -324,7 +324,8 @@
= render_if_exists 'layouts/nav/sidebar/project_packages_link'
- = render 'layouts/nav/sidebar/analytics_links', links: project_analytics_navbar_links(@project, current_user)
+ - if project_nav_tab? :analytics
+ = render 'layouts/nav/sidebar/analytics_links', links: project_analytics_navbar_links(@project, current_user)
- if project_nav_tab?(:confluence)
- confluence_url = project_wikis_confluence_path(@project)
diff --git a/app/views/shared/issuable/form/_metadata_issuable_reviewer.html.haml b/app/views/shared/issuable/form/_metadata_issuable_reviewer.html.haml
index a4e2ce035cc..a0df007f8ca 100644
--- a/app/views/shared/issuable/form/_metadata_issuable_reviewer.html.haml
+++ b/app/views/shared/issuable/form/_metadata_issuable_reviewer.html.haml
@@ -7,6 +7,6 @@
- if issuable.reviewers.empty?
= hidden_field_tag "#{issuable.to_ability_name}[reviewer_ids][]", 0, id: nil, data: { meta: '' }
- = dropdown_tag(users_dropdown_label(issuable.reviewers), options: reviewers_dropdown_options(issuable.to_ability_name))
+ = dropdown_tag(users_dropdown_label(issuable.reviewers), options: reviewers_dropdown_options(issuable.to_ability_name, issuable.iid, issuable.target_branch))
- if Feature.enabled?(:mr_collapsed_approval_rules, @project)
= render_if_exists 'shared/issuable/approver_suggestion', issuable: issuable, presenter: presenter
diff --git a/changelogs/unreleased/224700_add_toggle_to_remove_analytics_item.yml b/changelogs/unreleased/224700_add_toggle_to_remove_analytics_item.yml
new file mode 100644
index 00000000000..635e75627ad
--- /dev/null
+++ b/changelogs/unreleased/224700_add_toggle_to_remove_analytics_item.yml
@@ -0,0 +1,5 @@
+---
+title: Add toggle to remove Analytics left nav item
+merge_request: 46011
+author:
+type: added
diff --git a/changelogs/unreleased/281950-cleanup-count_uploads_size_in_storage_stats.yml b/changelogs/unreleased/281950-cleanup-count_uploads_size_in_storage_stats.yml
new file mode 100644
index 00000000000..6e0fd69eaa8
--- /dev/null
+++ b/changelogs/unreleased/281950-cleanup-count_uploads_size_in_storage_stats.yml
@@ -0,0 +1,5 @@
+---
+title: Removed count_uploads_size_in_storage_stats feature flag
+merge_request: 49998
+author:
+type: other
diff --git a/config/feature_flags/development/reviewer_approval_rules.yml b/config/feature_flags/development/reviewer_approval_rules.yml
new file mode 100644
index 00000000000..97181ef2a36
--- /dev/null
+++ b/config/feature_flags/development/reviewer_approval_rules.yml
@@ -0,0 +1,8 @@
+---
+name: reviewer_approval_rules
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46738
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/293742
+milestone: '13.7'
+type: development
+group: group::code review
+default_enabled: false
diff --git a/config/feature_flags/development/security_dast_site_profiles_additional_fields.yml b/config/feature_flags/development/security_dast_site_profiles_additional_fields.yml
new file mode 100644
index 00000000000..0ab96f16547
--- /dev/null
+++ b/config/feature_flags/development/security_dast_site_profiles_additional_fields.yml
@@ -0,0 +1,8 @@
+---
+name: security_dast_site_profiles_additional_fields
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46848
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/292897
+milestone: '13.7'
+type: development
+group: group::dynamic analysis
+default_enabled: false
diff --git a/db/migrate/20201021155606_add_analytics_access_level_to_project_features.rb b/db/migrate/20201021155606_add_analytics_access_level_to_project_features.rb
new file mode 100644
index 00000000000..faedbced06b
--- /dev/null
+++ b/db/migrate/20201021155606_add_analytics_access_level_to_project_features.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+# See https://docs.gitlab.com/ee/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddAnalyticsAccessLevelToProjectFeatures < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ add_column :project_features, :analytics_access_level, :integer, default: 20, null: false
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_column :project_features, :analytics_access_level, :integer
+ end
+ end
+end
diff --git a/db/schema_migrations/20201021155606 b/db/schema_migrations/20201021155606
new file mode 100644
index 00000000000..958683e9bce
--- /dev/null
+++ b/db/schema_migrations/20201021155606
@@ -0,0 +1 @@
+d9151c8cafe7a62be9904cb05cc2a6f6e28c2910e69744df1ddd4ad587c83335
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index f109dd71b86..105b7701409 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -15439,7 +15439,8 @@ CREATE TABLE project_features (
forking_access_level integer,
metrics_dashboard_access_level integer,
requirements_access_level integer DEFAULT 20 NOT NULL,
- operations_access_level integer DEFAULT 20 NOT NULL
+ operations_access_level integer DEFAULT 20 NOT NULL,
+ analytics_access_level integer DEFAULT 20 NOT NULL
);
CREATE SEQUENCE project_features_id_seq
diff --git a/doc/administration/feature_flags.md b/doc/administration/feature_flags.md
index 5a9847e9daa..272009c1f3a 100644
--- a/doc/administration/feature_flags.md
+++ b/doc/administration/feature_flags.md
@@ -13,7 +13,7 @@ to deploy features in an early stage of development so that they can be
incrementally rolled out.
Before making them permanently available, features can be deployed behind
-flags for a [number of reasons](../development/feature_flags/process.md#when-to-use-feature-flags), such as:
+flags for a [number of reasons](../development/feature_flags/index.md#when-to-use-feature-flags), such as:
- To test the feature.
- To get feedback from users and customers while in an early stage of the development of the feature.
diff --git a/doc/api/graphql/getting_started.md b/doc/api/graphql/getting_started.md
index e5f2dde325b..ca2b7989700 100644
--- a/doc/api/graphql/getting_started.md
+++ b/doc/api/graphql/getting_started.md
@@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Getting started with GitLab GraphQL API
-This guide demonstrates basic usage of GitLab's GraphQL API.
+This guide demonstrates basic usage of the GitLab GraphQL API.
See the [GraphQL API style guide](../../development/api_graphql_styleguide.md) for implementation details
aimed at developers who wish to work on developing the API itself.
@@ -69,7 +69,7 @@ In the GitLab GraphQL API, `id` refers to a
[Global ID](https://graphql.org/learn/global-object-identification/),
which is an object identifier in the format of `"gid://gitlab/Issue/123"`.
-[GitLab's GraphQL Schema](reference/index.md) outlines which objects and fields are
+[GitLab GraphQL Schema](reference/index.md) outlines which objects and fields are
available for clients to query and their corresponding data types.
Example: Get only the names of all the projects the currently logged in user can access (up to a limit, more on that later)
@@ -289,7 +289,7 @@ More about introspection:
## Sorting
-Some of GitLab's GraphQL endpoints allow you to specify how you'd like a collection of
+Some of the GitLab GraphQL endpoints allow you to specify how you'd like a collection of
objects to be sorted. You can only sort by what the schema allows you to.
Example: Issues can be sorted by creation date:
@@ -314,7 +314,7 @@ Pagination is a way of only asking for a subset of the records (say, the first 1
If we want more of them, we can make another request for the next 10 from the server
(in the form of something like "please give me the next 10 records").
-By default, GitLab's GraphQL API returns only the first 100 records of any collection.
+By default, the GitLab GraphQL API returns only the first 100 records of any collection.
This can be changed by using `first` or `last` arguments. Both arguments take a value,
so `first: 10` returns the first 10 records, and `last: 10` the last 10 records.
diff --git a/doc/api/graphql/index.md b/doc/api/graphql/index.md
index 68ac6deba19..681130e82c1 100644
--- a/doc/api/graphql/index.md
+++ b/doc/api/graphql/index.md
@@ -16,7 +16,7 @@ For those new to the GitLab GraphQL API, see
### Quick Reference
-- GitLab's GraphQL API endpoint is located at `/api/graphql`.
+- The GitLab GraphQL API endpoint is located at `/api/graphql`.
- Get an [introduction to GraphQL from graphql.org](https://graphql.org/).
- GitLab supports a wide range of resources, listed in the [GraphQL API Reference](reference/index.md).
@@ -115,9 +115,9 @@ library GitLab uses on the backend.
## Reference
-GitLab's GraphQL reference [is available](reference/index.md).
+The GitLab GraphQL reference [is available](reference/index.md).
-It is automatically generated from GitLab's GraphQL schema and embedded in a Markdown file.
+It is automatically generated from the GitLab GraphQL schema and embedded in a Markdown file.
Machine-readable versions are also available:
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index a06b1159034..2842f7893bf 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -26,7 +26,7 @@ in [Removed Items](../removed_items.md).
## Object types
-Object types represent the resources that GitLab's GraphQL API can return.
+Object types represent the resources that the GitLab GraphQL API can return.
They contain _fields_. Each field has its own type, which will either be one of the
basic GraphQL [scalar types](https://graphql.org/learn/schema/#scalar-types)
(e.g.: `String` or `Boolean`) or other object types.
diff --git a/doc/api/projects.md b/doc/api/projects.md
index eab99092e6f..b9f6448085d 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -1045,6 +1045,7 @@ POST /projects
| Attribute | Type | Required | Description |
|-------------------------------------------------------------|---------|------------------------|-------------|
| `allow_merge_on_skipped_pipeline` | boolean | **{dotted-circle}** No | Set whether or not merge requests can be merged with skipped jobs. |
+| `analytics_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `approvals_before_merge` **(STARTER)** | integer | **{dotted-circle}** No | How many approvers should approve merge requests by default. |
| `auto_cancel_pending_pipelines` | string | **{dotted-circle}** No | Auto-cancel pending pipelines. This isn't a boolean, but enabled/disabled. |
| `auto_devops_deploy_strategy` | string | **{dotted-circle}** No | Auto Deploy strategy (`continuous`, `manual` or `timed_incremental`). |
@@ -1118,6 +1119,7 @@ POST /projects/user/:user_id
| Attribute | Type | Required | Description |
|-------------------------------------------------------------|---------|------------------------|-------------|
| `allow_merge_on_skipped_pipeline` | boolean | **{dotted-circle}** No | Set whether or not merge requests can be merged with skipped jobs. |
+| `analytics_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `approvals_before_merge` **(STARTER)** | integer | **{dotted-circle}** No | How many approvers should approve merge requests by default. |
| `auto_cancel_pending_pipelines` | string | **{dotted-circle}** No | Auto-cancel pending pipelines. This isn't a boolean, but enabled/disabled. |
| `auto_devops_deploy_strategy` | string | **{dotted-circle}** No | Auto Deploy strategy (`continuous`, `manual` or `timed_incremental`). |
@@ -1190,6 +1192,7 @@ PUT /projects/:id
| Attribute | Type | Required | Description |
|-------------------------------------------------------------|----------------|------------------------|-------------|
| `allow_merge_on_skipped_pipeline` | boolean | **{dotted-circle}** No | Set whether or not merge requests can be merged with skipped jobs. |
+| `analytics_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `approvals_before_merge` **(STARTER)** | integer | **{dotted-circle}** No | How many approvers should approve merge request by default. |
| `auto_cancel_pending_pipelines` | string | **{dotted-circle}** No | Auto-cancel pending pipelines. This isn't a boolean, but enabled/disabled. |
| `auto_devops_deploy_strategy` | string | **{dotted-circle}** No | Auto Deploy strategy (`continuous`, `manual`, or `timed_incremental`). |
@@ -2374,6 +2377,7 @@ Example response:
"repository_access_level": "enabled",
"merge_requests_access_level": "enabled",
"forking_access_level": "enabled",
+ "analytics_access_level": "enabled",
"wiki_access_level": "enabled",
"builds_access_level": "enabled",
"snippets_access_level": "enabled",
diff --git a/doc/development/feature_flags/process.md b/doc/development/feature_flags/process.md
index a70edc2a052..2e3680bb103 100644
--- a/doc/development/feature_flags/process.md
+++ b/doc/development/feature_flags/process.md
@@ -53,7 +53,6 @@ problems, such as outages.
Please also read the [development guide for feature flags](development.md).
-
### Including a feature behind feature flag in the final release
In order to build a final release and present the feature for self-managed
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 077feae8bff..7234bca8e12 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -88,6 +88,9 @@ in multiple ways:
## Features
+NOTE:
+Depending on your target platform, some features might not be available to you.
+
Comprised of a set of [stages](stages.md), Auto DevOps brings these best practices to your
project in a simple and automatic way:
diff --git a/doc/topics/autodevops/stages.md b/doc/topics/autodevops/stages.md
index 662f2696353..66c7e9c3d1b 100644
--- a/doc/topics/autodevops/stages.md
+++ b/doc/topics/autodevops/stages.md
@@ -11,6 +11,9 @@ Read them carefully to understand how each one works.
## Auto Build
+NOTE:
+Auto Build is not supported if Docker in Docker is not available for your GitLab Runners, like in OpenShift clusters. GitLab's OpenShift support is tracked [in a dedicated epic](https://gitlab.com/groups/gitlab-org/-/epics/2068).
+
Auto Build creates a build of the application using an existing `Dockerfile` or
Heroku buildpacks. The resulting Docker image is pushed to the
[Container Registry](../../user/packages/container_registry/index.md), and tagged
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 3f36e833971..07774f51958 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -183,7 +183,7 @@ The following variables are used for configuring specific analyzers (used for a
| `PIP_REQUIREMENTS_FILE` | `gemnasium-python` | | Pip requirements file to be scanned. |
| `DS_PIP_VERSION` | `gemnasium-python` | | Force the install of a specific pip version (example: `"19.3"`), otherwise the pip installed in the Docker image is used. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12811) in GitLab 12.7) |
| `DS_PIP_DEPENDENCY_PATH` | `gemnasium-python` | | Path to load Python pip dependencies from. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12412) in GitLab 12.2) |
-| `DS_PYTHON_VERSION` | `retire.js` | | Version of Python. If set to 2, dependencies are installed using Python 2.7 instead of Python 3.6. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12296) in GitLab 12.1)|
+| `DS_PYTHON_VERSION` | `retire.js` | | Version of Python. If set to 2, dependencies are installed using Python 2.7 instead of Python 3.6. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12296) in GitLab 12.1, [removed](https://www.python.org/doc/sunset-python-2/) in GitLab 13.7)|
| `DS_JAVA_VERSION` | `gemnasium-maven` | `11` | Version of Java. Available versions: `8`, `11`, `13`, `14`. Maven and Gradle use the Java version specified by this value. |
| `MAVEN_CLI_OPTS` | `gemnasium-maven` | `"-DskipTests --batch-mode"` | List of command line arguments that are passed to `maven` by the analyzer. See an example for [using private repositories](../index.md#using-private-maven-repos). |
| `GRADLE_CLI_OPTS` | `gemnasium-maven` | | List of command line arguments that are passed to `gradle` by the analyzer. |
@@ -520,3 +520,8 @@ uses the [`rules:exists`](../../../ci/yaml/README.md#rulesexists)
syntax. This directive is limited to 10000 checks and always returns `true` after reaching this
number. Because of this, and depending on the number of files in your repository, a dependency
scanning job might be triggered even if the scanner doesn't support your project.
+
+### Issues building projects with npm or yarn packages relying on Python 2
+
+Python 2 was removed (see: [Python 2 sunsetting](https://www.python.org/doc/sunset-python-2/)) from the `retire.js` analyzer in GitLab 13.7 (analyzer version 2.10.1). Projects using packages
+with a dependency on this version of Python should use `retire.js` version 2.10.0 or lower (for example, `registry.gitlab.com/gitlab-org/security-products/analyzers/retire.js:2.10.0`).
diff --git a/doc/user/group/img/group_members_filter_2fa_disabled_13_7.png b/doc/user/group/img/group_members_filter_2fa_disabled_13_7.png
new file mode 100644
index 00000000000..8336103bad1
Binary files /dev/null and b/doc/user/group/img/group_members_filter_2fa_disabled_13_7.png differ
diff --git a/doc/user/group/img/group_members_filter_2fa_enabled_13_7.png b/doc/user/group/img/group_members_filter_2fa_enabled_13_7.png
new file mode 100644
index 00000000000..eb27906b583
Binary files /dev/null and b/doc/user/group/img/group_members_filter_2fa_enabled_13_7.png differ
diff --git a/doc/user/group/img/group_members_filter_direct_13_7.png b/doc/user/group/img/group_members_filter_direct_13_7.png
new file mode 100644
index 00000000000..c1b2d996e59
Binary files /dev/null and b/doc/user/group/img/group_members_filter_direct_13_7.png differ
diff --git a/doc/user/group/img/group_members_filter_inherited_13_7.png b/doc/user/group/img/group_members_filter_inherited_13_7.png
new file mode 100644
index 00000000000..f75990f9da8
Binary files /dev/null and b/doc/user/group/img/group_members_filter_inherited_13_7.png differ
diff --git a/doc/user/group/img/group_members_search_13_7.png b/doc/user/group/img/group_members_search_13_7.png
new file mode 100644
index 00000000000..21f36fc75f8
Binary files /dev/null and b/doc/user/group/img/group_members_search_13_7.png differ
diff --git a/doc/user/group/img/group_members_sort_13_7.png b/doc/user/group/img/group_members_sort_13_7.png
new file mode 100644
index 00000000000..5d307da649a
Binary files /dev/null and b/doc/user/group/img/group_members_sort_13_7.png differ
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 0eac10663ec..a0884461da1 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -211,6 +211,88 @@ To remove a member from a group:
1. (Optional) Select the **Also unassign this user from related issues and merge requests** checkbox.
1. Click **Remove member**.
+## Filter and sort members in a group
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21727) in GitLab 12.6.
+> - [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/228675) in GitLab 13.7.
+> - Improvements are [deployed behind a feature flag](../feature_flags.md), enabled by default.
+> - Improvements are enabled on GitLab.com.
+> - Improvements are recommended for production use.
+> - For GitLab self-managed instances, GitLab administrators can opt to [disable improvements](#enable-or-disable-improvements-to-the-ability-to-filter-and-sort-group-members). **(CORE ONLY)**
+
+The following sections illustrate how you can filter and sort members in a group. To view these options,
+navigate to your desired group, go to **Members**, and include the noted search terms.
+
+### Membership filter
+
+By default, inherited and direct members are displayed. The [membership](subgroups/index.md#membership) filter can be used to display only inherited or only direct members.
+
+#### Only display inherited members
+
+Include `Membership` `=` `Inherited` in the search text box.
+
+![Group members filter inherited](img/group_members_filter_inherited_13_7.png)
+
+#### Only display direct members
+
+Include `Membership` `=` `Direct` in the search text box.
+
+![Group members filter direct](img/group_members_filter_direct_13_7.png)
+
+### 2FA filter
+
+[Owner](../permissions.md#group-members-permissions) permissions required.
+
+By default, members with 2FA enabled and disabled are displayed. The 2FA filter can be used to display only members with 2FA enabled or only members with 2FA disabled.
+
+#### Only display members with 2FA enabled
+
+Include `2FA` `=` `Enabled` in the search text box.
+
+![Group members filter 2FA enabled](img/group_members_filter_2fa_enabled_13_7.png)
+
+#### Only display members with 2FA disabled
+
+Include `2FA` `=` `Disabled` in the search text box.
+
+![Group members filter 2FA disabled](img/group_members_filter_2fa_disabled_13_7.png)
+
+### Search
+
+You can search for members by name, username, or email.
+
+![Group members search](img/group_members_search_13_7.png)
+
+### Sort
+
+You can sort members by **Account**, **Access granted**, **Max role**, or **Last sign-in** in ascending or descending order.
+
+![Group members sort](img/group_members_sort_13_7.png)
+
+### Enable or disable improvements to the ability to filter and sort group members **(CORE ONLY)**
+
+Group member filtering and sorting improvements are deployed behind a feature flag that is **enabled by default**.
+[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
+can opt to disable the improvements.
+
+To disable them:
+
+```ruby
+# For the instance
+Feature.disable(:group_members_filtered_search)
+# For a single group
+Feature.disable(:group_members_filtered_search, Group.find())
+```
+
+To enable them:
+
+```ruby
+# For the instance
+Feature.enable(:group_members_filtered_search)
+# For a single group
+Feature.enable(:group_members_filtered_search, Group.find())
+```
+
## Changing the default branch protection of a group
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7583) in GitLab 12.9.
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index f6de4117f7c..62431747911 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -192,6 +192,8 @@ If the information you need isn't listed above you may wish to check our [troubl
## User access and management
+> [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/268142) in GitLab 13.7.
+
Once Group SSO is configured and enabled, users can access the GitLab.com group through the identity provider's dashboard. If [SCIM](scim_setup.md) is configured, please see the [user access and linking setup section on the SCIM page](scim_setup.md#user-access-and-linking-setup).
When a user tries to sign in with Group SSO, GitLab attempts to find or create a user based on the following:
diff --git a/doc/user/group/subgroups/img/group_members.png b/doc/user/group/subgroups/img/group_members.png
deleted file mode 100644
index 830ccafa794..00000000000
Binary files a/doc/user/group/subgroups/img/group_members.png and /dev/null differ
diff --git a/doc/user/group/subgroups/img/group_members_13_7.png b/doc/user/group/subgroups/img/group_members_13_7.png
new file mode 100644
index 00000000000..ab22bcb932c
Binary files /dev/null and b/doc/user/group/subgroups/img/group_members_13_7.png differ
diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index be86e5cfa2b..8af075fc0c0 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -116,9 +116,9 @@ Follow the same process to create any subsequent groups.
## Membership
-When you add a member to a subgroup, they inherit the membership and permission
-level from the parent group(s). This model allows access to nested groups if you
-have membership in one of its parents.
+When you add a member to a group, that member is also added to all subgroups.
+Permission level is inherited from the group’s parent. This model allows access to
+subgroups if you have membership in one of its parents.
Jobs for pipelines in subgroups can use [runners](../../../ci/runners/README.md) registered to the parent group(s).
This means secrets configured for the parent group are available to subgroup jobs.
@@ -131,31 +131,23 @@ the **Members** page of the group the member was added.
You can tell if a member has inherited the permissions from a parent group by
looking at the group's **Members** page.
-![Group members page](img/group_members.png)
+![Group members page](img/group_members_13_7.png)
From the image above, we can deduce the following things:
- There are 5 members that have access to the group `four`.
-- User0 is a Reporter and has inherited their permissions from group `one`
+- User 0 is a Reporter and has inherited their permissions from group `one`
which is above the hierarchy of group `four`.
-- User1 is a Developer and has inherited their permissions from group
+- User 1 is a Developer and has inherited their permissions from group
`one/two` which is above the hierarchy of group `four`.
-- User2 is a Developer and has inherited their permissions from group
+- User 2 is a Developer and has inherited their permissions from group
`one/two/three` which is above the hierarchy of group `four`.
-- For User3 there is no indication of a parent group, therefore they belong to
+- For User 3 the **Source** column indicates **Direct member**, therefore they belong to
group `four`, the one we're inspecting.
- Administrator is the Owner and member of **all** subgroups and for that reason,
- as with User3, there is no indication of an ancestor group.
+ as with User 3, the **Source** column indicates **Direct member**.
-[From](https://gitlab.com/gitlab-org/gitlab/-/issues/21727) GitLab 12.6, you can filter
-this list using dropdown on the right side:
-
-![Group members filter](img/group_members_filter_v12_6.png)
-
-- **Show only direct members** displays only Administrator and User3, since these are
- the only users that belong to group `four`, which is the one we're inspecting.
-- **Show only inherited members** displays User0, User1 and User2, no matter which group
- above the hierarchy is the source of inherited permissions.
+Members can be [filtered by inherited or direct membership](../index.md#membership-filter).
### Overriding the ancestor group membership
@@ -169,9 +161,9 @@ Therefore, you cannot reduce a user's permissions in a subgroup with respect to
To override a user's membership of an ancestor group (the first group they were
added to), add the user to the new subgroup again with a higher set of permissions.
-For example, if User0 was first added to group `group-1/group-1-1` with Developer
+For example, if User 1 was first added to group `one/two` with Developer
permissions, then they inherit those permissions in every other subgroup
-of `group-1/group-1-1`. To give them Maintainer access to `group-1/group-1-1/group1-1-1`,
+of `one/two`. To give them Maintainer access to group `one/two/three/four`,
you would add them again in that group as Maintainer. Removing them from that group,
the permissions fall back to those of the ancestor group.
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index f09a5d9c69b..26401914cce 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -289,6 +289,7 @@ group.
| View Value Stream analytics | ✓ | ✓ | ✓ | ✓ | ✓ |
| View Billing **(FREE ONLY)** | | | | | ✓ (4) |
| View Usage Quotas **(FREE ONLY)** | | | | | ✓ (4) |
+| Filter members by 2FA status | | | | | ✓ |
1. Groups can be set to [allow either Owners or Owners and
Maintainers to create subgroups](group/subgroups/index.md#creating-a-subgroup)
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index 18c106731b9..4bacf6f151f 100644
--- a/doc/user/project/issues/index.md
+++ b/doc/user/project/issues/index.md
@@ -35,7 +35,7 @@ you can also view all the issues collectively at the group level.
See also [Always start a discussion with an issue](https://about.gitlab.com/blog/2016/03/03/start-with-an-issue/).
-To learn how the GitLab Strategic Marketing department uses GitLab issues with [labels](../labels.md) and
+To learn how our Strategic Marketing department uses GitLab issues with [labels](../labels.md) and
[issue boards](../issue_board.md), see the video on
[Managing Commitments with Issues](https://www.youtube.com/watch?v=cuIHNintg1o&t=3).
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index 05968bb1f20..41f404de4f2 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -68,6 +68,7 @@ Use the switches to enable or disable the following features:
| **Container Registry** | | Activates a [registry](../../packages/container_registry/) for your Docker images |
| **Git Large File Storage** | | Enables the use of [large files](../../../topics/git/lfs/index.md#git-large-file-storage-lfs) |
| **Packages** | | Supports configuration of a [package registry](../../../administration/packages/index.md#gitlab-package-registry-administration) functionality |
+| **Analytics** | ✓ | Enables [analytics](../../analytics/) |
| **Wiki** | ✓ | Enables a separate system for [documentation](../wiki/) |
| **Snippets** | ✓ | Enables [sharing of code and text](../../snippets.md) |
| **Pages** | ✓ | Allows you to [publish static websites](../pages/) |
diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb
index e7610db85cb..317caefe0a1 100644
--- a/lib/api/entities/project.rb
+++ b/lib/api/entities/project.rb
@@ -68,6 +68,7 @@ module API
expose(:snippets_access_level) { |project, options| project.project_feature.string_access_level(:snippets) }
expose(:pages_access_level) { |project, options| project.project_feature.string_access_level(:pages) }
expose(:operations_access_level) { |project, options| project.project_feature.string_access_level(:operations) }
+ expose(:analytics_access_level) { |project, options| project.project_feature.string_access_level(:analytics) }
expose :emails_disabled
expose :shared_runners_enabled
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index 2a6f620ee86..f5f45cf7351 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -33,6 +33,7 @@ module API
optional :snippets_access_level, type: String, values: %w(disabled private enabled), desc: 'Snippets access level. One of `disabled`, `private` or `enabled`'
optional :pages_access_level, type: String, values: %w(disabled private enabled public), desc: 'Pages access level. One of `disabled`, `private`, `enabled` or `public`'
optional :operations_access_level, type: String, values: %w(disabled private enabled), desc: 'Operations access level. One of `disabled`, `private` or `enabled`'
+ optional :analytics_access_level, type: String, values: %w(disabled private enabled), desc: 'Analytics access level. One of `disabled`, `private` or `enabled`'
optional :emails_disabled, type: Boolean, desc: 'Disable email notifications'
optional :show_default_award_emojis, type: Boolean, desc: 'Show default award emojis'
diff --git a/lib/gitlab/graphql/docs/templates/default.md.haml b/lib/gitlab/graphql/docs/templates/default.md.haml
index 3e55bd4c136..8f5a1788fa5 100644
--- a/lib/gitlab/graphql/docs/templates/default.md.haml
+++ b/lib/gitlab/graphql/docs/templates/default.md.haml
@@ -21,7 +21,7 @@
:plain
## Object types
- Object types represent the resources that GitLab's GraphQL API can return.
+ Object types represent the resources that the GitLab GraphQL API can return.
They contain _fields_. Each field has its own type, which will either be one of the
basic GraphQL [scalar types](https://graphql.org/learn/schema/#scalar-types)
(e.g.: `String` or `Boolean`) or other object types.
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 779a1d14ad6..527bf534751 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -962,6 +962,9 @@ msgstr ""
msgid "(%{value}) has already been taken"
msgstr ""
+msgid "(+%{count} rules)"
+msgstr ""
+
msgid "(No changes)"
msgstr ""
@@ -8680,6 +8683,9 @@ msgstr ""
msgid "DastProfiles|Error Details"
msgstr ""
+msgid "DastProfiles|Excluded URLs"
+msgstr ""
+
msgid "DastProfiles|Hide debug messages"
msgstr ""
@@ -8728,6 +8734,9 @@ msgstr ""
msgid "DastProfiles|Profile name"
msgstr ""
+msgid "DastProfiles|Request headers"
+msgstr ""
+
msgid "DastProfiles|Run the AJAX spider, in addition to the traditional spider, to crawl the target site."
msgstr ""
@@ -21754,6 +21763,9 @@ msgstr ""
msgid "ProjectSettings|Allow users to request access"
msgstr ""
+msgid "ProjectSettings|Analytics"
+msgstr ""
+
msgid "ProjectSettings|Automatically resolve merge request diff discussions when they become outdated"
msgstr ""
@@ -21997,6 +22009,9 @@ msgstr ""
msgid "ProjectSettings|View and edit files in this project. Non-project members will only have read access"
msgstr ""
+msgid "ProjectSettings|View project analytics"
+msgstr ""
+
msgid "ProjectSettings|When approved for merge, merge requests are queued and pipelines validate the combined results of the source and target branches before merge."
msgstr ""
diff --git a/spec/factories/snippet_repository_storage_moves.rb b/spec/factories/snippet_repository_storage_moves.rb
new file mode 100644
index 00000000000..ed65dc5374f
--- /dev/null
+++ b/spec/factories/snippet_repository_storage_moves.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :snippet_repository_storage_move, class: 'SnippetRepositoryStorageMove' do
+ container { association(:snippet) }
+
+ source_storage_name { 'default' }
+
+ trait :scheduled do
+ state { SnippetRepositoryStorageMove.state_machines[:state].states[:scheduled].value }
+ end
+
+ trait :started do
+ state { SnippetRepositoryStorageMove.state_machines[:state].states[:started].value }
+ end
+
+ trait :replicated do
+ state { SnippetRepositoryStorageMove.state_machines[:state].states[:replicated].value }
+ end
+
+ trait :finished do
+ state { SnippetRepositoryStorageMove.state_machines[:state].states[:finished].value }
+ end
+
+ trait :failed do
+ state { SnippetRepositoryStorageMove.state_machines[:state].states[:failed].value }
+ end
+ end
+end
diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb
index 904b4bda0e0..2f0fbd29cb5 100644
--- a/spec/features/projects/features_visibility_spec.rb
+++ b/spec/features/projects/features_visibility_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe 'Edit Project Settings' do
sign_in(member)
end
- tools = { builds: "pipelines", issues: "issues", wiki: "wiki", snippets: "snippets", merge_requests: "merge_requests" }
+ tools = { builds: "pipelines", issues: "issues", wiki: "wiki", snippets: "snippets", merge_requests: "merge_requests", analytics: "analytics" }
tools.each do |tool_name, shortcut_name|
describe "feature #{tool_name}" do
diff --git a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
index a62977f30c3..0b58260ed1c 100644
--- a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
+++ b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
@@ -21,6 +21,7 @@ const defaultProps = {
wikiAccessLevel: 20,
snippetsAccessLevel: 20,
pagesAccessLevel: 10,
+ analyticsAccessLevel: 20,
containerRegistryEnabled: true,
lfsEnabled: true,
emailsDisabled: false,
@@ -79,6 +80,8 @@ describe('Settings Panel', () => {
const findRepositoryFeatureSetting = () =>
findRepositoryFeatureProjectRow().find(projectFeatureSetting);
+ const findAnalyticsRow = () => wrapper.find({ ref: 'analytics-settings' });
+
beforeEach(() => {
wrapper = mountComponent();
});
@@ -557,4 +560,12 @@ describe('Settings Panel', () => {
});
});
});
+
+ describe('Analytics', () => {
+ it('should show the analytics toggle', async () => {
+ await wrapper.vm.$nextTick();
+
+ expect(findAnalyticsRow().exists()).toBe(true);
+ });
+ });
});
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index cc565b93aea..fba32ae0673 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -107,6 +107,7 @@ snippets:
- user_mentions
- snippet_repository
- statistics
+- repository_storage_moves
releases:
- author
- project
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 8f1db484ed1..a93ee051ccf 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -578,6 +578,7 @@ ProjectFeature:
- pages_access_level
- metrics_dashboard_access_level
- requirements_access_level
+- analytics_access_level
- operations_access_level
- created_at
- updated_at
diff --git a/spec/models/project_repository_storage_move_spec.rb b/spec/models/project_repository_storage_move_spec.rb
index 85d3293a3b1..88535f6dd6e 100644
--- a/spec/models/project_repository_storage_move_spec.rb
+++ b/spec/models/project_repository_storage_move_spec.rb
@@ -3,11 +3,33 @@
require 'spec_helper'
RSpec.describe ProjectRepositoryStorageMove, type: :model do
- it_behaves_like 'handles repository moves' do
- let_it_be_with_refind(:container) { create(:project) }
+ let_it_be_with_refind(:project) { create(:project) }
+ it_behaves_like 'handles repository moves' do
+ let(:container) { project }
let(:repository_storage_factory_key) { :project_repository_storage_move }
let(:error_key) { :project }
let(:repository_storage_worker) { ProjectUpdateRepositoryStorageWorker }
end
+
+ describe 'state transitions' do
+ let(:storage) { 'test_second_storage' }
+
+ before do
+ stub_storage_settings(storage => { 'path' => 'tmp/tests/extra_storage' })
+ end
+
+ context 'when started' do
+ subject(:storage_move) { create(:project_repository_storage_move, :started, container: project, destination_storage_name: storage) }
+
+ context 'and transits to replicated' do
+ it 'sets the repository storage and marks the container as writable' do
+ storage_move.finish_replication!
+
+ expect(project.repository_storage).to eq(storage)
+ expect(project).not_to be_repository_read_only
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb
index 2d283766edb..cb1baa02e96 100644
--- a/spec/models/project_statistics_spec.rb
+++ b/spec/models/project_statistics_spec.rb
@@ -313,17 +313,6 @@ RSpec.describe ProjectStatistics do
it 'stores the size of related uploaded files' do
expect(statistics.update_uploads_size).to eq(3.megabytes)
end
-
- context 'with feature flag disabled' do
- before do
- statistics.update_columns(uploads_size: 0)
- stub_feature_flags(count_uploads_size_in_storage_stats: false)
- end
-
- it 'does not store the size of related uploaded files' do
- expect(statistics.update_uploads_size).to eq(0)
- end
- end
end
describe '#update_storage_size' do
diff --git a/spec/models/snippet_repository_storage_move_spec.rb b/spec/models/snippet_repository_storage_move_spec.rb
new file mode 100644
index 00000000000..c9feff0c22f
--- /dev/null
+++ b/spec/models/snippet_repository_storage_move_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe SnippetRepositoryStorageMove, type: :model do
+ it_behaves_like 'handles repository moves' do
+ let_it_be_with_refind(:container) { create(:snippet) }
+
+ let(:repository_storage_factory_key) { :snippet_repository_storage_move }
+ let(:error_key) { :snippet }
+ let(:repository_storage_worker) { nil } # TODO set to SnippetUpdateRepositoryStorageWorker after https://gitlab.com/gitlab-org/gitlab/-/issues/218991 is implemented
+ end
+end
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index a696c33837d..f87259ea048 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -21,6 +21,7 @@ RSpec.describe Snippet do
it { is_expected.to have_many(:user_mentions).class_name("SnippetUserMention") }
it { is_expected.to have_one(:snippet_repository) }
it { is_expected.to have_one(:statistics).class_name('SnippetStatistics').dependent(:destroy) }
+ it { is_expected.to have_many(:repository_storage_moves).class_name('SnippetRepositoryStorageMove').inverse_of(:container) }
end
describe 'validation' do
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 67c35185df7..7f6c47d675b 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -977,6 +977,34 @@ RSpec.describe ProjectPolicy do
end
end
+ describe 'read_analytics' do
+ context 'anonymous user' do
+ let(:current_user) { anonymous }
+
+ it { is_expected.to be_allowed(:read_analytics) }
+ end
+
+ context 'project member' do
+ let(:project) { private_project }
+
+ %w(guest reporter developer maintainer).each do |role|
+ context role do
+ let(:current_user) { send(role) }
+
+ it { is_expected.to be_allowed(:read_analytics) }
+
+ context "without access to Analytics" do
+ before do
+ project.project_feature.update!(analytics_access_level: ProjectFeature::DISABLED)
+ end
+
+ it { is_expected.to be_disallowed(:read_analytics) }
+ end
+ end
+ end
+ end
+ end
+
it_behaves_like 'Self-managed Core resource access tokens'
describe 'operations feature' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index c50fe2c7e1d..ad5468fb54c 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -873,6 +873,7 @@ RSpec.describe API::Projects do
jobs_enabled: false,
merge_requests_enabled: false,
forking_access_level: 'disabled',
+ analytics_access_level: 'disabled',
wiki_enabled: false,
resolve_outdated_diff_discussions: false,
remove_source_branch_after_merge: true,
@@ -903,6 +904,7 @@ RSpec.describe API::Projects do
expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::DISABLED)
expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::DISABLED)
expect(project.operations_access_level).to eq(ProjectFeature::DISABLED)
+ expect(project.project_feature.analytics_access_level).to eq(ProjectFeature::DISABLED)
end
it 'creates a project using a template' do
@@ -1623,6 +1625,7 @@ RSpec.describe API::Projects do
expect(json_response['issues_access_level']).to be_present
expect(json_response['merge_requests_access_level']).to be_present
expect(json_response['forking_access_level']).to be_present
+ expect(json_response['analytics_access_level']).to be_present
expect(json_response['wiki_access_level']).to be_present
expect(json_response['builds_access_level']).to be_present
expect(json_response['operations_access_level']).to be_present
diff --git a/spec/support/shared_examples/models/concerns/repository_storage_movable_shared_examples.rb b/spec/support/shared_examples/models/concerns/repository_storage_movable_shared_examples.rb
index d6059eaff33..5a8388d01df 100644
--- a/spec/support/shared_examples/models/concerns/repository_storage_movable_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/repository_storage_movable_shared_examples.rb
@@ -63,6 +63,7 @@ RSpec.shared_examples 'handles repository moves' do
context 'and transits to scheduled' do
it 'triggers the corresponding repository storage worker' do
+ skip unless repository_storage_worker # TODO remove after https://gitlab.com/gitlab-org/gitlab/-/issues/218991 is implemented
expect(repository_storage_worker).to receive(:perform_async).with(container.id, 'test_second_storage', storage_move.id)
storage_move.schedule!
@@ -72,6 +73,7 @@ RSpec.shared_examples 'handles repository moves' do
context 'when the transition fails' do
it 'does not trigger ProjectUpdateRepositoryStorageWorker and adds an error' do
+ skip unless repository_storage_worker # TODO remove after https://gitlab.com/gitlab-org/gitlab/-/issues/218991 is implemented
allow(storage_move.container).to receive(:set_repository_read_only!).and_raise(StandardError, 'foobar')
expect(repository_storage_worker).not_to receive(:perform_async)
@@ -94,10 +96,9 @@ RSpec.shared_examples 'handles repository moves' do
subject(:storage_move) { create(repository_storage_factory_key, :started, container: container, destination_storage_name: 'test_second_storage') }
context 'and transits to replicated' do
- it 'sets the repository storage and marks the container as writable' do
+ it 'marks the container as writable' do
storage_move.finish_replication!
- expect(container.repository_storage).to eq('test_second_storage')
expect(container).not_to be_repository_read_only
end
end