diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index b29d496a18a..00e8849e3c6 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -b670554eae8643f2072d3b4f6f7c5cd2b9ec8776 +b5937b10d1a386c392adb1ebced2d0c8cb038696 diff --git a/app/assets/javascripts/releases/components/app_index.vue b/app/assets/javascripts/releases/components/app_index.vue index ba5342e993e..b8cf6ce478f 100644 --- a/app/assets/javascripts/releases/components/app_index.vue +++ b/app/assets/javascripts/releases/components/app_index.vue @@ -25,31 +25,16 @@ export default { GlLink, GlButton, }, - props: { - projectId: { - type: String, - required: true, - }, - projectPath: { - type: String, - required: true, - }, - documentationPath: { - type: String, - required: true, - }, - illustrationPath: { - type: String, - required: true, - }, - newReleasePath: { - type: String, - required: false, - default: '', - }, - }, computed: { - ...mapState('list', ['isLoading', 'releases', 'hasError', 'pageInfo']), + ...mapState('list', [ + 'documentationPath', + 'illustrationPath', + 'newReleasePath', + 'isLoading', + 'releases', + 'hasError', + 'pageInfo', + ]), shouldRenderEmptyState() { return !this.releases.length && !this.hasError && !this.isLoading; }, @@ -65,15 +50,13 @@ export default { created() { this.fetchReleases({ page: getParameterByName('page'), - projectId: this.projectId, - projectPath: this.projectPath, }); }, methods: { ...mapActions('list', ['fetchReleases']), onChangePage(page) { historyPushState(buildUrlWithCurrentLocation(`?page=${page}`)); - this.fetchReleases({ page, projectId: this.projectId }); + this.fetchReleases({ page }); }, }, }; diff --git a/app/assets/javascripts/releases/components/releases_pagination_graphql.vue b/app/assets/javascripts/releases/components/releases_pagination_graphql.vue index 50d2796b0bd..a4fe407a5bd 100644 --- a/app/assets/javascripts/releases/components/releases_pagination_graphql.vue +++ b/app/assets/javascripts/releases/components/releases_pagination_graphql.vue @@ -7,7 +7,7 @@ export default { name: 'ReleasesPaginationGraphql', components: { GlKeysetPagination }, computed: { - ...mapState('list', ['projectPath', 'graphQlPageInfo']), + ...mapState('list', ['graphQlPageInfo']), showPagination() { return this.graphQlPageInfo.hasPreviousPage || this.graphQlPageInfo.hasNextPage; }, @@ -16,11 +16,11 @@ export default { ...mapActions('list', ['fetchReleasesGraphQl']), onPrev(before) { historyPushState(buildUrlWithCurrentLocation(`?before=${before}`)); - this.fetchReleasesGraphQl({ projectPath: this.projectPath, before }); + this.fetchReleasesGraphQl({ before }); }, onNext(after) { historyPushState(buildUrlWithCurrentLocation(`?after=${after}`)); - this.fetchReleasesGraphQl({ projectPath: this.projectPath, after }); + this.fetchReleasesGraphQl({ after }); }, }, }; diff --git a/app/assets/javascripts/releases/components/releases_pagination_rest.vue b/app/assets/javascripts/releases/components/releases_pagination_rest.vue index 52e88f5dc9b..992cc4cd469 100644 --- a/app/assets/javascripts/releases/components/releases_pagination_rest.vue +++ b/app/assets/javascripts/releases/components/releases_pagination_rest.vue @@ -7,13 +7,13 @@ export default { name: 'ReleasesPaginationRest', components: { TablePagination }, computed: { - ...mapState('list', ['projectId', 'pageInfo']), + ...mapState('list', ['pageInfo']), }, methods: { ...mapActions('list', ['fetchReleasesRest']), onChangePage(page) { historyPushState(buildUrlWithCurrentLocation(`?page=${page}`)); - this.fetchReleasesRest({ page, projectId: this.projectId }); + this.fetchReleasesRest({ page }); }, }, }; diff --git a/app/assets/javascripts/releases/mount_index.js b/app/assets/javascripts/releases/mount_index.js index c193cb9de9f..cd4fa5c5df5 100644 --- a/app/assets/javascripts/releases/mount_index.js +++ b/app/assets/javascripts/releases/mount_index.js @@ -21,9 +21,6 @@ export default () => { graphqlMilestoneStats: Boolean(gon.features?.graphqlMilestoneStats), }, }), - render: h => - h(ReleaseListApp, { - props: el.dataset, - }), + render: h => h(ReleaseListApp), }); }; diff --git a/app/assets/javascripts/releases/stores/modules/list/actions.js b/app/assets/javascripts/releases/stores/modules/list/actions.js index 3d28ef23fe7..945b093b983 100644 --- a/app/assets/javascripts/releases/stores/modules/list/actions.js +++ b/app/assets/javascripts/releases/stores/modules/list/actions.js @@ -23,7 +23,7 @@ export const requestReleases = ({ commit }) => commit(types.REQUEST_RELEASES); * * @param {String} projectId */ -export const fetchReleases = ({ dispatch, rootState }, { page = '1', projectId, projectPath }) => { +export const fetchReleases = ({ dispatch, rootState, state }, { page = '1' }) => { dispatch('requestReleases'); if ( @@ -35,7 +35,7 @@ export const fetchReleases = ({ dispatch, rootState }, { page = '1', projectId, .query({ query: allReleasesQuery, variables: { - fullPath: projectPath, + fullPath: state.projectPath, }, }) .then(response => { @@ -44,7 +44,7 @@ export const fetchReleases = ({ dispatch, rootState }, { page = '1', projectId, .catch(() => dispatch('receiveReleasesError')); } else { api - .releases(projectId, { page }) + .releases(state.projectId, { page }) .then(response => dispatch('receiveReleasesSuccess', response)) .catch(() => dispatch('receiveReleasesError')); } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 20819e82920..53525a4d877 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -731,7 +731,6 @@ .issuable-info-container { flex: 1; display: flex; - padding-right: $gl-padding; .issuable-main-info { flex: 1 auto; diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index a2f17678a8e..03603f637c8 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -14,7 +14,7 @@ } .issue { - padding: 10px 0 10px $gl-padding; + padding: 10px $gl-padding; position: relative; .title { diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index fc3d0053859..73f71f7ad55 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -170,6 +170,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController def set_application_setting @application_setting = ApplicationSetting.current_without_cache + @plans = Plan.all end def whitelist_query_limiting diff --git a/app/controllers/admin/plan_limits_controller.rb b/app/controllers/admin/plan_limits_controller.rb new file mode 100644 index 00000000000..2620db8aec5 --- /dev/null +++ b/app/controllers/admin/plan_limits_controller.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +class Admin::PlanLimitsController < Admin::ApplicationController + include InternalRedirect + + before_action :set_plan_limits + + def create + redirect_path = referer_path(request) || general_admin_application_settings_path + + respond_to do |format| + if @plan_limits.update(plan_limits_params) + format.json { head :ok } + format.html { redirect_to redirect_path, notice: _('Application limits saved successfully') } + else + format.json { head :bad_request } + format.html { render_update_error } + end + end + end + + private + + def set_plan_limits + @plan_limits = Plan.find(plan_limits_params[:plan_id]).actual_limits + end + + def plan_limits_params + params.require(:plan_limits).permit(%i[ + plan_id + conan_max_file_size + maven_max_file_size + npm_max_file_size + nuget_max_file_size + pypi_max_file_size + generic_packages_max_file_size + ]) + end +end diff --git a/app/views/admin/application_settings/_package_registry.html.haml b/app/views/admin/application_settings/_package_registry.html.haml new file mode 100644 index 00000000000..257a90252cc --- /dev/null +++ b/app/views/admin/application_settings/_package_registry.html.haml @@ -0,0 +1,50 @@ +- if Gitlab.config.packages.enabled + %section.settings.as-package.no-animate#js-package-settings{ class: ('expanded' if expanded_by_default?) } + .settings-header + %h4 + = _('Package Registry') + %button.btn.btn-default.js-settings-toggle{ type: 'button' } + = expanded_by_default? ? _('Collapse') : _('Expand') + %p + = _("Settings related to the use and experience of using GitLab's Package Registry.") + + = render_if_exists 'admin/application_settings/ee_package_registry' + + .settings-content + %h4 + = _('Package file size limits') + %p + = _('Set limit to 0 to allow any file size.') + .scrolling-tabs-container.inner-page-scroll-tabs + - if @plans.size > 1 + %ul.nav-links.scrolling-tabs.mobile-separator.nav.nav-tabs.mb-3 + - @plans.each_with_index do |plan, index| + %li + = link_to admin_plan_limits_path(anchor: 'js-package-settings'), data: { target: "div#plan#{index}", action: "plan#{index}", toggle: 'tab'}, class: index == 0 ? 'active': '' do + = plan.name.capitalize + .tab-content + - @plans.each_with_index do |plan, index| + .tab-pane{ :id => "plan#{index}", class: index == 0 ? 'active': '' } + = form_for plan.actual_limits, url: admin_plan_limits_path(anchor: 'js-package-settings'), html: { class: 'fieldset-form' }, method: :post do |f| + = form_errors(plan) + %fieldset + = f.hidden_field(:plan_id, value: plan.id) + .form-group + = f.label :conan_max_file_size, _('Maximum Conan package file size in bytes'), class: 'label-bold' + = f.number_field :conan_max_file_size, class: 'form-control' + .form-group + = f.label :maven_max_file_size, _('Maximum Maven package file size in bytes'), class: 'label-bold' + = f.number_field :maven_max_file_size, class: 'form-control' + .form-group + = f.label :npm_max_file_size, _('Maximum NPM package file size in bytes'), class: 'label-bold' + = f.number_field :npm_max_file_size, class: 'form-control' + .form-group + = f.label :nuget_max_file_size, _('Maximum NuGet package file size in bytes'), class: 'label-bold' + = f.number_field :nuget_max_file_size, class: 'form-control' + .form-group + = f.label :pypi_max_file_size, _('Maximum PyPI package file size in bytes'), class: 'label-bold' + = f.number_field :pypi_max_file_size, class: 'form-control' + .form-group + = f.label :generic_packages_max_file_size, _('Generic package file size in bytes'), class: 'label-bold' + = f.number_field :generic_packages_max_file_size, class: 'form-control' + = f.submit _('Save %{name} size limits').html_safe % { name: plan.name.capitalize }, class: 'btn gl-button btn-success' diff --git a/app/views/projects/_merge_request_merge_checks_settings.html.haml b/app/views/projects/_merge_request_merge_checks_settings.html.haml index 073a53d71fd..7a5997bbcfd 100644 --- a/app/views/projects/_merge_request_merge_checks_settings.html.haml +++ b/app/views/projects/_merge_request_merge_checks_settings.html.haml @@ -21,6 +21,6 @@ .text-secondary = s_('ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline.') .form-check.mb-2 - = form.check_box :only_allow_merge_if_all_discussions_are_resolved, class: 'form-check-input' + = form.check_box :only_allow_merge_if_all_discussions_are_resolved, class: 'form-check-input', data: { qa_selector: 'allow_merge_if_all_discussions_are_resolved_checkbox' } = form.label :only_allow_merge_if_all_discussions_are_resolved, class: 'form-check-label' do = s_('ProjectSettings|All discussions must be resolved') diff --git a/changelogs/unreleased/240951-package-size-limits-ui.yml b/changelogs/unreleased/240951-package-size-limits-ui.yml new file mode 100644 index 00000000000..5f98146bb88 --- /dev/null +++ b/changelogs/unreleased/240951-package-size-limits-ui.yml @@ -0,0 +1,5 @@ +--- +title: Add admin UI for adjusting package file size limits +merge_request: 40423 +author: +type: added diff --git a/changelogs/unreleased/250279-unnecessary-spacing-exists-in-issues-list.yml b/changelogs/unreleased/250279-unnecessary-spacing-exists-in-issues-list.yml new file mode 100644 index 00000000000..e335bfa8ddd --- /dev/null +++ b/changelogs/unreleased/250279-unnecessary-spacing-exists-in-issues-list.yml @@ -0,0 +1,5 @@ +--- +title: Remove an extra spacing from Dashboard Issues +merge_request: 42459 +author: Takuya Noguchi +type: fixed diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 1dd1149a9d2..bac8247de2e 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -141,6 +141,8 @@ namespace :admin do get :status_delete_self_monitoring_project end + resources :plan_limits, only: :create + resources :labels resources :runners, only: [:index, :show, :update, :destroy] do diff --git a/doc/administration/reference_architectures/10k_users.md b/doc/administration/reference_architectures/10k_users.md index 6afdf31a481..5f8ab6683a9 100644 --- a/doc/administration/reference_architectures/10k_users.md +++ b/doc/administration/reference_architectures/10k_users.md @@ -167,6 +167,14 @@ added to GitLab to configure SSL certificates. See [NGINX HTTPS documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https) for details on managing SSL certificates and configuring NGINX. +### Readiness checks + +Ensure the external load balancer only routes to working services with built +in monitoring endpoints. The [readiness checks](../../user/admin_area/monitoring/health_check.md) +all require [additional configuration](../monitoring/ip_whitelist.md) +on the nodes being checked, otherwise, the external load balancer will not be able to +connect. + ### Ports The basic ports to be used are shown in the table below. diff --git a/doc/administration/reference_architectures/25k_users.md b/doc/administration/reference_architectures/25k_users.md index 0aef42ec849..2ef555bff29 100644 --- a/doc/administration/reference_architectures/25k_users.md +++ b/doc/administration/reference_architectures/25k_users.md @@ -167,6 +167,14 @@ added to GitLab to configure SSL certificates. See [NGINX HTTPS documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https) for details on managing SSL certificates and configuring NGINX. +### Readiness checks + +Ensure the external load balancer only routes to working services with built +in monitoring endpoints. The [readiness checks](../../user/admin_area/monitoring/health_check.md) +all require [additional configuration](../monitoring/ip_whitelist.md) +on the nodes being checked, otherwise, the external load balancer will not be able to +connect. + ### Ports The basic ports to be used are shown in the table below. diff --git a/doc/administration/reference_architectures/2k_users.md b/doc/administration/reference_architectures/2k_users.md index 5583884e712..34b90964fbf 100644 --- a/doc/administration/reference_architectures/2k_users.md +++ b/doc/administration/reference_architectures/2k_users.md @@ -42,7 +42,7 @@ doesn't require you to provision and maintain a node. To set up GitLab and its components to accommodate up to 2,000 users: -1. [Configure the external load balancing node](#configure-the-load-balancer) +1. [Configure the external load balancing node](#configure-the-external-load-balancer) to handle the load balancing of the two GitLab application services nodes. 1. [Configure PostgreSQL](#configure-postgresql), the database for GitLab. 1. [Configure Redis](#configure-redis). @@ -60,7 +60,7 @@ To set up GitLab and its components to accommodate up to 2,000 users: storage. You can skip this step if you're not using GitLab Pages (which requires NFS). -## Configure the load balancer +## Configure the external load balancer NOTE: **Note:** This architecture has been tested and validated with [HAProxy](https://www.haproxy.org/). @@ -115,6 +115,14 @@ need to add a configuration to GitLab to configure SSL certificates. For details about managing SSL certificates and configuring NGINX, see the [NGINX HTTPS documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https). +### Readiness checks + +Ensure the external load balancer only routes to working services with built +in monitoring endpoints. The [readiness checks](../../user/admin_area/monitoring/health_check.md) +all require [additional configuration](../monitoring/ip_whitelist.md) +on the nodes being checked, otherwise, the external load balancer will not be able to +connect. + ### Ports The basic load balancer ports you should use are described in the following @@ -568,7 +576,7 @@ On each node perform the following: 1. Create/edit `/etc/gitlab/gitlab.rb` and use the following configuration. To maintain uniformity of links across nodes, the `external_url` on the application server should point to the external URL that users will use - to access GitLab. This would be the URL of the [load balancer](#configure-the-load-balancer) + to access GitLab. This would be the URL of the [load balancer](#configure-the-external-load-balancer) which will route traffic to the GitLab application server: ```ruby diff --git a/doc/administration/reference_architectures/3k_users.md b/doc/administration/reference_architectures/3k_users.md index ddc477c156f..be944586e43 100644 --- a/doc/administration/reference_architectures/3k_users.md +++ b/doc/administration/reference_architectures/3k_users.md @@ -162,6 +162,14 @@ added to GitLab to configure SSL certificates. See [NGINX HTTPS documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https) for details on managing SSL certificates and configuring NGINX. +### Readiness checks + +Ensure the external load balancer only routes to working services with built +in monitoring endpoints. The [readiness checks](../../user/admin_area/monitoring/health_check.md) +all require [additional configuration](../monitoring/ip_whitelist.md) +on the nodes being checked, otherwise, the external load balancer will not be able to +connect. + ### Ports The basic ports to be used are shown in the table below. diff --git a/doc/administration/reference_architectures/50k_users.md b/doc/administration/reference_architectures/50k_users.md index 60521c05a6c..e812eed0227 100644 --- a/doc/administration/reference_architectures/50k_users.md +++ b/doc/administration/reference_architectures/50k_users.md @@ -167,6 +167,14 @@ added to GitLab to configure SSL certificates. See [NGINX HTTPS documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https) for details on managing SSL certificates and configuring NGINX. +### Readiness checks + +Ensure the external load balancer only routes to working services with built +in monitoring endpoints. The [readiness checks](../../user/admin_area/monitoring/health_check.md) +all require [additional configuration](../monitoring/ip_whitelist.md) +on the nodes being checked, otherwise, the external load balancer will not be able to +connect. + ### Ports The basic ports to be used are shown in the table below. diff --git a/doc/administration/reference_architectures/5k_users.md b/doc/administration/reference_architectures/5k_users.md index 319820d8268..6dfa588b092 100644 --- a/doc/administration/reference_architectures/5k_users.md +++ b/doc/administration/reference_architectures/5k_users.md @@ -162,6 +162,14 @@ added to GitLab to configure SSL certificates. See [NGINX HTTPS documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https) for details on managing SSL certificates and configuring NGINX. +### Readiness checks + +Ensure the external load balancer only routes to working services with built +in monitoring endpoints. The [readiness checks](../../user/admin_area/monitoring/health_check.md) +all require [additional configuration](../monitoring/ip_whitelist.md) +on the nodes being checked, otherwise, the external load balancer will not be able to +connect. + ### Ports The basic ports to be used are shown in the table below. diff --git a/doc/install/installation.md b/doc/install/installation.md index 13b6fe5b379..e2c77073983 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -311,13 +311,20 @@ sudo adduser --disabled-login --gecos 'GitLab' git ## 6. Database NOTE: **Note:** -Starting from GitLab 12.1, only PostgreSQL is supported. Since GitLab 13.0, we require PostgreSQL 11+. +Starting from GitLab 12.1, only PostgreSQL is supported. Since GitLab 13.0, we [require PostgreSQL 11+](requirements.md#postgresql-requirements). 1. Install the database packages: ```shell sudo apt-get install -y postgresql postgresql-client libpq-dev postgresql-contrib ``` + +1. Verify the PostgreSQL version you have is supported by the version of GitLab you're + installing: + + ```shell + psql --version + ``` 1. Start the PostgreSQL service and confirm that the service is running: diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 921b6dc9b32..da0128fecc3 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -140,7 +140,6 @@ We highly recommend users to use the minimum PostgreSQL versions specified below GitLab version | Minimum PostgreSQL version -|- 10.0 | 9.6 -12.10 | 11 13.0 | 11 You must also ensure the `pg_trgm` and `btree_gist` extensions are [loaded into every diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md index 607678bfe0e..b4867d33644 100644 --- a/doc/user/admin_area/settings/continuous_integration.md +++ b/doc/user/admin_area/settings/continuous_integration.md @@ -196,7 +196,9 @@ To set required pipeline configuration: ![Required pipeline](img/admin_required_pipeline.png) -## Package Registry configuration **(PREMIUM ONLY)** +## Package Registry configuration + +### NPM Forwarding **(PREMIUM ONLY)** GitLab administrators can disable the forwarding of NPM requests to [npmjs.com](https://www.npmjs.com/). @@ -208,3 +210,15 @@ To disable it: 1. Click **Save changes**. ![NPM package requests forwarding](img/admin_package_registry_npm_package_requests_forward.png) + +### Package file size limits + +GitLab administrators can adjust the maximum allowed file size for each package type. + +To set the maximum file size: + +1. Go to **Admin Area > Settings > CI/CD**. +1. Expand the **Package Registry** section. +1. Find the package type you would like to adjust. +1. Enter the maximum file size, in bytes. +1. Click **Save size limits**. diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3f4b8095997..249b2a9e671 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3101,6 +3101,9 @@ msgstr "" msgid "Application ID" msgstr "" +msgid "Application limits saved successfully" +msgstr "" + msgid "Application settings saved successfully" msgstr "" @@ -11378,6 +11381,9 @@ msgstr "" msgid "Generate new token" msgstr "" +msgid "Generic package file size in bytes" +msgstr "" + msgid "Geo" msgstr "" @@ -15403,6 +15409,21 @@ msgstr "" msgid "Max size 15 MB" msgstr "" +msgid "Maximum Conan package file size in bytes" +msgstr "" + +msgid "Maximum Maven package file size in bytes" +msgstr "" + +msgid "Maximum NPM package file size in bytes" +msgstr "" + +msgid "Maximum NuGet package file size in bytes" +msgstr "" + +msgid "Maximum PyPI package file size in bytes" +msgstr "" + msgid "Maximum Users:" msgstr "" @@ -17862,6 +17883,9 @@ msgstr "" msgid "Package deleted successfully" msgstr "" +msgid "Package file size limits" +msgstr "" + msgid "Package recipe already exists" msgstr "" @@ -21987,6 +22011,9 @@ msgstr "" msgid "Save" msgstr "" +msgid "Save %{name} size limits" +msgstr "" + msgid "Save Changes" msgstr "" @@ -23048,6 +23075,9 @@ msgstr "" msgid "Set iteration" msgstr "" +msgid "Set limit to 0 to allow any file size." +msgstr "" + msgid "Set max session time for web terminal." msgstr "" diff --git a/qa/qa/page/project/settings/merge_request.rb b/qa/qa/page/project/settings/merge_request.rb index 0092426b31f..ec8d73df1b3 100644 --- a/qa/qa/page/project/settings/merge_request.rb +++ b/qa/qa/page/project/settings/merge_request.rb @@ -15,6 +15,10 @@ module QA element :radio_button_merge_ff end + view 'app/views/projects/_merge_request_merge_checks_settings.html.haml' do + element :allow_merge_if_all_discussions_are_resolved_checkbox + end + def click_save_changes click_element :save_merge_request_changes end @@ -23,6 +27,11 @@ module QA click_element :radio_button_merge_ff click_save_changes end + + def enable_merge_if_all_disscussions_are_resolved + click_element :allow_merge_if_all_discussions_are_resolved_checkbox + click_save_changes + end end end end diff --git a/rubocop/cop/usage_data/distinct_count_by_large_foreign_key.rb b/rubocop/cop/usage_data/distinct_count_by_large_foreign_key.rb index 36bcda527e8..9fdf52dac8b 100644 --- a/rubocop/cop/usage_data/distinct_count_by_large_foreign_key.rb +++ b/rubocop/cop/usage_data/distinct_count_by_large_foreign_key.rb @@ -22,6 +22,7 @@ module RuboCop def on_send(node) distinct_count?(node) do |method_name, method_arguments| next unless method_arguments && method_arguments.length >= 2 + next if batch_set_to_false?(method_arguments[2]) next if allowed_foreign_key?(method_arguments[1]) add_offense(node, location: :selector, message: format(MSG, method_name)) @@ -37,6 +38,21 @@ module RuboCop def allowed_foreign_keys (cop_config['AllowedForeignKeys'] || []).map(&:to_s) end + + def batch_set_to_false?(options) + return false unless options.is_a?(RuboCop::AST::HashNode) + + batch_set_to_false = false + options.each_pair do |key, value| + next unless value.boolean_type? && value.falsey_literal? + next unless key.type == :sym && key.value == :batch + + batch_set_to_false = true + break + end + + batch_set_to_false + end end end end diff --git a/spec/controllers/admin/plan_limits_controller_spec.rb b/spec/controllers/admin/plan_limits_controller_spec.rb new file mode 100644 index 00000000000..2666925c2b7 --- /dev/null +++ b/spec/controllers/admin/plan_limits_controller_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Admin::PlanLimitsController do + let_it_be(:plan) { create(:plan) } + let_it_be(:plan_limits) { create(:plan_limits, plan: plan) } + + describe 'POST create' do + let(:params) do + { + plan_limits: { + plan_id: plan.id, + conan_max_file_size: file_size, id: plan_limits.id + } + } + end + + context 'with an authenticated admin user' do + let(:file_size) { 10.megabytes } + + it 'updates the plan limits', :aggregate_failures do + sign_in(create(:admin)) + + post :create, params: params + + expect(response).to redirect_to(general_admin_application_settings_path) + expect(plan_limits.reload.conan_max_file_size).to eq(file_size) + end + end + + context 'without admin access' do + let(:file_size) { 1.megabytes } + + it 'returns `not_found`' do + sign_in(create(:user)) + + post :create, params: params + + expect(response).to have_gitlab_http_status(:not_found) + expect(plan_limits.conan_max_file_size).not_to eq(file_size) + end + end + end +end diff --git a/spec/factories/plan_limits.rb b/spec/factories/plan_limits.rb index 4aea09618d0..ae892307193 100644 --- a/spec/factories/plan_limits.rb +++ b/spec/factories/plan_limits.rb @@ -7,5 +7,14 @@ FactoryBot.define do trait :default_plan do plan factory: :default_plan end + + trait :with_package_file_sizes do + conan_max_file_size { 100 } + maven_max_file_size { 100 } + npm_max_file_size { 100 } + nuget_max_file_size { 100 } + pypi_max_file_size { 100 } + generic_packages_max_file_size { 100 } + end end end diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb index 6b8df8467e5..e705f2916da 100644 --- a/spec/features/expand_collapse_diffs_spec.rb +++ b/spec/features/expand_collapse_diffs_spec.rb @@ -7,11 +7,6 @@ RSpec.describe 'Expand and collapse diffs', :js do let(:project) { create(:project, :repository) } before do - # Set the limits to those when these specs were written, to avoid having to - # update the test repo every time we change them. - allow(Gitlab::Git::Diff).to receive(:size_limit).and_return(100.kilobytes) - allow(Gitlab::Git::Diff).to receive(:collapse_limit).and_return(10.kilobytes) - sign_in(create(:admin)) # Ensure that undiffable.md is in .gitattributes diff --git a/spec/features/merge_request/user_expands_diff_spec.rb b/spec/features/merge_request/user_expands_diff_spec.rb index b0cfa8d0d54..0e39cce13a1 100644 --- a/spec/features/merge_request/user_expands_diff_spec.rb +++ b/spec/features/merge_request/user_expands_diff_spec.rb @@ -7,9 +7,6 @@ RSpec.describe 'User expands diff', :js do let(:merge_request) { create(:merge_request, source_branch: 'expand-collapse-files', source_project: project, target_project: project) } before do - allow(Gitlab::Git::Diff).to receive(:size_limit).and_return(100.kilobytes) - allow(Gitlab::Git::Diff).to receive(:collapse_limit).and_return(10.kilobytes) - visit(diffs_project_merge_request_path(project, merge_request)) wait_for_requests diff --git a/spec/features/merge_request/user_views_auto_expanding_diff_spec.rb b/spec/features/merge_request/user_views_auto_expanding_diff_spec.rb index 585a389157e..1748f66c934 100644 --- a/spec/features/merge_request/user_views_auto_expanding_diff_spec.rb +++ b/spec/features/merge_request/user_views_auto_expanding_diff_spec.rb @@ -11,9 +11,6 @@ RSpec.describe 'User views diffs file-by-file', :js do let(:user) { create(:user, view_diffs_file_by_file: true) } before do - allow(Gitlab::Git::Diff).to receive(:size_limit).and_return(100.kilobytes) - allow(Gitlab::Git::Diff).to receive(:collapse_limit).and_return(10.kilobytes) - project.add_developer(user) sign_in(user) diff --git a/spec/frontend/releases/components/app_index_spec.js b/spec/frontend/releases/components/app_index_spec.js index 7c0b35f5eda..bcb87509cc3 100644 --- a/spec/frontend/releases/components/app_index_spec.js +++ b/spec/frontend/releases/components/app_index_spec.js @@ -27,15 +27,18 @@ describe('Releases App ', () => { tagName: `${index}.00`, })); - const defaultProps = { + const defaultInitialState = { projectId: 'gitlab-ce', projectPath: 'gitlab-org/gitlab-ce', documentationPath: 'help/releases', illustrationPath: 'illustration/path', }; - const createComponent = (propsData = defaultProps) => { - const listModule = createListModule({}); + const createComponent = (stateUpdates = {}) => { + const listModule = createListModule({ + ...defaultInitialState, + ...stateUpdates, + }); fetchReleaseSpy = jest.spyOn(listModule.actions, 'fetchReleases'); @@ -51,7 +54,6 @@ describe('Releases App ', () => { wrapper = shallowMount(ReleasesApp, { store, localVue, - propsData, }); }; @@ -68,13 +70,9 @@ describe('Releases App ', () => { createComponent(); }); - it('calls fetchRelease with the page, project ID, and project path', () => { + it('calls fetchRelease with the page parameter', () => { expect(fetchReleaseSpy).toHaveBeenCalledTimes(1); - expect(fetchReleaseSpy).toHaveBeenCalledWith(expect.anything(), { - page: null, - projectId: defaultProps.projectId, - projectPath: defaultProps.projectPath, - }); + expect(fetchReleaseSpy).toHaveBeenCalledWith(expect.anything(), { page: null }); }); }); @@ -156,7 +154,7 @@ describe('Releases App ', () => { const newReleasePath = 'path/to/new/release'; beforeEach(() => { - createComponent({ ...defaultProps, newReleasePath }); + createComponent({ ...defaultInitialState, newReleasePath }); }); it('renders the "New release" button', () => { diff --git a/spec/frontend/releases/components/releases_pagination_graphql_spec.js b/spec/frontend/releases/components/releases_pagination_graphql_spec.js index ae50c6ae4a6..b01a28eb6c3 100644 --- a/spec/frontend/releases/components/releases_pagination_graphql_spec.js +++ b/spec/frontend/releases/components/releases_pagination_graphql_spec.js @@ -143,7 +143,7 @@ describe('~/releases/components/releases_pagination_graphql.vue', () => { it('calls fetchReleasesGraphQl with the correct after cursor', () => { expect(listModule.actions.fetchReleasesGraphQl.mock.calls).toEqual([ - [expect.anything(), { projectPath, after: cursors.endCursor }], + [expect.anything(), { after: cursors.endCursor }], ]); }); @@ -161,7 +161,7 @@ describe('~/releases/components/releases_pagination_graphql.vue', () => { it('calls fetchReleasesGraphQl with the correct before cursor', () => { expect(listModule.actions.fetchReleasesGraphQl.mock.calls).toEqual([ - [expect.anything(), { projectPath, before: cursors.startCursor }], + [expect.anything(), { before: cursors.startCursor }], ]); }); diff --git a/spec/frontend/releases/components/releases_pagination_rest_spec.js b/spec/frontend/releases/components/releases_pagination_rest_spec.js index 7ff5200a289..4fd3e085fc9 100644 --- a/spec/frontend/releases/components/releases_pagination_rest_spec.js +++ b/spec/frontend/releases/components/releases_pagination_rest_spec.js @@ -59,7 +59,7 @@ describe('~/releases/components/releases_pagination_rest.vue', () => { it('calls fetchReleasesRest with the correct page', () => { expect(listModule.actions.fetchReleasesRest.mock.calls).toEqual([ - [expect.anything(), { projectId, page: newPage }], + [expect.anything(), { page: newPage }], ]); }); diff --git a/spec/frontend/releases/stores/modules/list/actions_spec.js b/spec/frontend/releases/stores/modules/list/actions_spec.js index 569c931757d..95e30659d6c 100644 --- a/spec/frontend/releases/stores/modules/list/actions_spec.js +++ b/spec/frontend/releases/stores/modules/list/actions_spec.js @@ -23,11 +23,16 @@ describe('Releases State actions', () => { let pageInfo; let releases; let graphqlReleasesResponse; - let projectPath; + + const projectPath = 'root/test-project'; + const projectId = 19; beforeEach(() => { mockedState = { - ...createState({}), + ...createState({ + projectId, + projectPath, + }), featureFlags: { graphqlReleaseData: true, graphqlReleasesPage: true, @@ -38,7 +43,6 @@ describe('Releases State actions', () => { pageInfo = parseIntPagination(pageInfoHeadersWithoutPagination); releases = convertObjectPropsToCamelCase(originalReleases, { deep: true }); graphqlReleasesResponse = cloneDeep(originalGraphqlReleasesResponse); - projectPath = 'root/test-project'; }); describe('requestReleases', () => { @@ -51,7 +55,7 @@ describe('Releases State actions', () => { describe('success', () => { it('dispatches requestReleases and receiveReleasesSuccess', done => { jest.spyOn(gqClient, 'query').mockImplementation(({ query, variables }) => { - expect(query).toEqual(allReleasesQuery); + expect(query).toBe(allReleasesQuery); expect(variables).toEqual({ fullPath: projectPath, }); @@ -60,7 +64,7 @@ describe('Releases State actions', () => { testAction( fetchReleases, - { projectPath }, + {}, mockedState, [], [ @@ -83,7 +87,7 @@ describe('Releases State actions', () => { testAction( fetchReleases, - { projectPath }, + {}, mockedState, [], [ @@ -107,14 +111,14 @@ describe('Releases State actions', () => { describe('success', () => { it('dispatches requestReleases and receiveReleasesSuccess', done => { jest.spyOn(api, 'releases').mockImplementation((id, options) => { - expect(id).toEqual(1); - expect(options.page).toEqual('1'); + expect(id).toBe(projectId); + expect(options.page).toBe('1'); return Promise.resolve({ data: releases, headers: pageInfoHeadersWithoutPagination }); }); testAction( fetchReleases, - { projectId: 1 }, + {}, mockedState, [], [ @@ -132,13 +136,13 @@ describe('Releases State actions', () => { it('dispatches requestReleases and receiveReleasesSuccess on page two', done => { jest.spyOn(api, 'releases').mockImplementation((_, options) => { - expect(options.page).toEqual('2'); + expect(options.page).toBe('2'); return Promise.resolve({ data: releases, headers: pageInfoHeadersWithoutPagination }); }); testAction( fetchReleases, - { page: '2', projectId: 1 }, + { page: '2' }, mockedState, [], [ @@ -161,7 +165,7 @@ describe('Releases State actions', () => { testAction( fetchReleases, - { projectId: null }, + {}, mockedState, [], [ diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb index 396b01edbfa..fedafff0d1b 100644 --- a/spec/routing/admin_routing_spec.rb +++ b/spec/routing/admin_routing_spec.rb @@ -178,3 +178,9 @@ RSpec.describe Admin::SessionsController, "routing" do expect(post("/admin/session/destroy")).to route_to('admin/sessions#destroy') end end + +RSpec.describe Admin::PlanLimitsController, "routing" do + it "to #create" do + expect(post("/admin/plan_limits")).to route_to('admin/plan_limits#create') + end +end diff --git a/spec/rubocop/cop/usage_data/distinct_count_by_large_foreign_key_spec.rb b/spec/rubocop/cop/usage_data/distinct_count_by_large_foreign_key_spec.rb index df8130b9c5d..8b6a2eac349 100644 --- a/spec/rubocop/cop/usage_data/distinct_count_by_large_foreign_key_spec.rb +++ b/spec/rubocop/cop/usage_data/distinct_count_by_large_foreign_key_spec.rb @@ -21,11 +21,23 @@ RSpec.describe RuboCop::Cop::UsageData::DistinctCountByLargeForeignKey, type: :r subject(:cop) { described_class.new(config) } context 'when counting by disallowed key' do - it 'register an offence' do + it 'registers an offence' do inspect_source('distinct_count(Issue, :creator_id)') expect(cop.offenses.size).to eq(1) end + + it 'does not register an offence when batch is false' do + inspect_source('distinct_count(Issue, :creator_id, batch: false)') + + expect(cop.offenses).to be_empty + end + + it 'register an offence when batch is true' do + inspect_source('distinct_count(Issue, :creator_id, batch: true)') + + expect(cop.offenses.size).to eq(1) + end end context 'when calling by allowed key' do diff --git a/spec/views/admin/application_settings/_package_registry.html.haml_spec.rb b/spec/views/admin/application_settings/_package_registry.html.haml_spec.rb new file mode 100644 index 00000000000..ef40829c29b --- /dev/null +++ b/spec/views/admin/application_settings/_package_registry.html.haml_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'admin/application_settings/_package_registry' do + let_it_be(:admin) { create(:admin) } + let_it_be(:default_plan_limits) { create(:plan_limits, :default_plan, :with_package_file_sizes) } + let_it_be(:application_setting) { build(:application_setting) } + let(:page) { Capybara::Node::Simple.new(rendered) } + + before do + assign(:application_setting, application_setting) + allow(view).to receive(:current_user) { admin } + allow(view).to receive(:expanded) { true } + end + + subject { render partial: 'admin/application_settings/package_registry' } + + context 'package file size limits' do + before do + assign(:plans, [default_plan_limits.plan]) + end + + it 'has fields for max package file sizes' do + subject + + expect(rendered).to have_field('Maximum Conan package file size in bytes', type: 'number') + expect(page.find_field('Maximum Conan package file size in bytes').value).to eq(default_plan_limits.conan_max_file_size.to_s) + + expect(rendered).to have_field('Maximum Maven package file size in bytes', type: 'number') + expect(page.find_field('Maximum Maven package file size in bytes').value).to eq(default_plan_limits.maven_max_file_size.to_s) + + expect(rendered).to have_field('Maximum NPM package file size in bytes', type: 'number') + expect(page.find_field('Maximum NPM package file size in bytes').value).to eq(default_plan_limits.npm_max_file_size.to_s) + + expect(rendered).to have_field('Maximum NuGet package file size in bytes', type: 'number') + expect(page.find_field('Maximum NuGet package file size in bytes').value).to eq(default_plan_limits.nuget_max_file_size.to_s) + + expect(rendered).to have_field('Maximum PyPI package file size in bytes', type: 'number') + expect(page.find_field('Maximum PyPI package file size in bytes').value).to eq(default_plan_limits.pypi_max_file_size.to_s) + end + + it 'does not display the plan name when there is only one plan' do + subject + + expect(page).not_to have_content('Default') + end + end + + context 'with multiple plans' do + let_it_be(:plan) { create(:plan, name: 'Gold') } + let_it_be(:gold_plan_limits) { create(:plan_limits, :with_package_file_sizes, plan: plan) } + + before do + assign(:plans, [default_plan_limits.plan, gold_plan_limits.plan]) + end + + it 'displays the plan name when there is more than one plan' do + subject + + expect(page).to have_content('Default') + expect(page).to have_content('Gold') + end + end +end