diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2024-01-15 15:10:21 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2024-01-15 15:10:21 +0300 |
commit | f58a5001b9c4988d8b95178b028a3d82bb346e28 (patch) | |
tree | e6092641f71f45c88d833f66b95b91de1f0ebcb3 | |
parent | 7cd86149222a2dab444a28bb999ecedd3e50e242 (diff) |
Add latest changes from gitlab-org/gitlab@master
55 files changed, 451 insertions, 248 deletions
diff --git a/.markdownlint.yml b/.markdownlint.yml index 5a9e1f05e0e..4107e8d1073 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -1,31 +1,39 @@ --- # Base Markdownlint configuration # Extended Markdownlint configuration in doc/.markdownlint/ +# See https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md for explanations of each rule +# First, set the default default: true -first-header-h1: true -header-style: + +# Per-rule settings in alphabetical order +code-block-style: # MD046 + style: "fenced" +emphasis-style: false # MD049 +first-header-h1: true # MD002 +first-line-h1: false # MD041 +header-style: # MD003 style: "atx" -ul-style: - style: "dash" -no-trailing-spaces: false -line-length: false -no-duplicate-header: +hr-style: # MD035 + style: "---" +line-length: false # MD013 +link-fragments: false # MD051 +no-duplicate-header: # MD024 allow_different_nesting: true -no-trailing-punctuation: +no-emphasis-as-heading: false # MD036 +no-inline-html: false # MD033 +no-trailing-punctuation: # MD026 punctuation: ".,;:!。,;:!?" -ol-prefix: +no-trailing-spaces: false # MD009 +ol-prefix: # MD029 style: "one" -no-inline-html: false -hr-style: - style: "---" -no-emphasis-as-heading: false -first-line-h1: false -code-block-style: - style: "fenced" -emphasis-style: false -link-fragments: false -reference-links-images: false -proper-names: +reference-links-images: false # MD052 +ul-style: # MD004 + style: "dash" + +# Keep this item last due to length +proper-names: # MD044 + code_blocks: false + html_elements: false names: [ "Akismet", "Alertmanager", @@ -150,5 +158,3 @@ proper-names: "YAML", "YouTrack" ] - code_blocks: false - html_elements: false diff --git a/Gemfile.checksum b/Gemfile.checksum index 7a8040c629c..7c3fe677ea8 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -492,7 +492,7 @@ {"name":"rails","version":"7.0.8","platform":"ruby","checksum":"8e43af921acf766fb429126f020ec90c3b25809631f8fbdff95c3553828d5867"}, {"name":"rails-controller-testing","version":"1.0.5","platform":"ruby","checksum":"741448db59366073e86fc965ba403f881c636b79a2c39a48d0486f2607182e94"}, {"name":"rails-dom-testing","version":"2.0.3","platform":"ruby","checksum":"b140c4f39f6e609c8113137b9a60dfc2ecb89864e496f87f23a68b3b8f12d8d1"}, -{"name":"rails-html-sanitizer","version":"1.5.0","platform":"ruby","checksum":"bf326075e8a968cd882c30b15a4c9100059be3af2356093dc68324ec3bd9ea79"}, +{"name":"rails-html-sanitizer","version":"1.6.0","platform":"ruby","checksum":"86e9f19d2e6748890dcc2633c8945ca45baa08a1df9d8c215ce17b3b0afaa4de"}, {"name":"rails-i18n","version":"7.0.3","platform":"ruby","checksum":"e3158e98c5332d129fd5131f171ac575eb30dbb8919b21595382b08850cf2bd3"}, {"name":"railties","version":"7.0.8","platform":"ruby","checksum":"12325c3933efd33f8ead640197dec3b8c27c8d45607dd68b7b925896bf09cc69"}, {"name":"rainbow","version":"3.1.1","platform":"ruby","checksum":"039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a"}, diff --git a/Gemfile.lock b/Gemfile.lock index ba111827700..c7be326191a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1324,8 +1324,9 @@ GEM rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.5.0) - loofah (~> 2.19, >= 2.19.1) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) rails-i18n (7.0.3) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) diff --git a/app/assets/javascripts/groups/components/overview_tabs.vue b/app/assets/javascripts/groups/components/overview_tabs.vue index 8781f03a412..82bd4765986 100644 --- a/app/assets/javascripts/groups/components/overview_tabs.vue +++ b/app/assets/javascripts/groups/components/overview_tabs.vue @@ -1,5 +1,5 @@ <script> -import { GlTabs, GlTab, GlSearchBoxByType, GlSorting, GlSortingItem } from '@gitlab/ui'; +import { GlTabs, GlTab, GlSearchBoxByType, GlSorting } from '@gitlab/ui'; import { isString, debounce } from 'lodash'; import { __ } from '~/locale'; import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants'; @@ -30,7 +30,6 @@ export default { GroupsApp, GlSearchBoxByType, GlSorting, - GlSortingItem, SubgroupsAndProjectsEmptyState, SharedProjectsEmptyState, ArchivedProjectsEmptyState, @@ -84,6 +83,9 @@ export default { sortQueryStringValue() { return this.isAscending ? this.sort.asc : this.sort.desc; }, + activeTabSortOptions() { + return this.activeTab.sortingItems.map(({ label }) => ({ value: label, text: label })); + }, }, mounted() { this.search = this.$route.query?.filter || ''; @@ -178,12 +180,14 @@ export default { this.handleSearchOrSortChange(); }, - handleSortingItemClick(sortingItem) { - if (sortingItem === this.sort) { + handleSortingItemClick(value) { + const selectedSortingItem = this.activeTab.sortingItems.find((item) => item.label === value); + + if (selectedSortingItem === this.sort) { return; } - this.sort = sortingItem; + this.sort = selectedSortingItem; this.handleSearchOrSortChange(); }, @@ -239,16 +243,11 @@ export default { data-testid="group_sort_by_dropdown" :text="sort.label" :is-ascending="isAscending" + :sort-options="activeTabSortOptions" + :sort-by="sort.label" + @sortByChange="handleSortingItemClick" @sortDirectionChange="handleSortDirectionChange" - > - <gl-sorting-item - v-for="sortingItem in activeTab.sortingItems" - :key="sortingItem.label" - :active="sortingItem === sort" - @click="handleSortingItemClick(sortingItem)" - >{{ sortingItem.label }}</gl-sorting-item - > - </gl-sorting> + /> </div> </div> </li> diff --git a/app/assets/javascripts/pages/sessions/new/index.js b/app/assets/javascripts/pages/sessions/new/index.js index 32df2911a48..ee1a7633a11 100644 --- a/app/assets/javascripts/pages/sessions/new/index.js +++ b/app/assets/javascripts/pages/sessions/new/index.js @@ -16,7 +16,7 @@ new SigninTabsMemoizer(); // eslint-disable-line no-new new NoEmojiValidator(); // eslint-disable-line no-new new OAuthRememberMe({ - container: $('.omniauth-container'), + container: $('.js-oauth-login'), }).bindEvents(); // Save the URL fragment from the current window location. This will be present if the user was diff --git a/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js b/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js index bad8a7cedc6..3336b094560 100644 --- a/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js +++ b/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js @@ -20,8 +20,8 @@ export default class OAuthRememberMe { toggleRememberMe(event) { const rememberMe = $(event.target).is(':checked'); - $('.js-oauth-login', this.container).each((i, element) => { - const $form = $(element).parent('form'); + $('.js-oauth-login form', this.container).each((_, form) => { + const $form = $(form); const href = $form.attr('action'); if (rememberMe) { diff --git a/app/assets/javascripts/pages/sessions/new/preserve_url_fragment.js b/app/assets/javascripts/pages/sessions/new/preserve_url_fragment.js index 70e5e336e78..54ec3c52f62 100644 --- a/app/assets/javascripts/pages/sessions/new/preserve_url_fragment.js +++ b/app/assets/javascripts/pages/sessions/new/preserve_url_fragment.js @@ -12,7 +12,7 @@ export default function preserveUrlFragment(fragment = '') { // Append the fragment to all sign-in/sign-up form actions so it is preserved when the user is // eventually redirected back to the originally requested URL. - const forms = document.querySelectorAll('#signin-container .tab-content form'); + const forms = document.querySelectorAll('.js-non-oauth-login form'); Array.prototype.forEach.call(forms, (form) => { const actionWithFragment = setUrlFragment(form.getAttribute('action'), `#${normalFragment}`); form.setAttribute('action', actionWithFragment); @@ -20,7 +20,7 @@ export default function preserveUrlFragment(fragment = '') { // Append a redirect_fragment query param to all oauth provider links. The redirect_fragment // query param will be available in the omniauth callback upon successful authentication - const oauthForms = document.querySelectorAll('#signin-container .omniauth-container form'); + const oauthForms = document.querySelectorAll('.js-oauth-login form'); Array.prototype.forEach.call(oauthForms, (oauthForm) => { const newHref = mergeUrlParams( { redirect_fragment: normalFragment }, diff --git a/app/assets/javascripts/vue_shared/components/timezone_dropdown/timezone_dropdown.vue b/app/assets/javascripts/vue_shared/components/timezone_dropdown/timezone_dropdown.vue index 6764ad4ce73..d4d241b12ec 100644 --- a/app/assets/javascripts/vue_shared/components/timezone_dropdown/timezone_dropdown.vue +++ b/app/assets/javascripts/vue_shared/components/timezone_dropdown/timezone_dropdown.vue @@ -110,6 +110,7 @@ export default { :no-results-text="$options.translations.noResultsText" :selected="tzValue" block + fluid-width searchable @search="setSearchTerm" @select="selectTimezone" diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index b9a59047238..1affdd8f433 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -450,6 +450,7 @@ module ApplicationSettingsHelper :issues_create_limit, :notes_create_limit, :notes_create_limit_allowlist_raw, + :members_delete_limit, :raw_blob_request_limit, :project_import_limit, :project_export_limit, diff --git a/app/helpers/time_zone_helper.rb b/app/helpers/time_zone_helper.rb index 29bd5a84651..3ea043557b8 100644 --- a/app/helpers/time_zone_helper.rb +++ b/app/helpers/time_zone_helper.rb @@ -33,6 +33,19 @@ module TimeZoneHelper end end + # The identifiers in `timezone_data` are not unique. Some cities (e.g. London and Edinburgh) have + # the same `identifier` value (e.g. "Europe/London"). + # This method merges such entries into one, joining the city names. + # This unique list is better suited for selectboxes etc. + def timezone_data_with_unique_identifiers(format: :short) + timezone_data(format: format) + .group_by { |entry| entry[:identifier] } + .map do |_identifier, entries| + names = entries.map { |entry| entry[:name] }.sort.join(', ') # rubocop:disable Rails/Pluck -- Not a ActiveRecord object + entries.first.merge({ name: names }) + end + end + def local_timezone_instance(timezone) return Time.zone if timezone.blank? diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 71dc5521a4d..35d4722b711 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -579,6 +579,7 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord :max_import_size, :max_pages_custom_domains_per_project, :max_terraform_state_size_bytes, + :members_delete_limit, :notes_create_limit, :package_registry_cleanup_policies_worker_capacity, :packages_cleanup_package_file_worker_capacity, @@ -594,6 +595,11 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord :users_get_by_id_limit end + jsonb_accessor :rate_limits, + members_delete_limit: [:integer, { default: 60 }] + + validates :rate_limits, json_schema: { filename: "application_setting_rate_limits" } + validates :search_rate_limit_allowlist, length: { maximum: 100, message: N_('is too long (maximum is 100 entries)') }, allow_nil: false diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index e18476bbae6..d1899b18a4f 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -137,6 +137,7 @@ module ApplicationSettingImplementation mirror_available: true, notes_create_limit: 300, notes_create_limit_allowlist: [], + members_delete_limit: 60, notify_on_unknown_sign_in: true, outbound_local_requests_whitelist: [], password_authentication_enabled_for_git: true, diff --git a/app/models/integrations/diffblue_cover.rb b/app/models/integrations/diffblue_cover.rb index 888aa9a5ffd..c0e0cae2b33 100644 --- a/app/models/integrations/diffblue_cover.rb +++ b/app/models/integrations/diffblue_cover.rb @@ -6,7 +6,7 @@ module Integrations section: SECTION_TYPE_CONNECTION, type: :password, title: -> { s_('DiffblueCover|License key') }, - description: -> { s_('DiffblueCover|Diffblue Cover license key') }, + description: -> { s_('DiffblueCover|Diffblue Cover license key.') }, non_empty_password_title: -> { s_('DiffblueCover|License key') }, non_empty_password_help: -> { s_( @@ -21,7 +21,7 @@ module Integrations format( s_( 'DiffblueCover|Enter your Diffblue Cover license key or ' \ - 'visit %{diffblue_link} to obtain a free trial license.' + 'go to %{diffblue_link} to obtain a free trial license.' ), diffblue_link: diffblue_link ) @@ -30,7 +30,7 @@ module Integrations field :diffblue_access_token_name, section: SECTION_TYPE_CONFIGURATION, title: -> { s_('DiffblueCover|Name') }, - description: -> { s_('DiffblueCover|Access token name used by Diffblue Cover in pipelines') }, + description: -> { s_('DiffblueCover|Access token name used by Diffblue Cover in pipelines.') }, required: true, placeholder: -> { s_('DiffblueCover|My token name') } @@ -38,7 +38,7 @@ module Integrations section: SECTION_TYPE_CONFIGURATION, type: :password, title: -> { s_('DiffblueCover|Secret') }, - description: -> { s_('DiffblueCover|Access token secret used by Diffblue Cover in pipelines') }, + description: -> { s_('DiffblueCover|Access token secret used by Diffblue Cover in pipelines.') }, non_empty_password_title: -> { s_('DiffblueCover|Secret') }, non_empty_password_help: -> { s_('DiffblueCover|Leave blank to use your current secret value.') }, required: true, @@ -82,8 +82,8 @@ module Integrations title: s_('DiffblueCover|Integration details'), description: s_( - 'DiffblueCover|Diffblue Cover is a reinforcement learning AI platform that automatically ' \ - 'writes comprehensive, human-like Java unit tests. Integrate the power of Diffblue ' \ + 'DiffblueCover|Diffblue Cover is a generative AI platform that automatically ' \ + 'writes comprehensive, human-like Java unit tests. Integrate Diffblue ' \ 'Cover into your CI/CD workflow for fully autonomous operation.' ) }, @@ -91,9 +91,9 @@ module Integrations type: SECTION_TYPE_CONFIGURATION, title: s_('DiffblueCover|Access token'), description: - 'A GitLab access token is required in allow Diffblue Cover to access your project. ' \ - 'Use a GitLab access token with a <code>Developer</code> role, plus ' \ - '<code>api</code> and <code>write_repository</code> scopes.' + 'You must have a GitLab access token for Diffblue Cover to access your project. ' \ + 'Use a GitLab access token with at least the Developer role and ' \ + 'the <code>api</code> and <code>write_repository</code> permissions.' } ] end diff --git a/app/validators/json_schemas/application_setting_rate_limits.json b/app/validators/json_schemas/application_setting_rate_limits.json new file mode 100644 index 00000000000..e74295291df --- /dev/null +++ b/app/validators/json_schemas/application_setting_rate_limits.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Application rate limits", + "type": "object", + "additionalProperties": false, + "properties": { + "members_delete_limit": { + "type": "integer", + "minimum": 0, + "description": "Number of project or group members a user can delete per minute." + } + } +} diff --git a/app/views/admin/application_settings/_members_api_limits.html.haml b/app/views/admin/application_settings/_members_api_limits.html.haml new file mode 100644 index 00000000000..3065c62b7e2 --- /dev/null +++ b/app/views/admin/application_settings/_members_api_limits.html.haml @@ -0,0 +1,21 @@ +%section.settings.as-members-api-limits.no-animate#js-members-api-limits-settings{ class: ('expanded' if expanded_by_default?) } + .settings-header + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only + = _('Members API rate limit') + = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do + = expanded_by_default? ? _('Collapse') : _('Expand') + %p.gl-text-secondary + = _('Limit the number of project or group members a user can delete per minute through API requests.') + = link_to _('Learn more.'), help_page_path('administration/settings/rate_limit_on_members_api'), target: '_blank', rel: 'noopener noreferrer' + .settings-content + = gitlab_ui_form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-members-api-limits-settings'), html: { class: 'fieldset-form' } do |f| + = form_errors(@application_setting) + + %fieldset + .form-group + = f.label :members_delete_limit, _('Maximum requests per minute per group / project'), class: 'label-bold' + = f.number_field :members_delete_limit, min: 0, class: 'form-control gl-form-input' + .form-text.gl-text-gray-600 + = _("Set to 0 to disable the limit.") + + = f.submit _('Save changes'), pajamas_button: true diff --git a/app/views/admin/application_settings/network.html.haml b/app/views/admin/application_settings/network.html.haml index ae5f7a5cec3..c2f19c11b4e 100644 --- a/app/views/admin/application_settings/network.html.haml +++ b/app/views/admin/application_settings/network.html.haml @@ -151,6 +151,8 @@ = render 'projects_api_limits' += render 'members_api_limits' + %section.settings.as-import-export-limits.no-animate#js-import-export-limits-settings{ class: ('expanded' if expanded_by_default?) } .settings-header %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only diff --git a/app/views/admin/sessions/new.html.haml b/app/views/admin/sessions/new.html.haml index 8ba5329d15e..e2f5ef5d786 100644 --- a/app/views/admin/sessions/new.html.haml +++ b/app/views/admin/sessions/new.html.haml @@ -4,7 +4,7 @@ .row.gl-mt-5.justify-content-center .col-md-5 .login-page - #signin-container.borderless + .borderless - if any_form_based_providers_enabled? = render 'devise/shared/tabs_ldap', show_password_form: allow_admin_mode_password_authentication_for_web?, render_signup_link: false .tab-content diff --git a/app/views/admin/sessions/two_factor.html.haml b/app/views/admin/sessions/two_factor.html.haml index 4b78a6ce748..898d47f446a 100644 --- a/app/views/admin/sessions/two_factor.html.haml +++ b/app/views/admin/sessions/two_factor.html.haml @@ -4,7 +4,7 @@ .row.justify-content-center .col-md-5 .login-page - #signin-container.borderless + .borderless .login-box.gl-p-5 .login-body - if current_user.two_factor_enabled? diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index c7cbf09d846..728728ea653 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -9,26 +9,23 @@ = render_if_exists "layouts/google_tag_manager_body" -#signin-container +.js-non-oauth-login - if any_form_based_providers_enabled? = render 'devise/shared/tabs_ldap', render_signup_link: false .tab-content - if password_authentication_enabled_for_web? || ldap_sign_in_enabled? || crowd_enabled? = render 'devise/shared/signin_box' - - -# Show a message if none of the mechanisms above are enabled - - if !password_authentication_enabled_for_web? && !ldap_sign_in_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?) - %div - = _('No authentication methods configured.') - - - if Gitlab::CurrentSettings.current_application_settings.terms - %p.gl-px-5 - = html_escape(s_("SignUp|By signing in you accept the %{link_start}Terms of Use and acknowledge the Privacy Statement and Cookie Policy%{link_end}.")) % { link_start: "<a href='#{terms_path}' target='_blank' rel='noreferrer noopener'>".html_safe, - link_end: '</a>'.html_safe } - - - if allow_signup? - %p.gl-mt-3.gl-text-center - = _("Don't have an account yet?") - = link_to _("Register now"), new_registration_path(:user, invite_email: @invite_email), data: { testid: 'register-link' } - - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? - = render 'devise/shared/omniauth_box' +-# Show a message if none of the mechanisms above are enabled +- if !password_authentication_enabled_for_web? && !ldap_sign_in_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?) + %div + = _('No authentication methods configured.') +- if Gitlab::CurrentSettings.current_application_settings.terms + %p.gl-px-5 + = html_escape(s_("SignUp|By signing in you accept the %{link_start}Terms of Use and acknowledge the Privacy Statement and Cookie Policy%{link_end}.")) % { link_start: "<a href='#{terms_path}' target='_blank' rel='noreferrer noopener'>".html_safe, + link_end: '</a>'.html_safe } +- if allow_signup? + %p.gl-mt-3.gl-text-center + = _("Don't have an account yet?") + = link_to _("Register now"), new_registration_path(:user, invite_email: @invite_email), data: { testid: 'register-link' } +- if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? + = render 'devise/shared/omniauth_box' diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml index be559b30ce9..8197abcc787 100644 --- a/app/views/devise/shared/_omniauth_box.html.haml +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -4,12 +4,11 @@ .omniauth-divider.gl-display-flex.gl-align-items-center = _("or sign in with") -.gl-mt-5.gl-px-5.omniauth-container.gl-text-center.gl-display-flex.gl-flex-direction-column.gl-gap-3 +.gl-mt-5.gl-px-5.gl-text-center.gl-display-flex.gl-flex-direction-column.gl-gap-3.js-oauth-login - enabled_button_based_providers.each do |provider| = render 'devise/shared/omniauth_provider_button', href: omniauth_authorize_path(:user, provider), provider: provider, - classes: 'js-oauth-login', data: { testid: test_id_for_provider(provider) }, id: "oauth-login-#{provider}" - if render_remember_me diff --git a/app/views/devise/shared/_omniauth_provider_button.haml b/app/views/devise/shared/_omniauth_provider_button.haml index e92b41a6254..c33e2253bb1 100644 --- a/app/views/devise/shared/_omniauth_provider_button.haml +++ b/app/views/devise/shared/_omniauth_provider_button.haml @@ -1,4 +1,4 @@ -- button_options = { class: classes, data: data, id: id } +- button_options = { class: local_assigns.fetch(:classes, nil) || nil, data: data, id: id } = render Pajamas::ButtonComponent.new(href: href, method: :post, form: true, block: true, button_options: button_options) do - if provider_has_icon?(provider) diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 79b2726ed2d..9f33ad0c2d4 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -66,7 +66,7 @@ %h4.gl-my-0= s_("Profiles|Time settings") %p.gl-text-secondary= s_("Profiles|Set your local time zone.") = f.label :user_timezone, _("Time zone") - .js-timezone-dropdown{ data: { timezone_data: timezone_data.to_json, initial_value: @user.timezone, name: 'user[timezone]' } } + .js-timezone-dropdown{ data: { timezone_data: timezone_data_with_unique_identifiers.to_json, initial_value: @user.timezone, name: 'user[timezone]' } } .settings-section.js-search-settings-section.gl-border-t.gl-pt-6 .settings-sticky-header diff --git a/data/deprecations/15-3-omniauth-crowd.yml b/data/deprecations/15-3-omniauth-crowd.yml index 7c28226a674..2230bbd17ed 100644 --- a/data/deprecations/15-3-omniauth-crowd.yml +++ b/data/deprecations/15-3-omniauth-crowd.yml @@ -3,13 +3,13 @@ # - title: "Atlassian Crowd OmniAuth provider" # (required) The name of the feature to be deprecated announcement_milestone: "15.3" # (required) The milestone when this feature was first announced as deprecated. - removal_milestone: "17.0" # (required) The milestone when this feature is planned to be removed + removal_milestone: "18.0" # (required) The milestone when this feature is planned to be removed breaking_change: true # (required) If this deprecation is a breaking change, set this value to true reporter: hsutor # (required) GitLab username of the person reporting the deprecation stage: Manage # (required) String value of the stage that the feature was created in. e.g., Growth issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/369117 # (required) Link to the deprecation issue in GitLab body: | # (required) Do not modify this line, instead modify the lines below. The `omniauth_crowd` gem that provides GitLab with the Atlassian Crowd OmniAuth provider will be removed in our - next major release, GitLab 16.0. This gem sees very little use and its + next major release, GitLab 18.0. This gem sees very little use and its [lack of compatibility](https://github.com/robdimarco/omniauth_crowd/issues/37) with OmniAuth 2.0 is [blocking our upgrade](https://gitlab.com/gitlab-org/gitlab/-/issues/30073). diff --git a/db/docs/agent_project_authorizations.yml b/db/docs/agent_project_authorizations.yml index 77c26571c28..144598b8109 100644 --- a/db/docs/agent_project_authorizations.yml +++ b/db/docs/agent_project_authorizations.yml @@ -4,7 +4,16 @@ classes: - Clusters::Agents::Authorizations::CiAccess::ProjectAuthorization feature_categories: - deployment_management -description: Configuration for a project that is authorized to use a particular cluster agent +description: Configuration for a project that is authorized to use a particular cluster + agent introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67295 milestone: '14.3' -gitlab_schema: gitlab_main +gitlab_schema: gitlab_main_cell +allow_cross_joins: +- gitlab_main_clusterwide +allow_cross_transactions: +- gitlab_main_clusterwide +allow_cross_foreign_keys: +- gitlab_main_clusterwide +sharding_key: + project_id: projects diff --git a/db/docs/compliance_management_frameworks.yml b/db/docs/compliance_management_frameworks.yml index 9a75e43a938..40697a5a28b 100644 --- a/db/docs/compliance_management_frameworks.yml +++ b/db/docs/compliance_management_frameworks.yml @@ -7,4 +7,12 @@ feature_categories: description: TODO introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44098 milestone: '13.5' -gitlab_schema: gitlab_main +gitlab_schema: gitlab_main_cell +allow_cross_joins: +- gitlab_main_clusterwide +allow_cross_transactions: +- gitlab_main_clusterwide +allow_cross_foreign_keys: +- gitlab_main_clusterwide +sharding_key: + namespace_id: namespaces diff --git a/db/docs/external_status_checks.yml b/db/docs/external_status_checks.yml index 5f7ea9b5314..c8263d921bf 100644 --- a/db/docs/external_status_checks.yml +++ b/db/docs/external_status_checks.yml @@ -7,4 +7,12 @@ feature_categories: description: Stores project's external status checks introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62186 milestone: '14.0' -gitlab_schema: gitlab_main +gitlab_schema: gitlab_main_cell +allow_cross_joins: +- gitlab_main_clusterwide +allow_cross_transactions: +- gitlab_main_clusterwide +allow_cross_foreign_keys: +- gitlab_main_clusterwide +sharding_key: + project_id: projects diff --git a/db/docs/project_compliance_framework_settings.yml b/db/docs/project_compliance_framework_settings.yml index ab68259e87e..3a9d82dfb27 100644 --- a/db/docs/project_compliance_framework_settings.yml +++ b/db/docs/project_compliance_framework_settings.yml @@ -7,4 +7,12 @@ feature_categories: description: TODO introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28182 milestone: '13.0' -gitlab_schema: gitlab_main +gitlab_schema: gitlab_main_cell +allow_cross_joins: +- gitlab_main_clusterwide +allow_cross_transactions: +- gitlab_main_clusterwide +allow_cross_foreign_keys: +- gitlab_main_clusterwide +sharding_key: + project_id: projects diff --git a/db/docs/project_compliance_standards_adherence.yml b/db/docs/project_compliance_standards_adherence.yml index 78fbf8a8a46..0eae8b3fbb6 100644 --- a/db/docs/project_compliance_standards_adherence.yml +++ b/db/docs/project_compliance_standards_adherence.yml @@ -1,10 +1,12 @@ --- table_name: project_compliance_standards_adherence classes: - - Projects::ComplianceStandards::Adherence +- Projects::ComplianceStandards::Adherence feature_categories: - compliance_management description: Stores the details about projects and their adherence to compliance standards introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122293 milestone: '16.1' gitlab_schema: gitlab_main_cell +sharding_key: + project_id: projects diff --git a/db/migrate/20240110085226_add_rate_limits_to_application_settings.rb b/db/migrate/20240110085226_add_rate_limits_to_application_settings.rb new file mode 100644 index 00000000000..2560977f979 --- /dev/null +++ b/db/migrate/20240110085226_add_rate_limits_to_application_settings.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddRateLimitsToApplicationSettings < Gitlab::Database::Migration[2.2] + milestone '16.9' + enable_lock_retries! + + def change + add_column :application_settings, :rate_limits, :jsonb, default: {}, null: false + end +end diff --git a/db/schema_migrations/20240110085226 b/db/schema_migrations/20240110085226 new file mode 100644 index 00000000000..35e3cc7237a --- /dev/null +++ b/db/schema_migrations/20240110085226 @@ -0,0 +1 @@ +9c9eb37365dae73fb68000d18675a280e063711eaed8f96f64724bff20326957
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index b7368d8b289..793fa64b53e 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -12633,6 +12633,7 @@ CREATE TABLE application_settings ( lock_toggle_security_policy_custom_ci boolean DEFAULT false NOT NULL, toggle_security_policies_policy_scope boolean DEFAULT false NOT NULL, lock_toggle_security_policies_policy_scope boolean DEFAULT false NOT NULL, + rate_limits jsonb DEFAULT '{}'::jsonb NOT NULL, CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)), CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)), diff --git a/doc/administration/settings/rate_limit_on_members_api.md b/doc/administration/settings/rate_limit_on_members_api.md new file mode 100644 index 00000000000..3e8868adc91 --- /dev/null +++ b/doc/administration/settings/rate_limit_on_members_api.md @@ -0,0 +1,33 @@ +--- +stage: Data Stores +group: Tenant Scale +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + +# Rate limit on Members API **(FREE SELF)** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140633) in GitLab 16.9. + +You can configure the rate limit per group (or project) per user to the +[delete members API](../../api/members.md#remove-a-member-from-a-group-or-project). + +To change the rate limit: + +1. On the left sidebar, at the bottom, select **Admin Area**. +1. Select **Settings > Network**. +1. Expand **Members API rate limit**. +1. In the **Maximum requests per minute per group / project** text box, enter the new value. +1. Select **Save changes**. + +The rate limit: + +- Applies per group or project per user. +- Can be set to 0 to disable rate limiting. + +The default value of the rate limit is `60`. + +Requests over the rate limit are logged into the `auth.log` file. + +For example, if you set a limit of 60, requests sent to the +[delete members API](../../api/members.md#remove-a-member-from-a-group-or-project) exceeding a rate of 300 per minute +are blocked. Access to the endpoint is allowed after one minute. diff --git a/doc/api/integrations.md b/doc/api/integrations.md index 9aec4e0b17d..3e2286fa0a5 100644 --- a/doc/api/integrations.md +++ b/doc/api/integrations.md @@ -462,6 +462,40 @@ Get the Datadog integration settings for a project. GET /projects/:id/integrations/datadog ``` +## Diffblue Cover + +### Set up Diffblue Cover + +Set up the Diffblue Cover integration for a project. + +```plaintext +PUT /projects/:id/integrations/diffblue-cover +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `diffblue_license_key` | string | true | Diffblue Cover license key. | +| `diffblue_access_token_name` | string | true | Access token name used by Diffblue Cover in pipelines. | +| `diffblue_access_token_secret` | string | true | Access token secret used by Diffblue Cover in pipelines. | + +### Disable Diffblue Cover + +Disable the Diffblue Cover integration for a project. Integration settings are reset. + +```plaintext +DELETE /projects/:id/integrations/diffblue-cover +``` + +### Get Diffblue Cover settings + +Get the Diffblue Cover integration settings for a project. + +```plaintext +GET /projects/:id/integrations/diffblue-cover +``` + ## Discord Notifications ### Set up Discord Notifications diff --git a/doc/development/git_object_deduplication.md b/doc/development/git_object_deduplication.md index f3e35f9cdf8..5bfa77e2aa3 100644 --- a/doc/development/git_object_deduplication.md +++ b/doc/development/git_object_deduplication.md @@ -42,7 +42,7 @@ repositories that depend on the object pool. The danger lies in `git prune`, and `git gc` calls `git prune`. The problem is that `git prune`, when running in a pool repository, cannot -reliable decide if an object is no longer needed. +reliably decide if an object is no longer needed. ### Git alternates in GitLab: pool repositories @@ -51,7 +51,7 @@ which are hidden from the user. We then use Git alternates to let a collection of project repositories borrow from a single pool repository. We call such a collection of project repositories a pool. Pools form star-shaped networks of repositories -that borrow from a single pool, which resemble (but not be +that borrow from a single pool, which resemble (but are not identical to) the fork networks that get formed when users fork projects. diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md index 02fd4b3454e..a7eb061fa47 100644 --- a/doc/update/deprecations.md +++ b/doc/update/deprecations.md @@ -52,6 +52,23 @@ For deprecation reviewers (Technical Writers only): <div class="deprecation breaking-change" data-milestone="18.0"> +### Atlassian Crowd OmniAuth provider + +<div class="deprecation-notes"> +- Announced in GitLab <span class="milestone">15.3</span> +- Removal in GitLab <span class="milestone">18.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change)) +- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/369117). +</div> + +The `omniauth_crowd` gem that provides GitLab with the Atlassian Crowd OmniAuth provider will be removed in our +next major release, GitLab 18.0. This gem sees very little use and its +[lack of compatibility](https://github.com/robdimarco/omniauth_crowd/issues/37) with OmniAuth 2.0 is +[blocking our upgrade](https://gitlab.com/gitlab-org/gitlab/-/issues/30073). + +</div> + +<div class="deprecation breaking-change" data-milestone="18.0"> + ### GitLab Runner registration token in Runner Operator <div class="deprecation-notes"> @@ -194,23 +211,6 @@ From GitLab 18.0 and later, the methods to register runners introduced by the ne <div class="deprecation breaking-change" data-milestone="17.0"> -### Atlassian Crowd OmniAuth provider - -<div class="deprecation-notes"> -- Announced in GitLab <span class="milestone">15.3</span> -- Removal in GitLab <span class="milestone">17.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change)) -- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/369117). -</div> - -The `omniauth_crowd` gem that provides GitLab with the Atlassian Crowd OmniAuth provider will be removed in our -next major release, GitLab 16.0. This gem sees very little use and its -[lack of compatibility](https://github.com/robdimarco/omniauth_crowd/issues/37) with OmniAuth 2.0 is -[blocking our upgrade](https://gitlab.com/gitlab-org/gitlab/-/issues/30073). - -</div> - -<div class="deprecation breaking-change" data-milestone="17.0"> - ### Auto DevOps support for Herokuish is deprecated <div class="deprecation-notes"> diff --git a/doc/user/group/insights/img/insights_example_stacked_bar_chart_v15_4.png b/doc/user/group/insights/img/insights_example_stacked_bar_chart_v15_4.png Binary files differdeleted file mode 100644 index f7963c170e1..00000000000 --- a/doc/user/group/insights/img/insights_example_stacked_bar_chart_v15_4.png +++ /dev/null diff --git a/doc/user/group/insights/index.md b/doc/user/group/insights/index.md index ad93e783b88..0cb1ad093a5 100644 --- a/doc/user/group/insights/index.md +++ b/doc/user/group/insights/index.md @@ -1,94 +1,11 @@ --- -stage: Plan -group: Optimize -info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +redirect_to: '../../project/insights/index.md' +remove_date: '2024-04-20' --- -# Insights for groups **(ULTIMATE ALL)** +This document was moved to [another location](../../project/insights/index.md). -> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/725) in GitLab 12.0. - -Configure insights to explore data about you group's activity, such as -triage hygiene, issues created or closed in a given period, and average time for merge -requests to be merged. -You can also create custom insights reports that are relevant for your group. - -## View group insights - -Prerequisites: - -- You must have [permission](../../permissions.md#group-members-permissions) to view the group. -- You must have access to a project to view information about its merge requests and issues, - and permission to view them if they are confidential. - -To access your group's insights: - -1. On the left sidebar, select **Search or go to** and find your group. -1. Select **Analyze > Insights**. - -## Interact with insights charts - -You can interact with the insights charts to view details about your group's activity. - -![Insights example stacked bar chart](img/insights_example_stacked_bar_chart_v15_4.png) - -### Display different reports - -To display one of the available reports on the insights page, from the **Select report** dropdown list, -select the report you want to display. - -### View bar chart annotations - -To view annotations, hover over each bar in the chart. - -### Zoom in on chart - -Insights display data from the last 90 days. You can zoom in to display data only from a subset of the 90-day range. - -To do this, select the pause icons (**{status-paused}**) and slide them along the horizontal axis: - -- To change the start date, slide the left pause icon to the left or right. -- To change the end date, slide the right pause icon to the left or right. - -### Exclude dimensions from charts - -By default, insights display all available dimensions on the chart. - -To exclude a dimension, from the legend below the chart, select the name of the dimension. - -### Drill down on charts - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/372215/) in GitLab 16.7. - -You can drill down into the data of the **Bugs created per month by priority** and **Bugs created per month by severity** charts from the [default configuration file](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/fixtures/insights/default.yml). - -To view a drill-down report of the data for a specific priority or severity in a month: - -- On the chart, select the bar stack you want to drill down on. - -## Configure group insights - -GitLab reads insights from the -[default configuration file](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/fixtures/insights/default.yml). - -To configure group insights: - -1. Create a new file [`.gitlab/insights.yml`](../../project/insights/index.md#configure-project-insights) - in a project that belongs to your group. -1. On the left sidebar, select **Search or go to** and find your group. -1. Select **Settings > General**. -1. Expand **Analytics** and find the **Insights** section. -1. Select the project that contains your `.gitlab/insights.yml` configuration file. -1. Select **Save changes**. - -<!-- ## Troubleshooting - -Include any troubleshooting steps that you can foresee. If you know beforehand what issues -one might have when setting this up, or when something is changed, or on upgrading, it's -important to describe those, too. Think of things that may go wrong and include them here. -This is important to minimize requests for support, and to avoid doc comments with -questions that you know someone might ask. - -Each scenario can be a third-level heading, for example `### Getting error message X`. -If you have none to add when creating a doc, leave this section in place -but commented out to help encourage others to add to it in the future. --> +<!-- This redirect file can be deleted after <YYYY-MM-DD>. --> +<!-- Redirects that point to other docs in the same project expire in three months. --> +<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. --> +<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html --> diff --git a/doc/user/project/insights/index.md b/doc/user/project/insights/index.md index 8a91a0c4621..b7addb5131f 100644 --- a/doc/user/project/insights/index.md +++ b/doc/user/project/insights/index.md @@ -4,27 +4,28 @@ group: Optimize info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Insights for projects **(ULTIMATE ALL)** +# Insights **(ULTIMATE ALL)** -Configure project insights to explore data such as: +> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/725) in GitLab 12.0. + +Configure insights for your projects and groups to explore data such as: - Issues created and closed during a specified period. - Average time for merge requests to be merged. - Triage hygiene. -Insights are also available for [groups](../../group/insights/index.md). +You can also create custom Insights reports that are relevant for your group. ## View project insights Prerequisites: -- You must have: - - Access to a project to view information about its merge requests and issues. - - Permission to view confidential merge requests and issues in the project. +- For project insights, you must have access to the project and permission to view information about its merge requests and issues. +- For group insights, you must have permission to view the group. -To view project insights: +To view insights for a project or group: -1. On the left sidebar, select **Search or go to** and find your project. +1. On the left sidebar, select **Search or go to** and find your project or group. 1. Select **Analyze > Insights**. 1. To view a report, select the **Select report** dropdown list. @@ -35,23 +36,61 @@ You can direct users to a specific report in Insights by using the deep-linked U To create a deep link, append the report key to the end of the Insights report URL. For example, a GitLab report with the key `bugsCharts` has the deep link URL `https://gitlab.com/gitlab-org/gitlab/insights/#/bugsCharts`. +## Interact with Insights charts + +You can interact with the insights charts to view details about your group's activity. + +### Display different reports + +To display one of the available reports on the insights page, from the **Select report** dropdown list, +select the report you want to display. + +### View bar chart annotations + +To view annotations, hover over each bar in the chart. + +### Zoom in on chart + +Insights display data from the last 90 days. You can zoom in to display data only from a subset of the 90-day range. + +To do this, select the pause icons (**{status-paused}**) and slide them along the horizontal axis: + +- To change the start date, slide the left pause icon to the left or right. +- To change the end date, slide the right pause icon to the left or right. + +### Exclude dimensions from charts + +By default, insights display all available dimensions on the chart. + +To exclude a dimension, from the legend below the chart, select the name of the dimension. + +### Drill down on charts + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/372215/) in GitLab 16.7. + +You can drill down into the data of the **Bugs created per month by priority** and **Bugs created per month by severity** charts from the [default configuration file](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/fixtures/insights/default.yml). + +To view a drill-down report of the data for a specific priority or severity in a month: + +- On the chart, select the bar stack you want to drill down on. + ## Configure project insights Prerequisites: - Depending on your project configuration, you must have at least the Developer role. -Project insights are configured with the [`.gitlab/insights.yml`](#insights-configuration-file) file in the project. If a project doesn't have a configuration file, it uses the [group configuration](../../group/insights/index.md#configure-group-insights). +Project insights are configured with the [`.gitlab/insights.yml`](#insights-configuration-file) file in the project. If a project doesn't have a configuration file, it uses the [group configuration](#configure-group-insights). The `.gitlab/insights.yml` file is a YAML file where you define: - The structure and order of charts in a report. - The style of charts displayed in the report of your project or group. -To configure project insights, either: +To configure project insights, create a file `.gitlab/insights.yml` either: -- Create a `.gitlab/insights.yml` file locally in the root directory of your project, and push your changes. -- Create a `.gitlab/insights.yml` file in the UI: +- Locally, in the root directory of your project, and push your changes. +- From the UI: 1. On the left sidebar, select **Search or go to** and find your project. 1. Above the file list, select the branch you want to commit to, select the plus icon, then select **New file**. 1. In the **File name** text box, enter `.gitlab/insights.yml`. @@ -59,7 +98,21 @@ To configure project insights, either: 1. Select **Commit changes**. After you create the configuration file, you can also -[use it for the project's group](../../group/insights/index.md#configure-group-insights). +use it for the project's group. + +## Configure group insights + +GitLab reads insights from the +[default configuration file](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/fixtures/insights/default.yml). + +To configure group insights: + +1. In a project that belongs to your group, [create a `.gitlab/insights.yml` file](#configure-project-insights). +1. On the left sidebar, select **Search or go to** and find your group. +1. Select **Settings > General**. +1. Expand **Analytics** and find the **Insights** section. +1. Select the project that contains your `.gitlab/insights.yml` configuration file. +1. Select **Save changes**. ## Insights configuration file @@ -403,7 +456,7 @@ Use `query.environment_tiers` to define an array of environments to include the Use `projects` to limit where issuables are queried from: -- If `.gitlab/insights.yml` is used for a [group's insights](../../group/insights/index.md#configure-group-insights), use `projects` to define the projects from which to query issuables. By default, all projects under the group are used. +- If `.gitlab/insights.yml` is used for a group's insights, use `projects` to define the projects from which to query issuables. By default, all projects under the group are used. - If `.gitlab/insights.yml` is used for a project's insights, specifying other projects does not yield results. By default, the project is used. #### `projects.only` diff --git a/lib/api/members.rb b/lib/api/members.rb index 56a15c41e1c..908733d4aa1 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -176,7 +176,7 @@ module API source = find_source(source_type, params[:id]) member = source_members(source).find_by!(user_id: params[:user_id]) - check_rate_limit!(:member_delete, scope: [source, current_user]) + check_rate_limit!(:members_delete, scope: [source, current_user]) destroy_conditionally!(member) do ::Members::DestroyService.new(current_user).execute(member, skip_subresources: params[:skip_subresources], unassign_issuables: params[:unassign_issuables]) diff --git a/lib/gitlab/application_rate_limiter.rb b/lib/gitlab/application_rate_limiter.rb index df7f2986304..5a2881e6c96 100644 --- a/lib/gitlab/application_rate_limiter.rb +++ b/lib/gitlab/application_rate_limiter.rb @@ -30,7 +30,7 @@ module Gitlab group_download_export: { threshold: -> { application_settings.group_download_export_limit }, interval: 1.minute }, group_import: { threshold: -> { application_settings.group_import_limit }, interval: 1.minute }, group_testing_hook: { threshold: 5, interval: 1.minute }, - member_delete: { threshold: 60, interval: 1.minute }, + members_delete: { threshold: -> { application_settings.members_delete_limit }, interval: 1.minute }, profile_add_new_email: { threshold: 5, interval: 1.minute }, web_hook_calls: { interval: 1.minute }, web_hook_calls_mid: { interval: 1.minute }, diff --git a/locale/gitlab.pot b/locale/gitlab.pot index fab424dc159..b41845620a1 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -16658,6 +16658,9 @@ msgstr "" msgid "Dependencies|Export as JSON" msgstr "" +msgid "Dependencies|Filtering unavailable" +msgstr "" + msgid "Dependencies|Job failed to generate the dependency list" msgstr "" @@ -16682,6 +16685,9 @@ msgstr "" msgid "Dependencies|Projects" msgstr "" +msgid "Dependencies|Search or filter dependencies..." +msgstr "" + msgid "Dependencies|Software Bill of Materials (SBOM) based on the %{linkStart}latest successful%{linkEnd} scan" msgstr "" @@ -16706,6 +16712,9 @@ msgstr "" msgid "Dependencies|There was an error fetching the projects for this group. Please try again later." msgstr "" +msgid "Dependencies|This group exceeds the maximum number of 600 sub-groups. We cannot accurately filter or search the dependency list above this maximum. To view or filter a subset of this information, go to a subgroup's dependency list." +msgstr "" + msgid "Dependencies|This group exceeds the maximum number of sub-groups of 600. We cannot accurately display a project list at this time. Please access a sub-group dependency list to view this information or see the %{linkStart}dependency list help %{linkEnd} page to learn more." msgstr "" @@ -17733,22 +17742,22 @@ msgstr "" msgid "DiffblueCover|Access token" msgstr "" -msgid "DiffblueCover|Access token name used by Diffblue Cover in pipelines" +msgid "DiffblueCover|Access token name used by Diffblue Cover in pipelines." msgstr "" -msgid "DiffblueCover|Access token secret used by Diffblue Cover in pipelines" +msgid "DiffblueCover|Access token secret used by Diffblue Cover in pipelines." msgstr "" msgid "DiffblueCover|Automatically write comprehensive, human-like Java unit tests." msgstr "" -msgid "DiffblueCover|Diffblue Cover is a reinforcement learning AI platform that automatically writes comprehensive, human-like Java unit tests. Integrate the power of Diffblue Cover into your CI/CD workflow for fully autonomous operation." +msgid "DiffblueCover|Diffblue Cover is a generative AI platform that automatically writes comprehensive, human-like Java unit tests. Integrate Diffblue Cover into your CI/CD workflow for fully autonomous operation." msgstr "" -msgid "DiffblueCover|Diffblue Cover license key" +msgid "DiffblueCover|Diffblue Cover license key." msgstr "" -msgid "DiffblueCover|Enter your Diffblue Cover license key or visit %{diffblue_link} to obtain a free trial license." +msgid "DiffblueCover|Enter your Diffblue Cover license key or go to %{diffblue_link} to obtain a free trial license." msgstr "" msgid "DiffblueCover|Integration details" @@ -29013,6 +29022,9 @@ msgstr "" msgid "Limit the number of pipeline creation requests per minute. This limit includes pipelines created through the UI, the API, and by background processing." msgstr "" +msgid "Limit the number of project or group members a user can delete per minute through API requests." +msgstr "" + msgid "Limit the size of Sidekiq jobs stored in Redis." msgstr "" @@ -29882,6 +29894,9 @@ msgstr "" msgid "Maximum requests per minute" msgstr "" +msgid "Maximum requests per minute per group / project" +msgstr "" + msgid "Maximum running slices" msgstr "" @@ -30071,6 +30086,9 @@ msgstr "" msgid "Members" msgstr "" +msgid "Members API rate limit" +msgstr "" + msgid "Members can be added by project %{i_open}Maintainers%{i_close} or %{i_open}Owners%{i_close}" msgstr "" @@ -43389,9 +43407,6 @@ msgstr "" msgid "Search or filter commits" msgstr "" -msgid "Search or filter dependencies..." -msgstr "" - msgid "Search or filter results…" msgstr "" diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb index c2ab5edf79c..553c2f0266e 100644 --- a/spec/features/groups/show_spec.rb +++ b/spec/features/groups/show_spec.rb @@ -81,6 +81,7 @@ RSpec.describe 'Group show page', feature_category: :groups_and_projects do expect(find('.group-row:nth-child(1) .namespace-title > a')).to have_content(project2.title) expect(find('.group-row:nth-child(2) .namespace-title > a')).to have_content(project1.title) expect(find('.group-row:nth-child(3) .namespace-title > a')).to have_content(project3.title) + expect(page).to have_selector('button[data-testid="base-dropdown-toggle"]', text: 'Stars') end end end diff --git a/spec/features/projects/user_sorts_projects_spec.rb b/spec/features/projects/user_sorts_projects_spec.rb index b80caca5810..3576225a417 100644 --- a/spec/features/projects/user_sorts_projects_spec.rb +++ b/spec/features/projects/user_sorts_projects_spec.rb @@ -10,6 +10,10 @@ RSpec.describe 'User sorts projects and order persists', feature_category: :grou let_it_be(:group_member) { create(:group_member, :maintainer, user: user, group: group) } let_it_be(:project) { create(:project, :public, group: group) } + def find_dropdown_toggle + find('button[data-testid=base-dropdown-toggle]') + end + shared_examples_for "sort order persists across all views" do |project_paths_label, group_paths_label| it "is set on the dashboard_projects_path" do visit(dashboard_projects_path) @@ -27,7 +31,7 @@ RSpec.describe 'User sorts projects and order persists', feature_category: :grou visit(group_canonical_path(group)) within '[data-testid=group_sort_by_dropdown]' do - expect(find('.gl-dropdown-toggle')).to have_content(group_paths_label) + expect(find_dropdown_toggle).to have_content(group_paths_label) end end @@ -35,7 +39,7 @@ RSpec.describe 'User sorts projects and order persists', feature_category: :grou visit(details_group_path(group)) within '[data-testid=group_sort_by_dropdown]' do - expect(find('.gl-dropdown-toggle')).to have_content(group_paths_label) + expect(find_dropdown_toggle).to have_content(group_paths_label) end end end @@ -67,8 +71,8 @@ RSpec.describe 'User sorts projects and order persists', feature_category: :grou sign_in(user) visit(group_canonical_path(group)) within '[data-testid=group_sort_by_dropdown]' do - find('button.gl-dropdown-toggle').click - first(:button, 'Created').click + find_dropdown_toggle.click + find('li', text: 'Created').click wait_for_requests end end @@ -81,8 +85,8 @@ RSpec.describe 'User sorts projects and order persists', feature_category: :grou sign_in(user) visit(details_group_path(group)) within '[data-testid=group_sort_by_dropdown]' do - find('button.gl-dropdown-toggle').click - first(:button, 'Updated').click + find_dropdown_toggle.click + find('li', text: 'Updated').click wait_for_requests end end diff --git a/spec/frontend/fixtures/static/oauth_remember_me.html b/spec/frontend/fixtures/static/oauth_remember_me.html index 60277ecf66e..d7519dd695f 100644 --- a/spec/frontend/fixtures/static/oauth_remember_me.html +++ b/spec/frontend/fixtures/static/oauth_remember_me.html @@ -1,20 +1,20 @@ -<div id="oauth-container"> +<div class="js-oauth-login"> <input id="remember_me_omniauth" type="checkbox" /> <form method="post" action="http://example.com/"> - <button class="js-oauth-login twitter" type="submit"> + <button class="twitter" type="submit"> <span>Twitter</span> </button> </form> <form method="post" action="http://example.com/"> - <button class="js-oauth-login github" type="submit"> + <button class="github" type="submit"> <span>GitHub</span> </button> </form> <form method="post" action="http://example.com/?redirect_fragment=L1"> - <button class="js-oauth-login facebook" type="submit"> + <button class="facebook" type="submit"> <span>Facebook</span> </button> </form> diff --git a/spec/frontend/groups/components/overview_tabs_spec.js b/spec/frontend/groups/components/overview_tabs_spec.js index 0ca59e9c6bf..8b80330c910 100644 --- a/spec/frontend/groups/components/overview_tabs_spec.js +++ b/spec/frontend/groups/components/overview_tabs_spec.js @@ -1,4 +1,4 @@ -import { GlSorting, GlSortingItem, GlTab } from '@gitlab/ui'; +import { GlSorting, GlTab } from '@gitlab/ui'; import Vue, { nextTick } from 'vue'; import AxiosMockAdapter from 'axios-mock-adapter'; import { mountExtended } from 'helpers/vue_test_utils_helper'; @@ -17,6 +17,7 @@ import { ACTIVE_TAB_SUBGROUPS_AND_PROJECTS, ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED, + OVERVIEW_TABS_SORTING_ITEMS, SORTING_ITEM_NAME, SORTING_ITEM_UPDATED, SORTING_ITEM_STARS, @@ -74,6 +75,7 @@ describe('OverviewTabs', () => { const findTab = (name) => wrapper.findByRole('tab', { name }); const findSelectedTab = () => wrapper.findByRole('tab', { selected: true }); const findSearchInput = () => wrapper.findByPlaceholderText(OverviewTabs.i18n.searchPlaceholder); + const findGlSorting = () => wrapper.findComponent(GlSorting); beforeEach(() => { axiosMock = new AxiosMockAdapter(axios); @@ -301,7 +303,7 @@ describe('OverviewTabs', () => { describe('when sort is changed', () => { beforeEach(async () => { await setup(); - wrapper.findAllComponents(GlSortingItem).at(2).vm.$emit('click'); + findGlSorting().vm.$emit('sortByChange', SORTING_ITEM_UPDATED.label); await nextTick(); }); @@ -403,12 +405,15 @@ describe('OverviewTabs', () => { }); it('sets sort dropdown', () => { - expect(wrapper.findComponent(GlSorting).props()).toMatchObject({ + const expectedSortOptions = OVERVIEW_TABS_SORTING_ITEMS.map(({ label }) => { + return { value: label, text: label }; + }); + expect(findGlSorting().props()).toMatchObject({ text: SORTING_ITEM_UPDATED.label, isAscending: false, + sortBy: SORTING_ITEM_UPDATED.label, + sortOptions: expectedSortOptions, }); - - expect(wrapper.findAllComponents(GlSortingItem).at(2).vm.$attrs.active).toBe(true); }); }); }); diff --git a/spec/frontend/oauth_remember_me_spec.js b/spec/frontend/oauth_remember_me_spec.js index 33295d46fea..4fea216302f 100644 --- a/spec/frontend/oauth_remember_me_spec.js +++ b/spec/frontend/oauth_remember_me_spec.js @@ -5,13 +5,13 @@ import OAuthRememberMe from '~/pages/sessions/new/oauth_remember_me'; describe('OAuthRememberMe', () => { const findFormAction = (selector) => { - return $(`#oauth-container .js-oauth-login${selector}`).parent('form').attr('action'); + return $(`.js-oauth-login ${selector}`).parent('form').attr('action'); }; beforeEach(() => { setHTMLFixture(htmlOauthRememberMe); - new OAuthRememberMe({ container: $('#oauth-container') }).bindEvents(); + new OAuthRememberMe({ container: $('.js-oauth-login') }).bindEvents(); }); afterEach(() => { @@ -19,7 +19,7 @@ describe('OAuthRememberMe', () => { }); it('adds and removes the "remember_me" query parameter from all OAuth login buttons', () => { - $('#oauth-container #remember_me_omniauth').click(); + $('.js-oauth-login #remember_me_omniauth').click(); expect(findFormAction('.twitter')).toBe('http://example.com/?remember_me=1'); expect(findFormAction('.github')).toBe('http://example.com/?remember_me=1'); @@ -27,7 +27,7 @@ describe('OAuthRememberMe', () => { 'http://example.com/?redirect_fragment=L1&remember_me=1', ); - $('#oauth-container #remember_me_omniauth').click(); + $('.js-oauth-login #remember_me_omniauth').click(); expect(findFormAction('.twitter')).toBe('http://example.com/'); expect(findFormAction('.github')).toBe('http://example.com/'); diff --git a/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js b/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js index 6ff2bb42d8d..7607381a981 100644 --- a/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js +++ b/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js @@ -5,7 +5,7 @@ import preserveUrlFragment from '~/pages/sessions/new/preserve_url_fragment'; describe('preserve_url_fragment', () => { const findFormAction = (selector) => { - return $(`.omniauth-container ${selector}`).parent('form').attr('action'); + return $(`.js-oauth-login ${selector}`).parent('form').attr('action'); }; beforeEach(() => { @@ -44,9 +44,7 @@ describe('preserve_url_fragment', () => { }); it('when "remember-me" is present', () => { - $('.js-oauth-login') - .parent('form') - .attr('action', (i, href) => `${href}?remember_me=1`); + $('.js-oauth-login form').attr('action', (i, href) => `${href}?remember_me=1`); preserveUrlFragment('#L65'); diff --git a/spec/helpers/application_settings_helper_spec.rb b/spec/helpers/application_settings_helper_spec.rb index 5dc75a60a6e..b378437c407 100644 --- a/spec/helpers/application_settings_helper_spec.rb +++ b/spec/helpers/application_settings_helper_spec.rb @@ -65,6 +65,7 @@ RSpec.describe ApplicationSettingsHelper do project_download_export_limit project_export_limit project_import_limit raw_blob_request_limit group_export_limit group_download_export_limit group_import_limit users_get_by_id_limit search_rate_limit search_rate_limit_unauthenticated + members_delete_limit ]) end diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index 22d1113ee8c..831f41cde0a 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -461,7 +461,7 @@ RSpec.describe MarkupHelper, feature_category: :team_planning do it 'displays the first line of a code block' do object = create_object("```\nCode block\nwith two lines\n```") - expected = %r{<pre.+><code><span class="line">Code block\.\.\.</span></code></pre>} + expected = %r{<pre.+><code><span class="line" lang="plaintext">Code block\.\.\.</span></code></pre>} expect(helper.first_line_in_markdown(object, attribute, 100, is_todo: true, project: project)).to match(expected) end @@ -476,8 +476,8 @@ RSpec.describe MarkupHelper, feature_category: :team_planning do it 'preserves code color scheme' do object = create_object("```ruby\ndef test\n 'hello world'\nend\n```") - expected = "\n<pre class=\"code highlight js-syntax-highlight language-ruby\">" \ - "<code><span class=\"line\"><span class=\"k\">def</span> <span class=\"nf\">test</span>...</span>" \ + expected = "\n<pre class=\"code highlight js-syntax-highlight language-ruby\" lang=\"ruby\">" \ + "<code><span class=\"line\" lang=\"ruby\"><span class=\"k\">def</span> <span class=\"nf\">test</span>...</span>" \ "</code></pre>\n" expect(helper.first_line_in_markdown(object, attribute, 150, is_todo: true, project: project)).to eq(expected) diff --git a/spec/helpers/time_zone_helper_spec.rb b/spec/helpers/time_zone_helper_spec.rb index e8d96ee0700..95acb2ee9d9 100644 --- a/spec/helpers/time_zone_helper_spec.rb +++ b/spec/helpers/time_zone_helper_spec.rb @@ -93,6 +93,29 @@ RSpec.describe TimeZoneHelper, :aggregate_failures do end end + describe '#timezone_data_with_unique_identifiers' do + subject { helper.timezone_data_with_unique_identifiers } + + before do + allow(helper).to receive(:timezone_data).and_return([ + { identifier: 'Europe/London', name: 'London' }, + { identifier: 'Europe/London', name: 'Edinburgh' }, + { identifier: 'Europe/Berlin', name: 'Berlin' }, + { identifier: 'Europe/London', name: 'Hogwarts' } + + ]) + end + + let(:expected) do + [ + { identifier: 'Europe/London', name: 'Edinburgh, Hogwarts, London' }, + { identifier: 'Europe/Berlin', name: 'Berlin' } + ] + end + + it { is_expected.to eq(expected) } + end + describe '#local_time' do let_it_be(:timezone) { 'America/Los_Angeles' } diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 9b2c14314df..b4003469ebb 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -28,6 +28,7 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do it { expect(setting.decompress_archive_file_timeout).to eq(210) } it { expect(setting.bulk_import_concurrent_pipeline_batch_limit).to eq(25) } it { expect(setting.allow_project_creation_for_guest_and_below).to eq(true) } + it { expect(setting.members_delete_limit).to eq(60) } end describe 'validations' do @@ -58,6 +59,8 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do } end + it { expect(described_class).to validate_jsonb_schema(['application_setting_rate_limits']) } + it { is_expected.to allow_value(nil).for(:home_page_url) } it { is_expected.to allow_value(http).for(:home_page_url) } it { is_expected.to allow_value(https).for(:home_page_url) } @@ -225,6 +228,8 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do max_import_size max_pages_custom_domains_per_project max_terraform_state_size_bytes + members_delete_limit + notes_create_limit package_registry_cleanup_policies_worker_capacity packages_cleanup_package_file_worker_capacity pipeline_limit_per_project_user_sha @@ -237,7 +242,6 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do sidekiq_job_limiter_limit_bytes terminal_max_session_time users_get_by_id_limit - notes_create_limit ] end diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index feb24a4e73f..7fc58140fb6 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -717,7 +717,7 @@ RSpec.describe API::Members, feature_category: :groups_and_projects do end.to change { source.members.count }.by(-1) end - it_behaves_like 'rate limited endpoint', rate_limit_key: :member_delete do + it_behaves_like 'rate limited endpoint', rate_limit_key: :members_delete do let(:current_user) { maintainer } let(:another_member) { create(:user) } diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb index 91f0218732e..83849df73dc 100644 --- a/spec/support/helpers/login_helpers.rb +++ b/spec/support/helpers/login_helpers.rb @@ -110,7 +110,7 @@ module LoginHelpers def login_via(provider, user, uid, remember_me: false, additional_info: {}) mock_auth_hash(provider, uid, user.email, additional_info: additional_info) visit new_user_session_path - expect(page).to have_css('.omniauth-container') + expect(page).to have_css('.js-oauth-login') check 'remember_me_omniauth' if remember_me diff --git a/spec/views/admin/application_settings/network.html.haml_spec.rb b/spec/views/admin/application_settings/network.html.haml_spec.rb index 989977bac3e..193ee8a32d5 100644 --- a/spec/views/admin/application_settings/network.html.haml_spec.rb +++ b/spec/views/admin/application_settings/network.html.haml_spec.rb @@ -18,4 +18,12 @@ RSpec.describe 'admin/application_settings/network.html.haml', feature_category: expect(rendered).to have_field('application_setting_projects_api_rate_limit_unauthenticated') end end + + context 'for Members API rate limit' do + it 'renders the `members_delete_limit` field' do + render + + expect(rendered).to have_field('application_setting_members_delete_limit') + end + end end diff --git a/spec/views/admin/sessions/new.html.haml_spec.rb b/spec/views/admin/sessions/new.html.haml_spec.rb index 81275fa8750..73d6298c27e 100644 --- a/spec/views/admin/sessions/new.html.haml_spec.rb +++ b/spec/views/admin/sessions/new.html.haml_spec.rb @@ -45,7 +45,7 @@ RSpec.describe 'admin/sessions/new.html.haml' do expect(rendered).not_to have_content _('No authentication methods configured.') expect(rendered).to have_css('.omniauth-divider') expect(rendered).to have_content(_('or sign in with')) - expect(rendered).to have_css('.omniauth-container') + expect(rendered).to have_css('.js-oauth-login') end end |