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 ${this.renderRowAvatar(issuableType, user, img)} - + ${escape(user.name)} - ${username ? `${username}` : ''} + ${ + username + ? `${username}` + : '' + } + ${this.renderApprovalRules(elsClassName, user.applicable_approval_rules)} @@ -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