diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-10-13 21:11:49 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-10-13 21:11:49 +0300 |
commit | 718637f88ce9933f581c58e27dfffc389cbb6111 (patch) | |
tree | ab46e3b90bff9842387639ff849bfeee9c5e63a3 | |
parent | b0b94ea6e54f824d02840912f7086e9f47350571 (diff) |
Add latest changes from gitlab-org/gitlab@master
72 files changed, 1025 insertions, 492 deletions
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index bbe631b2a4a..9490924470a 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -85,141 +85,159 @@ rspec migration pg14: extends: - .rspec-base-pg14 - .rspec-base-migration + - .rspec-migration-parallel-pg14 - .rails:rules:ee-and-foss-migration - - .rspec-migration-parallel rspec background_migration pg14: extends: - .rspec-base-pg14 - .rspec-base-migration - - .rails:rules:ee-and-foss-background-migration - .rspec-background-migration-parallel + - .rails:rules:ee-and-foss-background-migration rspec migration pg14 single-db: extends: - rspec migration pg14 - .single-db-rspec + - .rspec-migration-parallel - .rails:rules:single-db rspec background_migration pg14 single-db: extends: - rspec background_migration pg14 - .single-db-rspec + - .rspec-background-migration-parallel - .rails:rules:single-db rspec migration pg14 single-db-ci-connection: extends: - rspec migration pg14 - .single-db-ci-connection-rspec + - .rspec-migration-parallel - .rails:rules:single-db-ci-connection rspec background_migration pg14 single-db-ci-connection: extends: - rspec background_migration pg14 - .single-db-ci-connection-rspec + - .rspec-background-migration-parallel - .rails:rules:single-db-ci-connection rspec migration pg14 praefect: extends: - rspec migration pg14 - .praefect-with-db + - .rspec-migration-parallel - .rails:rules:praefect-with-db rspec background_migration pg14 praefect: extends: - rspec background_migration pg14 - .praefect-with-db + - .rspec-background-migration-parallel - .rails:rules:praefect-with-db rspec migration pg14 clusterwide-db: extends: - rspec migration pg14 - .clusterwide-db + - .rspec-migration-parallel - .rails:rules:clusterwide-db rspec background_migration pg14 clusterwide-db: extends: - rspec background_migration pg14 - .clusterwide-db + - .rspec-background-migration-parallel - .rails:rules:clusterwide-db rspec unit pg14: extends: - .rspec-base-pg14 - .rails:rules:ee-and-foss-unit - - .rspec-unit-parallel + - .rspec-unit-pg14-parallel rspec unit pg14 single-redis: extends: - rspec unit pg14 - .no-redis-cluster + - .rspec-unit-parallel - .rails:rules:single-redis rspec unit pg14 single-db: extends: - rspec unit pg14 - .single-db-rspec + - .rspec-unit-parallel - .rails:rules:single-db rspec unit pg14 single-db-ci-connection: extends: - rspec unit pg14 - .single-db-ci-connection-rspec + - .rspec-unit-parallel - .rails:rules:single-db-ci-connection rspec unit pg14 clusterwide-db: extends: - rspec unit pg14 - .clusterwide-db + - .rspec-unit-parallel - .rails:rules:clusterwide-db rspec unit pg14 praefect: extends: - rspec unit pg14 - .praefect-with-db + - .rspec-unit-parallel - .rails:rules:praefect-with-db rspec integration pg14: extends: - .rspec-base-pg14 + - .rspec-integration-parallel-pg14 - .rails:rules:ee-and-foss-integration - - .rspec-integration-parallel rspec integration pg14 single-redis: extends: - rspec integration pg14 - .no-redis-cluster + - .rspec-integration-parallel - .rails:rules:single-redis rspec integration pg14 single-db: extends: - rspec integration pg14 - .single-db-rspec + - .rspec-integration-parallel - .rails:rules:single-db rspec integration pg14 single-db-ci-connection: extends: - rspec integration pg14 - .single-db-ci-connection-rspec + - .rspec-integration-parallel - .rails:rules:single-db-ci-connection rspec integration pg14 clusterwide-db: extends: - rspec integration pg14 - .clusterwide-db + - .rspec-integration-parallel - .rails:rules:clusterwide-db rspec integration pg14 praefect: extends: - rspec integration pg14 - .praefect-with-db + - .rspec-integration-parallel - .rails:rules:praefect-with-db rspec system pg14: extends: - .rspec-base-pg14 - .rails:rules:ee-and-foss-system - - .rspec-system-parallel + - .rspec-system-parallel-pg14 variables: DEBUG_GITLAB_TRANSACTION_STACK: "true" @@ -227,30 +245,35 @@ rspec system pg14 single-redis: extends: - rspec system pg14 - .no-redis-cluster + - .rspec-system-parallel - .rails:rules:single-redis rspec system pg14 single-db: extends: - rspec system pg14 - .single-db-rspec + - .rspec-system-parallel - .rails:rules:single-db rspec system pg14 single-db-ci-connection: extends: - rspec system pg14 - .single-db-ci-connection-rspec + - .rspec-system-parallel - .rails:rules:single-db-ci-connection rspec system pg14 clusterwide-db: extends: - rspec system pg14 - .clusterwide-db + - .rspec-system-parallel - .rails:rules:clusterwide-db rspec system pg14 praefect: extends: - rspec system pg14 - .praefect-with-db + - .rspec-system-parallel - .rails:rules:praefect-with-db # Dedicated job to test DB library code against PG12. @@ -335,7 +358,7 @@ rspec:artifact-collector unit: - .artifact-collector - .rails:rules:ee-and-foss-unit needs: - - rspec unit pg14 # 28 jobs + - rspec unit pg14 # 24 jobs - job: rspec unit clickhouse # 1 job optional: true @@ -344,15 +367,15 @@ rspec:artifact-collector system: - .artifact-collector - .rails:rules:ee-and-foss-system needs: - - rspec system pg14 # 28 jobs + - rspec system pg14 # 26 jobs rspec:artifact-collector remainder: extends: - .artifact-collector needs: - - job: rspec integration pg14 # 12 jobs + - job: rspec integration pg14 # 13 jobs optional: true - - job: rspec migration pg14 # 8 jobs + - job: rspec migration pg14 # 12 jobs optional: true - job: rspec background_migration pg14 # 4 jobs optional: true @@ -425,13 +448,13 @@ rspec:artifact-collector ee: optional: true - job: rspec-ee background_migration pg14 # 2 jobs optional: true - - job: rspec-ee unit pg14 # 18 jobs + - job: rspec-ee unit pg14 # 22 jobs optional: true - job: rspec-ee unit clickhouse # 1 job optional: true - - job: rspec-ee integration pg14 # 6 jobs + - job: rspec-ee integration pg14 # 5 jobs optional: true - - job: rspec-ee system pg14 # 10 jobs + - job: rspec-ee system pg14 # 12 jobs optional: true rules: - !reference ['.rails:rules:ee-only-migration', rules] @@ -620,20 +643,21 @@ rspec migration pg14-as-if-foss: extends: - .rspec-base-pg14-as-if-foss - .rspec-base-migration + - .rspec-migration-parallel-pg14-as-if-foss - .rails:rules:as-if-foss-migration - - .rspec-migration-parallel rspec background_migration pg14-as-if-foss: extends: - .rspec-base-pg14-as-if-foss - .rspec-base-migration - - .rails:rules:as-if-foss-background-migration - .rspec-background-migration-parallel + - .rails:rules:as-if-foss-background-migration rspec migration pg14-as-if-foss single-db: extends: - rspec migration pg14-as-if-foss - .single-db-rspec + - .rspec-migration-parallel - .rails:rules:single-db-as-if-foss rspec background_migration pg14-as-if-foss single-db: @@ -646,6 +670,7 @@ rspec migration pg14-as-if-foss single-db-ci-connection: extends: - rspec migration pg14-as-if-foss - .single-db-ci-connection-rspec + - .rspec-migration-parallel - .rails:rules:single-db-ci-connection-as-if-foss rspec background_migration pg14-as-if-foss single-db-ci-connection: @@ -658,6 +683,7 @@ rspec migration pg14-as-if-foss clusterwide-db: extends: - rspec migration pg14-as-if-foss - .clusterwide-db + - .rspec-migration-parallel - .rails:rules:clusterwide-db rspec background_migration pg14-as-if-foss clusterwide-db: @@ -792,19 +818,21 @@ rspec-ee migration pg14 praefect: extends: - rspec migration pg14 - .praefect-with-db + - .rspec-migration-parallel - .rails:rules:praefect-with-db rspec-ee background_migration pg14 praefect: extends: - rspec background_migration pg14 - .praefect-with-db + - .rspec-background-migration-parallel - .rails:rules:praefect-with-db rspec-ee unit pg14: extends: - .rspec-ee-base-pg14 - .rails:rules:ee-only-unit - - .rspec-ee-unit-parallel + - .rspec-ee-unit-pg14-parallel rspec-ee unit pg14 es8: extends: @@ -815,31 +843,35 @@ rspec-ee unit pg14 single-db: extends: - rspec-ee unit pg14 - .single-db-rspec + - .rspec-ee-unit-parallel - .rails:rules:single-db rspec-ee unit pg14 single-redis: extends: - rspec-ee unit pg14 - .no-redis-cluster + - .rspec-ee-unit-parallel - .rails:rules:single-redis rspec-ee unit pg14 single-db-ci-connection: extends: - rspec-ee unit pg14 - .single-db-ci-connection-rspec + - .rspec-ee-unit-parallel - .rails:rules:single-db-ci-connection rspec-ee unit pg14 clusterwide-db: extends: - rspec-ee unit pg14 - .clusterwide-db + - .rspec-ee-unit-parallel - .rails:rules:clusterwide-db rspec-ee integration pg14: extends: - .rspec-ee-base-pg14 - .rails:rules:ee-only-integration - - .rspec-ee-integration-parallel + - .rspec-ee-integration-parallel-pg14 rspec-ee integration pg14 es8: extends: @@ -850,31 +882,35 @@ rspec-ee integration pg14 single-db: extends: - rspec-ee integration pg14 - .single-db-rspec + - .rspec-ee-integration-parallel - .rails:rules:single-db rspec-ee integration pg14 single-redis: extends: - rspec-ee integration pg14 - .no-redis-cluster + - .rspec-ee-integration-parallel - .rails:rules:single-redis rspec-ee integration pg14 single-db-ci-connection: extends: - rspec-ee integration pg14 - .single-db-ci-connection-rspec + - .rspec-ee-integration-parallel - .rails:rules:single-db-ci-connection rspec-ee integration pg14 clusterwide-db: extends: - rspec-ee integration pg14 - .clusterwide-db + - .rspec-ee-integration-parallel - .rails:rules:clusterwide-db rspec-ee system pg14: extends: - .rspec-ee-base-pg14 - .rails:rules:ee-only-system - - .rspec-ee-system-parallel + - .rspec-ee-system-parallel-pg14 rspec-ee system pg14 es8: extends: @@ -885,24 +921,28 @@ rspec-ee system pg14 single-db: extends: - rspec-ee system pg14 - .single-db-rspec + - .rspec-ee-system-parallel - .rails:rules:single-db rspec-ee system pg14 single-redis: extends: - rspec-ee system pg14 - .no-redis-cluster + - .rspec-ee-system-parallel - .rails:rules:single-redis rspec-ee system pg14 single-db-ci-connection: extends: - rspec-ee system pg14 - .single-db-ci-connection-rspec + - .rspec-ee-system-parallel - .rails:rules:single-db-ci-connection rspec-ee system pg14 clusterwide-db: extends: - rspec-ee system pg14 - .clusterwide-db + - .rspec-ee-system-parallel - .rails:rules:clusterwide-db # EE: default refs (MRs, default branch, schedules) jobs # ################################################## diff --git a/.gitlab/ci/rails/shared.gitlab-ci.yml b/.gitlab/ci/rails/shared.gitlab-ci.yml index 8d04e600736..fad24c7b1cb 100644 --- a/.gitlab/ci/rails/shared.gitlab-ci.yml +++ b/.gitlab/ci/rails/shared.gitlab-ci.yml @@ -249,7 +249,7 @@ include: ############################ # rspec job parallel configs -# Adjusting these parallel job counts has an impact on the +# Adjusting these parallel job counts has an impact on the # rspec:artifact-collector jobs in .gitlab/ci/rails.gitlab-ci.yml # Please double-check and adjust accordingly .rspec-migration-parallel: @@ -281,5 +281,42 @@ include: .rspec-ee-system-parallel: parallel: 10 + +# Optimizations to distribute CI time more evenly across the CI/CD pipeline +# +# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/133976 for more info + +# Giving 4 jobs to `rspec-ee unit pg14` +.rspec-unit-pg14-parallel: + parallel: 24 + +# Receiving 4 jobs from `rspec unit pg14` +.rspec-ee-unit-pg14-parallel: + parallel: 22 + +# Giving 2 jobs to `rspec-ee system pg14` +.rspec-system-parallel-pg14: + parallel: 26 + +# Receiving 2 jobs from `rspec system pg14` +.rspec-ee-system-parallel-pg14: + parallel: 12 + +# Adding 4 jobs, as those needs to be a bit faster. +.rspec-migration-parallel-pg14: + parallel: 12 + +# Adding 2 jobs, as those needs to be a bit faster. +.rspec-migration-parallel-pg14-as-if-foss: + parallel: 10 + +# Giving 1 job to `rspec integration pg14` +.rspec-ee-integration-parallel-pg14: + parallel: 5 + +# Receiving 1 job from `rspec-ee integration pg14` +.rspec-integration-parallel-pg14: + parallel: 13 + # rspec job parallel configs ############################ diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 00d30ba74da..a4f11608dbf 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -582,7 +582,7 @@ - "{,ee/,jh/}Gemfile.lock" # This should include gitlab-styles, rubocop itself, and any plugins we might be using - "lib/gitlab_edition.rb" # This is required in RuboCop::CodeReuseHelpers - ".gitlab/ci/static-analysis.gitlab-ci.yml" - - "config/feature_categories.yml" # Used by RSpec/InvalidFeatureCategory + - "config/feature_categories.yml" # Used by RSpec/FeatureCategory .danger-patterns: &danger-patterns - "Dangerfile" diff --git a/.rubocop.yml b/.rubocop.yml index d2e4116bfd7..3f2cf15e46d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -565,7 +565,7 @@ RSpec/FactoriesInMigrationSpecs: - 'spec/lib/ee/gitlab/background_migration/**/*.rb' - 'ee/spec/lib/ee/gitlab/background_migration/**/*.rb' -RSpec/MissingFeatureCategory: +RSpec/FeatureCategory: Enabled: true Exclude: - 'qa/**/*.rb' diff --git a/.rubocop_todo/rspec/missing_feature_category.yml b/.rubocop_todo/rspec/feature_category.yml index a5a512e92b8..a8db7b1fa09 100644 --- a/.rubocop_todo/rspec/missing_feature_category.yml +++ b/.rubocop_todo/rspec/feature_category.yml @@ -1,5 +1,5 @@ --- -RSpec/MissingFeatureCategory: +RSpec/FeatureCategory: Exclude: - 'ee/spec/components/namespaces/storage/subgroup_pre_enforcement_alert_component_spec.rb' - 'ee/spec/controllers/admin/application_settings_controller_spec.rb' diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index ef57bf3dfa9..32fe05220fc 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -8c09007d6a340364eda51476ab99a3d27c720713 +da0e1ee67e706eb33e22aadc0a87f8b728342303 @@ -448,7 +448,7 @@ group :development, :test do end group :development, :test, :danger do - gem 'gitlab-dangerfiles', '~> 4.1.0', require: false, feature_category: :tooling + gem 'gitlab-dangerfiles', '~> 4.3.2', require: false, feature_category: :tooling end group :development, :test, :coverage do diff --git a/Gemfile.checksum b/Gemfile.checksum index 8397f016bac..7b924e447d7 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -206,7 +206,7 @@ {"name":"gitaly","version":"16.5.0.pre.rc1","platform":"ruby","checksum":"ed17515ad04d4663a0efc15c8f2887b705f006133e8b10cc9321460eb0a38353"}, {"name":"gitlab","version":"4.19.0","platform":"ruby","checksum":"3f645e3e195dbc24f0834fbf83e8ccfb2056d8e9712b01a640aad418a6949679"}, {"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"}, -{"name":"gitlab-dangerfiles","version":"4.1.0","platform":"ruby","checksum":"ecf2262fcc038c1e77f7ea014f5fa8657e02ae37fde5034a2d7f67fd6e804d8d"}, +{"name":"gitlab-dangerfiles","version":"4.3.2","platform":"ruby","checksum":"978bd81e30faccc629f2cdbc6f320a1d225188fbc18f072b8a60abdb53e80a96"}, {"name":"gitlab-experiment","version":"0.8.0","platform":"ruby","checksum":"b4e2f73e0af19cdd899a745f5a846c1318d44054e068a8f4ac887f6b1017d3f9"}, {"name":"gitlab-fog-azure-rm","version":"1.8.0","platform":"ruby","checksum":"e4f24b174b273b88849d12fbcfecb79ae1c09f56cbd614998714c7f0a81e6c28"}, {"name":"gitlab-labkit","version":"0.34.0","platform":"ruby","checksum":"ca5c504201390cd07ba1029e6ca3059f4e2e6005eb121ba8a103af1e166a3ecd"}, diff --git a/Gemfile.lock b/Gemfile.lock index 77e936d8bc0..c84cca6e917 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -646,10 +646,10 @@ GEM terminal-table (>= 1.5.1) gitlab-chronic (0.10.5) numerizer (~> 0.2) - gitlab-dangerfiles (4.1.0) + gitlab-dangerfiles (4.3.2) danger (>= 9.3.0) danger-gitlab (>= 8.0.0) - rake + rake (~> 13.0) gitlab-experiment (0.8.0) activesupport (>= 3.0) request_store (>= 1.0) @@ -1818,7 +1818,7 @@ DEPENDENCIES gettext_i18n_rails_js (~> 1.3) gitaly (~> 16.5.0.pre.rc1) gitlab-chronic (~> 0.10.5) - gitlab-dangerfiles (~> 4.1.0) + gitlab-dangerfiles (~> 4.3.2) gitlab-experiment (~> 0.8.0) gitlab-fog-azure-rm (~> 1.8.0) gitlab-http! diff --git a/app/assets/javascripts/behaviors/markdown/render_math.js b/app/assets/javascripts/behaviors/markdown/render_math.js index 7525fc76d16..4cba3eccb45 100644 --- a/app/assets/javascripts/behaviors/markdown/render_math.js +++ b/app/assets/javascripts/behaviors/markdown/render_math.js @@ -22,6 +22,31 @@ const waitForReflow = (fn) => { window.requestIdleCallback(fn); }; +const katexOptions = (el) => { + const options = { + displayMode: el.dataset.mathStyle === 'display', + throwOnError: true, + trust: (context) => + // this config option restores the KaTeX pre-v0.11.0 + // behavior of allowing certain commands and protocols + // eslint-disable-next-line @gitlab/require-i18n-strings + ['\\url', '\\href'].includes(context.command) && + ['http', 'https', 'mailto', '_relative'].includes(context.protocol), + }; + + if (gon.math_rendering_limits_enabled) { + options.maxSize = MAX_USER_SPECIFIED_EMS; + // See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111107 for + // reasoning behind this value + options.maxExpand = MAX_MACRO_EXPANSIONS; + } else { + // eslint-disable-next-line @gitlab/require-i18n-strings + options.maxExpand = 'Infinity'; + } + + return options; +}; + /** * Renders math blocks sequentially while protecting against DoS attacks. Math blocks have a maximum character limit of MAX_MATH_CHARS. If rendering math takes longer than MAX_RENDER_TIME_MS, all subsequent math blocks are skipped and an error message is shown. */ @@ -60,7 +85,10 @@ class SafeMathRenderer { } const el = chosenEl || this.queue.shift(); - const forceRender = Boolean(chosenEl) || unrestrictedPages.includes(this.pageName); + const forceRender = + Boolean(chosenEl) || + unrestrictedPages.includes(this.pageName) || + !gon.math_rendering_limits_enabled; const text = el.textContent; el.removeAttribute('style'); @@ -128,20 +156,7 @@ class SafeMathRenderer { } // eslint-disable-next-line no-unsanitized/property - displayContainer.innerHTML = this.katex.renderToString(text, { - displayMode: el.dataset.mathStyle === 'display', - throwOnError: true, - maxSize: MAX_USER_SPECIFIED_EMS, - // See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111107 for - // reasoning behind this value - maxExpand: MAX_MACRO_EXPANSIONS, - trust: (context) => - // this config option restores the KaTeX pre-v0.11.0 - // behavior of allowing certain commands and protocols - // eslint-disable-next-line @gitlab/require-i18n-strings - ['\\url', '\\href'].includes(context.command) && - ['http', 'https', 'mailto', '_relative'].includes(context.protocol), - }); + displayContainer.innerHTML = this.katex.renderToString(text, katexOptions(el)); } catch (e) { // Don't show a flash for now because it would override an existing flash message if (e.message.match(/Too many expansions/)) { diff --git a/app/assets/javascripts/ci/pipelines_page/components/pipeline_labels.vue b/app/assets/javascripts/ci/pipelines_page/components/pipeline_labels.vue index 35baee7e96a..8f45094eb74 100644 --- a/app/assets/javascripts/ci/pipelines_page/components/pipeline_labels.vue +++ b/app/assets/javascripts/ci/pipelines_page/components/pipeline_labels.vue @@ -37,6 +37,13 @@ export default { this.pipeline?.project?.full_path !== `/${this.targetProjectFullPath}`, ); }, + showMergedResultsBadge() { + // A merge train pipeline is technically also a merged results pipeline, + // but we want the badges to be mutually exclusive. + return ( + this.pipeline.flags.merged_result_pipeline && !this.pipeline.flags.merge_train_pipeline + ); + }, autoDevopsTagId() { return `pipeline-url-autodevops-${this.pipeline.id}`; }, @@ -73,7 +80,7 @@ export default { v-gl-tooltip :title=" s__( - 'Pipeline|This pipeline ran on the contents of this merge request combined with the contents of all other merge requests queued for merging into the target branch.', + 'Pipeline|This pipeline ran on the contents of the merge request combined with the contents of all other merge requests queued for merging into the target branch.', ) " variant="info" @@ -148,7 +155,7 @@ export default { v-gl-tooltip :title=" s__( - `Pipeline|This pipeline ran on the contents of this merge request's source branch, not the target branch.`, + `Pipeline|This pipeline ran on the contents of the merge request's source branch, not the target branch.`, ) " variant="info" @@ -157,11 +164,11 @@ export default { >{{ s__('Pipeline|merge request') }}</gl-badge > <gl-badge - v-if="pipeline.flags.merged_result_pipeline" + v-if="showMergedResultsBadge" v-gl-tooltip :title=" s__( - `Pipeline|This pipeline ran on the contents of this merge request combined with the contents of the target branch.`, + `Pipeline|This pipeline ran on the contents of the merge request combined with the contents of the target branch.`, ) " variant="info" diff --git a/app/assets/javascripts/issues/show/components/header_actions.vue b/app/assets/javascripts/issues/show/components/header_actions.vue index 681d2c79ba1..13bcb4706c9 100644 --- a/app/assets/javascripts/issues/show/components/header_actions.vue +++ b/app/assets/javascripts/issues/show/components/header_actions.vue @@ -406,7 +406,7 @@ export default { right @shown="dismissPopover" > - <template v-if="showMovedSidebarOptions"> + <template v-if="showMovedSidebarOptions && !glFeatures.notificationsTodosButtons"> <sidebar-subscriptions-widget :iid="String(iid)" :full-path="fullPath" diff --git a/app/assets/javascripts/merge_requests/components/sticky_header.vue b/app/assets/javascripts/merge_requests/components/sticky_header.vue index acb7b8353c0..e8bdb854334 100644 --- a/app/assets/javascripts/merge_requests/components/sticky_header.vue +++ b/app/assets/javascripts/merge_requests/components/sticky_header.vue @@ -87,7 +87,7 @@ export default { return isLoggedIn(); }, isNotificationsTodosButtons() { - return this.glFeatures.notificationsTodosButtons; + return this.glFeatures.notificationsTodosButtons && this.glFeatures.movedMrSidebar; }, }, watch: { diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue index 9404fd04bc9..b392ad55fa2 100644 --- a/app/assets/javascripts/notes/components/discussion_counter.vue +++ b/app/assets/javascripts/notes/components/discussion_counter.vue @@ -72,7 +72,7 @@ export default { return options; }, isNotificationsTodosButtons() { - return this.glFeatures.notificationsTodosButtons; + return this.glFeatures.notificationsTodosButtons && this.glFeatures.movedMrSidebar; }, }, methods: { diff --git a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue index 54b9ad90b62..866db2a43b8 100644 --- a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue +++ b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue @@ -2,7 +2,6 @@ import { GlButton, GlDisclosureDropdownItem, - GlDropdownForm, GlIcon, GlLoadingIcon, GlToggle, @@ -33,7 +32,6 @@ export default { components: { GlButton, GlDisclosureDropdownItem, - GlDropdownForm, GlIcon, GlLoadingIcon, GlToggle, @@ -133,7 +131,10 @@ export default { return this.emailsDisabled || !this.isLoggedIn; }, isNotificationsTodosButtons() { - return this.glFeatures.notificationsTodosButtons; + return this.glFeatures.notificationsTodosButtons && this.glFeatures.movedMrSidebar; + }, + isMergeRequest() { + return this.issuableType === 'merge_request'; }, }, methods: { @@ -199,20 +200,8 @@ export default { </script> <template> - <gl-dropdown-form v-if="isMovedMrSidebar && isIssuable" class="gl-dropdown-item"> - <div class="gl-px-5 gl-pb-2 gl-pt-1"> - <gl-toggle - :value="subscribed" - :label="$options.i18n.notifications" - class="merge-request-notification-toggle" - label-position="left" - data-testid="notification-toggle" - @change="toggleSubscribed" - /> - </div> - </gl-dropdown-form> <gl-disclosure-dropdown-item - v-else-if="isMovedMrSidebar && !isNotificationsTodosButtons" + v-if="isMovedMrSidebar && !isNotificationsTodosButtons" data-testid="notification-toggle" @action="toggleSubscribed" > @@ -225,17 +214,32 @@ export default { /> </template> </gl-disclosure-dropdown-item> - <gl-button - v-else-if="isNotificationsTodosButtons" - ref="tooltip" - v-gl-tooltip.hover.top - category="secondary" - data-testid="subscribe-button" - :title="notificationTooltip" - @click="toggleSubscribed" - > - <gl-icon :name="notificationIcon" :size="16" :class="{ 'gl-fill-blue-500': subscribed }" /> - </gl-button> + <div v-else-if="isNotificationsTodosButtons" :class="{ 'inline-block': !isMergeRequest }"> + <gl-button + ref="tooltip" + v-gl-tooltip.hover.top + category="secondary" + data-testid="subscribe-button" + class="hide-collapsed" + :title="notificationTooltip" + :class="{ 'gl-ml-2': isIssuable, 'btn-icon': isNotificationsTodosButtons }" + @click="toggleSubscribed" + > + <gl-icon :name="notificationIcon" :size="16" :class="{ 'gl-fill-blue-500': subscribed }" /> + </gl-button> + <gl-button + v-if="!isMergeRequest" + ref="tooltip" + v-gl-tooltip.left.viewport + category="secondary" + data-testid="subscribe-button" + :title="notificationTooltip" + class="sidebar-collapsed-icon sidebar-collapsed-container gl-rounded-0! gl-shadow-none!" + @click="toggleSubscribed" + > + <gl-icon :name="notificationIcon" :size="16" :class="{ 'gl-fill-blue-500': subscribed }" /> + </gl-button> + </div> <sidebar-editable-item v-else ref="editable" diff --git a/app/assets/javascripts/sidebar/components/todo_toggle/sidebar_todo_widget.vue b/app/assets/javascripts/sidebar/components/todo_toggle/sidebar_todo_widget.vue index 31133139bd4..e034607f79c 100644 --- a/app/assets/javascripts/sidebar/components/todo_toggle/sidebar_todo_widget.vue +++ b/app/assets/javascripts/sidebar/components/todo_toggle/sidebar_todo_widget.vue @@ -115,7 +115,7 @@ export default { return todoLabel(this.hasTodo); }, isNotificationsTodosButtons() { - return this.glFeatures.notificationsTodosButtons; + return this.glFeatures.notificationsTodosButtons && this.glFeatures.movedMrSidebar; }, }, methods: { @@ -186,27 +186,25 @@ export default { </script> <template> - <gl-button - v-if="isNotificationsTodosButtons && isMergeRequest" - v-gl-tooltip.hover.top - data-testid="sidebar-todo" - :title="tootltipTitle" - category="secondary" - type="reset" - @click.stop.prevent="toggleTodo" - > - <gl-icon :class="{ 'todo-undone gl-fill-blue-500': hasTodo }" :name="collapsedButtonIcon" /> - </gl-button> - <div v-else data-testid="sidebar-todo"> + <div data-testid="sidebar-todo" :class="{ 'inline-block': !isMergeRequest }"> <todo-button + v-gl-tooltip.hover.top + :title="isNotificationsTodosButtons ? tootltipTitle : undefined" :issuable-type="issuableType" :issuable-id="issuableId" :is-todo="hasTodo" :loading="isLoading" - :size="isMergeRequest ? 'medium' : 'small'" + :size="isMergeRequest || isNotificationsTodosButtons ? 'medium' : 'small'" + :class="{ 'btn-icon': isNotificationsTodosButtons }" class="hide-collapsed" @click.stop.prevent="toggleTodo" - /> + > + <gl-icon + v-if="isNotificationsTodosButtons" + :class="{ 'todo-undone gl-fill-blue-500': hasTodo }" + :name="collapsedButtonIcon" + /> + </todo-button> <gl-button v-if="isClassicSidebar && !isMergeRequest" v-gl-tooltip.left.viewport diff --git a/app/assets/javascripts/sidebar/components/todo_toggle/todo_button.vue b/app/assets/javascripts/sidebar/components/todo_toggle/todo_button.vue index b49b8fc389b..2aa79b45093 100644 --- a/app/assets/javascripts/sidebar/components/todo_toggle/todo_button.vue +++ b/app/assets/javascripts/sidebar/components/todo_toggle/todo_button.vue @@ -39,6 +39,6 @@ export default { <template> <gl-button v-bind="$attrs" :aria-label="buttonLabel" @click="onToggle($event)"> - {{ buttonLabel }} + <slot>{{ buttonLabel }}</slot> </gl-button> </template> diff --git a/app/assets/javascripts/vue_shared/components/mr_more_dropdown.vue b/app/assets/javascripts/vue_shared/components/mr_more_dropdown.vue index e2227904daf..8d228216d84 100644 --- a/app/assets/javascripts/vue_shared/components/mr_more_dropdown.vue +++ b/app/assets/javascripts/vue_shared/components/mr_more_dropdown.vue @@ -144,7 +144,7 @@ export default { return this.glFeatures.movedMrSidebar; }, isNotificationsTodosButtons() { - return this.glFeatures.notificationsTodosButtons; + return this.glFeatures.notificationsTodosButtons && this.glFeatures.movedMrSidebar; }, draftLabel() { return this.draft ? this.$options.i18n.markAsReady : this.$options.i18n.markAsDraft; diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 9c31559cc0e..0619d5f166e 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -55,6 +55,10 @@ padding-right: 0; z-index: $zindex-dropdown-menu; + .inline-block { + @include gl-display-inline-block; + } + &.right-sidebar-merge-requests { width: $right-sidebar-width; diff --git a/app/controllers/projects/incidents_controller.rb b/app/controllers/projects/incidents_controller.rb index e78c9171a53..bacf3192ee6 100644 --- a/app/controllers/projects/incidents_controller.rb +++ b/app/controllers/projects/incidents_controller.rb @@ -12,6 +12,7 @@ class Projects::IncidentsController < Projects::ApplicationController push_force_frontend_feature_flag(:work_items_mvc_2, @project&.work_items_mvc_2_feature_flag_enabled?) push_frontend_feature_flag(:moved_mr_sidebar, project) push_force_frontend_feature_flag(:linked_work_items, @project&.linked_work_items_feature_flag_enabled?) + push_frontend_feature_flag(:notifications_todos_buttons, project) end feature_category :incident_management diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 80783534ca3..4849cccac52 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -70,6 +70,7 @@ class Projects::IssuesController < Projects::ApplicationController push_frontend_feature_flag(:epic_widget_edit_confirmation, project) push_frontend_feature_flag(:moved_mr_sidebar, project) push_force_frontend_feature_flag(:linked_work_items, project.linked_work_items_feature_flag_enabled?) + push_frontend_feature_flag(:notifications_todos_buttons, project) end around_action :allow_gitaly_ref_name_caching, only: [:discussions] diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 3685352b17d..7fff31c767f 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -47,10 +47,6 @@ class SearchController < ApplicationController push_frontend_feature_flag(:search_merge_requests_hide_archived_projects, current_user) end - before_action only: :show do - push_frontend_feature_flag(:search_commits_hide_archived_projects, current_user) - end - rescue_from ActiveRecord::QueryCanceled, with: :render_timeout layout 'search' diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index e857d81886f..58648a82487 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -315,6 +315,7 @@ module ApplicationSettingsHelper :jira_connect_application_key, :jira_connect_public_key_storage_enabled, :jira_connect_proxy_url, + :math_rendering_limits_enabled, :max_artifacts_size, :max_attachment_size, :max_decompressed_archive_size, diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 14b5973b8b5..824a2bd9fa4 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -838,6 +838,9 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord allow_nil: false, inclusion: { in: [true, false], message: N_('must be a boolean value') } + validates :math_rendering_limits_enabled, + inclusion: { in: [true, false], message: N_('must be a boolean value') } + before_validation :ensure_uuid! before_validation :coerce_repository_storages_weighted, if: :repository_storages_weighted_changed? before_validation :normalize_default_branch_name diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index 06d4aab8b8e..1bd15a56de5 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -119,6 +119,7 @@ module ApplicationSettingImplementation login_recaptcha_protection_enabled: false, mailgun_signing_key: nil, mailgun_events_enabled: false, + math_rendering_limits_enabled: true, max_artifacts_size: Settings.artifacts['max_size'], max_attachment_size: Settings.gitlab['max_attachment_size'], max_decompressed_archive_size: 25600, diff --git a/app/models/user_custom_attribute.rb b/app/models/user_custom_attribute.rb index b2674cb4e88..728c1f4844a 100644 --- a/app/models/user_custom_attribute.rb +++ b/app/models/user_custom_attribute.rb @@ -18,6 +18,7 @@ class UserCustomAttribute < ApplicationRecord AUTO_BANNED_BY_ABUSE_REPORT_ID = 'auto_banned_by_abuse_report_id' AUTO_BANNED_BY_SPAM_LOG_ID = 'auto_banned_by_spam_log_id' TRUSTED_BY = 'trusted_by' + AUTO_BANNED_BY = 'auto_banned_by' IDENTITY_VERIFICATION_PHONE_EXEMPT = 'identity_verification_phone_exempt' class << self diff --git a/app/services/users/auto_ban_service.rb b/app/services/users/auto_ban_service.rb new file mode 100644 index 00000000000..fa3b738b4cd --- /dev/null +++ b/app/services/users/auto_ban_service.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Users + class AutoBanService < BaseService + def initialize(user:, reason:) + @user = user + @reason = reason + end + + def execute + if user.ban + record_custom_attribute + success + else + messages = user.errors.full_messages + error(messages.uniq.join('. ')) + end + end + + private + + attr_reader :user, :reason + + def record_custom_attribute + custom_attribute = { + user_id: user.id, + key: UserCustomAttribute::AUTO_BANNED_BY, + value: reason + } + UserCustomAttribute.upsert_custom_attributes([custom_attribute]) + end + end +end diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 3afde51041e..f863fbd296f 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -15,11 +15,14 @@ %aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { always_show_toggle: true, signed: { in: signed_in }, issuable_type: issuable_type }, class: "#{sidebar_gutter_collapsed_class(is_merge_request_with_flag)} #{'right-sidebar-merge-requests' if is_merge_request_with_flag}", 'aria-live' => 'polite', 'aria-label': issuable_type } .issuable-sidebar{ class: "#{'is-merge-request' if is_merge_request_with_flag}" } - .issuable-sidebar-header{ class: "#{'gl-pb-2! gl-md-display-flex gl-justify-content-end gl-lg-display-none!' if is_merge_request_with_flag}" } + .issuable-sidebar-header{ class: "gl-pb-4! #{'gl-pb-2! gl-md-display-flex gl-justify-content-end gl-lg-display-none!' if is_merge_request_with_flag}" } = render Pajamas::ButtonComponent.new(button_options: { class: "gutter-toggle float-right js-sidebar-toggle has-tooltip gl-shadow-none! #{'gl-display-block' if moved_sidebar_enabled}", type: 'button', 'aria-label' => _('Toggle sidebar'), title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left', boundary: 'viewport' } }) do = sidebar_gutter_toggle_icon - - if signed_in && !is_merge_request_with_flag - .js-sidebar-todo-widget-root{ data: { project_path: issuable_sidebar[:project_full_path], iid: issuable_sidebar[:iid], id: issuable_sidebar[:id] } } + - if signed_in + - if !is_merge_request_with_flag + .js-sidebar-todo-widget-root{ data: { project_path: issuable_sidebar[:project_full_path], iid: issuable_sidebar[:iid], id: issuable_sidebar[:id] } } + - if notifications_todos_buttons_enabled? + .js-sidebar-subscriptions-widget-root = form_for issuable_type, url: issuable_sidebar[:issuable_json_path], remote: true, html: { class: 'issuable-context-form inline-update js-issuable-update' } do |f| .block.assignee{ class: "#{'gl-mt-3' if !signed_in && moved_sidebar_enabled}", data: { testid: 'assignee-block-container' } } diff --git a/config/feature_flags/development/search_commits_hide_archived_projects.yml b/config/feature_flags/development/admin_group_member.yml index 068118bbcbc..c6267dd3fe3 100644 --- a/config/feature_flags/development/search_commits_hide_archived_projects.yml +++ b/config/feature_flags/development/admin_group_member.yml @@ -1,8 +1,8 @@ --- -name: search_commits_hide_archived_projects -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128080 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/420910 -milestone: '16.3' +name: admin_group_member +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131914 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/426580 +milestone: '16.5' type: development -group: group::global search +group: group::authentication and authorization default_enabled: false diff --git a/danger/plugins/rubocop.rb b/danger/plugins/rubocop.rb new file mode 100644 index 00000000000..6645a37041b --- /dev/null +++ b/danger/plugins/rubocop.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require_relative '../../tooling/danger/rubocop_inline_disable_suggestion' + +module Danger + class Rubocop < ::Danger::Plugin + # Put the helper code somewhere it can be tested + def add_suggestions_for(filename) + Tooling::Danger::RubocopInlineDisableSuggestion.new(filename, context: self).suggest + end + end +end diff --git a/danger/rubocop/Dangerfile b/danger/rubocop/Dangerfile new file mode 100644 index 00000000000..a53847199db --- /dev/null +++ b/danger/rubocop/Dangerfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +helper.all_changed_files.each do |filename| + rubocop.add_suggestions_for(filename) +end diff --git a/db/migrate/20230910143103_add_admin_members_to_member_role.rb b/db/migrate/20230910143103_add_admin_members_to_member_role.rb new file mode 100644 index 00000000000..a4d191ad7db --- /dev/null +++ b/db/migrate/20230910143103_add_admin_members_to_member_role.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class AddAdminMembersToMemberRole < Gitlab::Database::Migration[2.1] + enable_lock_retries! + + def up + add_column :member_roles, :admin_group_member, :boolean, default: false, null: false + end + + def down + remove_column :member_roles, :admin_group_member + end +end diff --git a/db/migrate/20230929151451_add_math_rendering_limits_enabled.rb b/db/migrate/20230929151451_add_math_rendering_limits_enabled.rb new file mode 100644 index 00000000000..d52019c41ed --- /dev/null +++ b/db/migrate/20230929151451_add_math_rendering_limits_enabled.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddMathRenderingLimitsEnabled < Gitlab::Database::Migration[2.1] + enable_lock_retries! + + def change + add_column :application_settings, :math_rendering_limits_enabled, :boolean, default: true, null: false + end +end diff --git a/db/schema_migrations/20230910143103 b/db/schema_migrations/20230910143103 new file mode 100644 index 00000000000..0cdb16e4b85 --- /dev/null +++ b/db/schema_migrations/20230910143103 @@ -0,0 +1 @@ +42b5ec67a607af2774d224f6b90b05a419bedcb33ef17b66f893a446e03c0839
\ No newline at end of file diff --git a/db/schema_migrations/20230929151451 b/db/schema_migrations/20230929151451 new file mode 100644 index 00000000000..fbc9c908c91 --- /dev/null +++ b/db/schema_migrations/20230929151451 @@ -0,0 +1 @@ +414442e7daf9e23bb33c8471f30c6465bcef6a19051d609888f5bbf88bb5306e
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 48b25ae3cfc..bf2af226227 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -11856,6 +11856,7 @@ CREATE TABLE application_settings ( encrypted_vertex_ai_access_token bytea, encrypted_vertex_ai_access_token_iv bytea, project_jobs_api_rate_limit integer DEFAULT 600 NOT NULL, + math_rendering_limits_enabled boolean DEFAULT true 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)), @@ -18144,6 +18145,7 @@ CREATE TABLE member_roles ( name text DEFAULT 'Custom'::text NOT NULL, description text, admin_merge_request boolean DEFAULT false NOT NULL, + admin_group_member boolean DEFAULT false NOT NULL, CONSTRAINT check_4364846f58 CHECK ((char_length(description) <= 255)), CONSTRAINT check_9907916995 CHECK ((char_length(name) <= 255)) ); diff --git a/doc/administration/audit_event_streaming/index.md b/doc/administration/audit_event_streaming/index.md index 8a1aa9a661c..acf6d3c02e0 100644 --- a/doc/administration/audit_event_streaming/index.md +++ b/doc/administration/audit_event_streaming/index.md @@ -11,7 +11,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - Custom HTTP headers UI [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/361630) in GitLab 15.2 [with a flag](../feature_flags.md) named `custom_headers_streaming_audit_events_ui`. Disabled by default. > - Custom HTTP headers UI [made generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/365259) in GitLab 15.3. [Feature flag `custom_headers_streaming_audit_events_ui`](https://gitlab.com/gitlab-org/gitlab/-/issues/365259) removed. > - [Improved user experience](https://gitlab.com/gitlab-org/gitlab/-/issues/367963) in GitLab 15.3. -> - [HTTP destination **Name*** field](https://gitlab.com/gitlab-org/gitlab/-/issues/411357) added in GitLab 16.3. +> - HTTP destination **Name*** field [added](https://gitlab.com/gitlab-org/gitlab/-/issues/411357) in GitLab 16.3. +> - Functionality for the **Active** checkbox [added](https://gitlab.com/gitlab-org/gitlab/-/issues/415268) in GitLab 16.5. Users can set a streaming destination for a top-level group or instance to receive all audit events about the group, subgroups, and projects, as structured JSON. @@ -52,8 +53,7 @@ To add streaming destinations to a top-level group: 1. Select **Add streaming destination** and select **HTTP endpoint** to show the section for adding destinations. 1. In the **Name** and **Destination URL** fields, add a destination name and URL. 1. Optional. Locate the **Custom HTTP headers** table. -1. Ignore the **Active** checkbox because it isn't functional. To track progress on adding functionality to the - **Active** checkbox, see [issue 367509](https://gitlab.com/gitlab-org/gitlab/-/issues/367509). +1. To make the header active, select the **Active** checkbox. The header will be sent with the audit event. 1. Select **Add header** to create a new name and value pair. Enter as many name and value pairs as required. You can add up to 20 headers per streaming destination. 1. After all headers have been filled out, select **Add** to add the new streaming destination. @@ -94,8 +94,7 @@ To update a streaming destination's custom HTTP headers: 1. Select the stream to expand. 1. Locate the **Custom HTTP headers** table. 1. Locate the header that you wish to update. -1. Ignore the **Active** checkbox because it isn't functional. To track progress on adding functionality to the - **Active** checkbox, see [issue 367509](https://gitlab.com/gitlab-org/gitlab/-/issues/367509). +1. To make the header active, select the **Active** checkbox. The header will be sent with the audit event. 1. Select **Add header** to create a new name and value pair. Enter as many name and value pairs as required. You can add up to 20 headers per streaming destination. 1. Select **Save** to update the streaming destination. @@ -281,8 +280,7 @@ To add a streaming destination for an instance: 1. Select **Add streaming destination** and select **HTTP endpoint** to show the section for adding destinations. 1. In the **Name** and **Destination URL** fields, add a destination name and URL. 1. Optional. To add custom HTTP headers, select **Add header** to create a new name and value pair, and input their values. Repeat this step for as many name and value pairs are required. You can add up to 20 headers per streaming destination. -1. Ignore the **Active** checkbox because it isn't functional. To track progress on adding functionality to the - **Active** checkbox, see [issue 367509](https://gitlab.com/gitlab-org/gitlab/-/issues/367509). +1. To make the header active, select the **Active** checkbox. The header will be sent with the audit event. 1. Select **Add header** to create a new name and value pair. Repeat this step for as many name and value pairs are required. You can add up to 20 headers per streaming destination. 1. After all headers have been filled out, select **Add** to add the new streaming destination. @@ -326,8 +324,7 @@ To update a instance streaming destination's custom HTTP headers: 1. Select the stream to expand. 1. Locate the **Custom HTTP headers** table. 1. Locate the header that you wish to update. -1. Ignore the **Active** checkbox because it isn't functional. To track progress on adding functionality to the - **Active** checkbox, see [issue 367509](https://gitlab.com/gitlab-org/gitlab/-/issues/367509). +1. To make the header active, select the **Active** checkbox. The header will be sent with the audit event. 1. Select **Add header** to create a new name and value pair. Enter as many name and value pairs as required. You can add up to 20 headers per streaming destination. 1. Select **Save** to update the streaming destination. diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md index 377a3dba6c9..8f03a2224ec 100644 --- a/doc/administration/instance_limits.md +++ b/doc/administration/instance_limits.md @@ -990,6 +990,31 @@ You can configure this limit for self-managed installations when you [enable Elasticsearch](../integration/advanced_search/elasticsearch.md#enable-advanced-search). Set the limit to `0` to disable it. +## Math rendering limits + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132939) in GitLab 16.5. + +GitLab imposes default limits when rendering math in Markdown fields. These limits provide better security and performance. + +The limits for issues, merge requests, wikis, and repositories: + +- Maximum number of nodes rendered: `50`. +- Maximum number of macro expansions: `1000`. +- Maximum user-specified size in em: `20`. + +The limits for issues and merge requests: + +- Maximum number of characters in a math block: `1000`. +- Maximum rendering time: `2000 ms`. + +You can disable these limits when running in a self-managed instance and you trust the user input. + +Use the [GitLab Rails console](operations/rails_console.md#starting-a-rails-console-session): + +```ruby +ApplicationSetting.update(math_rendering_limits_enabled: false) +``` + ## Wiki limits - [Wiki page content size limit](wikis/index.md#wiki-page-content-size-limit). diff --git a/doc/api/member_roles.md b/doc/api/member_roles.md index c5d68371afd..36f9b4694b9 100644 --- a/doc/api/member_roles.md +++ b/doc/api/member_roles.md @@ -13,9 +13,10 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - [Read dependency added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/126247) in GitLab 16.3. > - [Name and description fields added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/126423) in GitLab 16.3. > - [Admin merge request introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128302) in GitLab 16.4 [with a flag](../administration/feature_flags.md) named `admin_merge_request`. Disabled by default. +> - [Admin group members introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131914) in GitLab 16.5 [with a flag](../administration/feature_flags.md) named `admin_group_member`. Disabled by default. FLAG: -On self-managed GitLab, by default this feature is not available. To make it available, an administrator can [enable the feature flag](../administration/feature_flags.md) named `admin_merge_request`. +On self-managed GitLab, by default these two features are not available. To make them available, an administrator can [enable the feature flags](../administration/feature_flags.md) named `admin_merge_request` and `admin_member_custom_role`. On GitLab.com, this feature is not available. ## List all member roles of a group @@ -44,6 +45,7 @@ If successful, returns [`200`](rest/index.md#status-codes) and the following res | `[].read_code` | boolean | Permission to read project code. | | `[].read_dependency` | boolean | Permission to read project dependencies. | | `[].read_vulnerability` | boolean | Permission to read project vulnerabilities. | +| `[].admin_group_member` | boolean | Permission to admin members of a group. | Example request: diff --git a/doc/development/feature_categorization/index.md b/doc/development/feature_categorization/index.md index 7302c3abbd1..2f4a7380bff 100644 --- a/doc/development/feature_categorization/index.md +++ b/doc/development/feature_categorization/index.md @@ -232,7 +232,7 @@ To disable the warning use `RSPEC_WARN_MISSING_FEATURE_CATEGORY=false` when runn RSPEC_WARN_MISSING_FEATURE_CATEGORY=false bin/rspec spec/<test_file> ``` -Additionally, we flag the offenses via `RSpec/MissingFeatureCategory` RuboCop rule. +Additionally, we flag the offenses via `RSpec/FeatureCategory` RuboCop rule. ### Tooling feature category diff --git a/doc/user/custom_roles.md b/doc/user/custom_roles.md index 9ae65c681fc..53cb835eed1 100644 --- a/doc/user/custom_roles.md +++ b/doc/user/custom_roles.md @@ -13,6 +13,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - Ability to view a vulnerability report [enabled by default](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123835) in GitLab 16.1. > - [Feature flag `custom_roles_vulnerability` removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124049) in GitLab 16.2. > - Ability to create and remove a custom role with the UI [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/393235) in GitLab 16.4. +> - Ability to manage group members [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/17364) in GitLab 16.5 under `admin_group_member` Feature flag. Custom roles allow group members who are assigned the Owner role to create roles specific to the needs of their organization. @@ -26,6 +27,7 @@ The following granular permissions are available. You can add these permissions - In GitLab 16.1 and later, you can create a custom role that can view vulnerability reports and change the status of the vulnerabilities. - In GitLab 16.3 and later, you can create a custom role that can view the dependency list. - In GitLab 16.4 and later, you can create a custom role that can approve merge requests. +- In GitLab 16.5 and later, you can create a custom role that can manage group members. You can discuss individual custom role and permission requests in [issue 391760](https://gitlab.com/gitlab-org/gitlab/-/issues/391760). @@ -90,15 +92,16 @@ For every ability, a minimal access level is defined. To be able to create a cus Some roles and abilities require having other abilities enabled. For example, a custom role can only have administration of vulnerabilities (`admin_vulnerability`) enabled if reading vulnerabilities (`read_vulnerability`) is also enabled. -You can see the required minimal access levels and abilities requirements in the following table. +You can see the abilities requirements in the following table. -| Ability | Minimal access level | Required ability | -| -- | -- | -- | -| `read_code` | Guest | - | -| `read_dependency` | Guest | - | -| `read_vulnerability` | Guest | - | -| `admin_merge_request` | Guest | - | -| `admin_vulnerability` | Guest | `read_vulnerability` | +| Ability | Required ability | +| -- | -- | +| `read_code` | - | +| `read_dependency` | - | +| `read_vulnerability` | - | +| `admin_merge_request` | - | +| `admin_vulnerability` | `read_vulnerability` | +| `admin_group_member` | - | ## Associate a custom role with an existing group member diff --git a/gems/config/rubocop.yml b/gems/config/rubocop.yml index 72b37aa60b5..d6139bef1b5 100644 --- a/gems/config/rubocop.yml +++ b/gems/config/rubocop.yml @@ -93,7 +93,7 @@ RSpec/ContextWording: - 'if' # This cop doesn't make sense in the context of gems -RSpec/MissingFeatureCategory: +RSpec/FeatureCategory: Enabled: false # Enable once we drop 3.0 support diff --git a/gems/gitlab-schema-validation/lib/gitlab/schema/validation/adapters/column_structure_sql_adapter.rb b/gems/gitlab-schema-validation/lib/gitlab/schema/validation/adapters/column_structure_sql_adapter.rb index 62e501bf16b..5b6026a1f73 100644 --- a/gems/gitlab-schema-validation/lib/gitlab/schema/validation/adapters/column_structure_sql_adapter.rb +++ b/gems/gitlab-schema-validation/lib/gitlab/schema/validation/adapters/column_structure_sql_adapter.rb @@ -36,7 +36,7 @@ module Gitlab value = parse_node(constraints.find { |node| node.constraint.contype == DEFAULT_CONSTR }) - return unless value + return if value.nil? "DEFAULT #{value}" end diff --git a/gems/gitlab-schema-validation/spec/fixtures/structure.sql b/gems/gitlab-schema-validation/spec/fixtures/structure.sql index 421fb6c3593..7bb68734c72 100644 --- a/gems/gitlab-schema-validation/spec/fixtures/structure.sql +++ b/gems/gitlab-schema-validation/spec/fixtures/structure.sql @@ -19,7 +19,8 @@ CREATE TABLE test_table ( numeric_column numeric NOT NULL, numeric_with_default_column numeric DEFAULT 1.0 NOT NULL, boolean_colum boolean, - boolean_with_default_colum boolean DEFAULT true NOT NULL, + boolean_with_default_column_true boolean DEFAULT true NOT NULL, + boolean_with_default_column_false boolean DEFAULT false NOT NULL, double_precision_column double precision, double_precision_with_default_column double precision DEFAULT 1.0, varying_column character varying, diff --git a/gems/gitlab-schema-validation/spec/lib/gitlab/schema/validation/adapters/column_structure_sql_adapter_spec.rb b/gems/gitlab-schema-validation/spec/lib/gitlab/schema/validation/adapters/column_structure_sql_adapter_spec.rb index ae0d635e8ca..1c44db5286a 100644 --- a/gems/gitlab-schema-validation/spec/lib/gitlab/schema/validation/adapters/column_structure_sql_adapter_spec.rb +++ b/gems/gitlab-schema-validation/spec/lib/gitlab/schema/validation/adapters/column_structure_sql_adapter_spec.rb @@ -21,7 +21,8 @@ RSpec.describe Gitlab::Schema::Validation::Adapters::ColumnStructureSqlAdapter, ['smallint_with_default_column', 'smallint', 'DEFAULT 0', 'NOT NULL', false], ['double_precision_with_default_column', 'double precision', 'DEFAULT 1.0', nil, false], ['numeric_with_default_column', 'numeric', 'DEFAULT 1.0', 'NOT NULL', false], - ['boolean_with_default_colum', 'boolean', 'DEFAULT true', 'NOT NULL', false], + ['boolean_with_default_column_true', 'boolean', 'DEFAULT true', 'NOT NULL', false], + ['boolean_with_default_column_false', 'boolean', 'DEFAULT false', 'NOT NULL', false], ['varying_with_default_column', 'character varying', "DEFAULT 'DEFAULT'::character varying", 'NOT NULL', false], ['varying_with_limit_and_default_column', 'character varying(255)', "DEFAULT 'DEFAULT'::character varying", nil, false], diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb index a82aed507fd..1a23dcd0d3c 100644 --- a/lib/api/helpers/members_helpers.rb +++ b/lib/api/helpers/members_helpers.rb @@ -22,6 +22,14 @@ module API authorize! :"read_#{source_type}_member", source end + def authorize_admin_source_member!(source_type, source) + authorize! :"admin_#{source_type}_member", source + end + + def authorize_update_source_member!(source_type, member) + authorize! :"update_#{source_type}_member", member + end + def authorize_admin_source!(source_type, source) authorize! :"admin_#{source_type}", source end diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb index dff7b5243aa..34f9538b047 100644 --- a/lib/api/invitations.rb +++ b/lib/api/invitations.rb @@ -33,7 +33,12 @@ module API bad_request!('Must provide either email or user_id as a parameter') if params[:email].blank? && params[:user_id].blank? source = find_source(source_type, params[:id]) - authorize_admin_source!(source_type, source) + + if ::Feature.enabled?(:admin_group_member, source) + authorize_admin_source_member!(source_type, source) + else + authorize_admin_source!(source_type, source) + end create_service_params = params.merge(source: source) @@ -56,7 +61,11 @@ module API source = find_source(source_type, params[:id]) query = params[:query] - authorize_admin_source!(source_type, source) + if ::Feature.enabled?(:admin_group_member, source) + authorize_admin_source_member!(source_type, source) + else + authorize_admin_source!(source_type, source) + end invitations = paginate(retrieve_member_invitations(source, query)) @@ -75,7 +84,12 @@ module API put ":id/invitations/:email", requirements: { email: %r{[^/]+} } do source = find_source(source_type, params.delete(:id)) invite_email = params[:email] - authorize_admin_source!(source_type, source) + + if ::Feature.enabled?(:admin_group_member, source) + authorize_admin_source_member!(source_type, source) + else + authorize_admin_source!(source_type, source) + end invite = retrieve_member_invitations(source, invite_email).first not_found! unless invite @@ -112,7 +126,12 @@ module API delete ":id/invitations/:email", requirements: { email: %r{[^/]+} } do source = find_source(source_type, params[:id]) invite_email = params[:email] - authorize_admin_source!(source_type, source) + + if ::Feature.enabled?(:admin_group_member, source) + authorize_admin_source_member!(source_type, source) + else + authorize_admin_source!(source_type, source) + end invite = retrieve_member_invitations(source, invite_email).first not_found! unless invite diff --git a/lib/api/members.rb b/lib/api/members.rb index 6dc4e45ffc1..bdbdea70da0 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -118,7 +118,11 @@ module API post ":id/members", feature_category: feature_category do source = find_source(source_type, params[:id]) - authorize_admin_source!(source_type, source) + if ::Feature.enabled?(:admin_group_member, source) + authorize_admin_source_member!(source_type, source) + else + authorize_admin_source!(source_type, source) + end create_service_params = params.merge(source: source) @@ -142,10 +146,14 @@ module API # rubocop: disable CodeReuse/ActiveRecord put ":id/members/:user_id", feature_category: feature_category do source = find_source(source_type, params.delete(:id)) - authorize_admin_source!(source_type, source) - member = source_members(source).find_by!(user_id: params[:user_id]) + if ::Feature.enabled?(:admin_group_member, source) + authorize_update_source_member!(source_type, member) + else + authorize_admin_source!(source_type, source) + end + result = ::Members::UpdateService .new(current_user, declared_params(include_missing: false)) .execute(member) diff --git a/lib/banzai/filter/math_filter.rb b/lib/banzai/filter/math_filter.rb index e568f51652f..3161e030194 100644 --- a/lib/banzai/filter/math_filter.rb +++ b/lib/banzai/filter/math_filter.rb @@ -47,7 +47,7 @@ module Banzai # Add necessary classes to any existing math blocks def process_existing doc.xpath(XPATH_INLINE_CODE).each do |code| - break if @nodes_count >= RENDER_NODES_LIMIT + break if render_nodes_limit_reached?(@nodes_count) code[:class] = MATH_CLASSES @@ -58,7 +58,7 @@ module Banzai # Corresponds to the "$`...`$" syntax def process_dollar_backtick_inline doc.xpath(XPATH_CODE).each do |code| - break if @nodes_count >= RENDER_NODES_LIMIT + break if render_nodes_limit_reached?(@nodes_count) closing = code.next opening = code.previous @@ -87,6 +87,16 @@ module Banzai pre_node[:class] = TAG_CLASS end end + + def settings + Gitlab::CurrentSettings.current_application_settings + end + + def render_nodes_limit_reached?(count) + return false unless settings.math_rendering_limits_enabled? + + count >= RENDER_NODES_LIMIT + end end end end diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index a7f8b93ee10..168f3aa4c59 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -7,15 +7,16 @@ module Gitlab include WebpackHelper def add_gon_variables - gon.api_version = 'v4' - gon.default_avatar_url = default_avatar_url - gon.max_file_size = Gitlab::CurrentSettings.max_attachment_size - gon.asset_host = ActionController::Base.asset_host - gon.webpack_public_path = webpack_public_path - gon.relative_url_root = Gitlab.config.gitlab.relative_url_root - gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class - gon.markdown_surround_selection = current_user&.markdown_surround_selection - gon.markdown_automatic_lists = current_user&.markdown_automatic_lists + gon.api_version = 'v4' + gon.default_avatar_url = default_avatar_url + gon.max_file_size = Gitlab::CurrentSettings.max_attachment_size + gon.asset_host = ActionController::Base.asset_host + gon.webpack_public_path = webpack_public_path + gon.relative_url_root = Gitlab.config.gitlab.relative_url_root + gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class + gon.markdown_surround_selection = current_user&.markdown_surround_selection + gon.markdown_automatic_lists = current_user&.markdown_automatic_lists + gon.math_rendering_limits_enabled = Gitlab::CurrentSettings.math_rendering_limits_enabled add_browsersdk_tracking diff --git a/locale/gitlab.pot b/locale/gitlab.pot index bb9f7e019ff..fcd8c86969e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -35123,13 +35123,13 @@ msgstr "" msgid "Pipeline|This change will not change the overall test coverage if merged." msgstr "" -msgid "Pipeline|This pipeline ran on the contents of this merge request combined with the contents of all other merge requests queued for merging into the target branch." +msgid "Pipeline|This pipeline ran on the contents of the merge request combined with the contents of all other merge requests queued for merging into the target branch." msgstr "" -msgid "Pipeline|This pipeline ran on the contents of this merge request combined with the contents of the target branch." +msgid "Pipeline|This pipeline ran on the contents of the merge request combined with the contents of the target branch." msgstr "" -msgid "Pipeline|This pipeline ran on the contents of this merge request's source branch, not the target branch." +msgid "Pipeline|This pipeline ran on the contents of the merge request's source branch, not the target branch." msgstr "" msgid "Pipeline|To run a merge request pipeline, the jobs in the CI/CD configuration file %{linkStart}must be configured%{linkEnd} to run in merge request pipelines." diff --git a/rubocop/cop/gemfile/missing_feature_category.rb b/rubocop/cop/gemfile/missing_feature_category.rb index 323900efd84..18e29fc30b2 100644 --- a/rubocop/cop/gemfile/missing_feature_category.rb +++ b/rubocop/cop/gemfile/missing_feature_category.rb @@ -38,19 +38,11 @@ module RuboCop value_node = feature_category_value(node) - if value_node - feature_categories.check( - value_node: value_node, - document_link: DOCUMENT_LINK - ) do |message| - add_offense(value_node, message: message) - end - else - message = format( - FeatureCategories::MSG, - msg_suggestion: nil, - document_link: DOCUMENT_LINK) - add_offense(node, message: message) + feature_categories.check( + value_node: value_node, + document_link: DOCUMENT_LINK + ) do |message| + add_offense(value_node || node, message: message) end end diff --git a/rubocop/cop/rspec/invalid_feature_category.rb b/rubocop/cop/rspec/feature_category.rb index 27e28940df2..a247bd807b0 100644 --- a/rubocop/cop/rspec/invalid_feature_category.rb +++ b/rubocop/cop/rspec/feature_category.rb @@ -8,12 +8,17 @@ require_relative '../../feature_categories' module RuboCop module Cop module RSpec - # Ensures that feature categories in specs are valid. + # Ensures that feature categories in specs are present and valid. # # @example # # # bad + # RSpec.describe 'foo' do + # end + # # RSpec.describe 'foo', feature_category: :invalid do + # context 'a context', feature_category: :aip do + # end # end # # RSpec.describe 'foo', feature_category: :not_owned do @@ -22,12 +27,16 @@ module RuboCop # # good # # RSpec.describe 'foo', feature_category: :wiki do + # context 'a context', feature_category: :api do + # end # end # # RSpec.describe 'foo', feature_category: :tooling do # end # - class InvalidFeatureCategory < RuboCop::Cop::RSpec::Base + class FeatureCategory < RuboCop::Cop::RSpec::Base + include RuboCop::Cop::RSpec::TopLevelGroup + DOCUMENT_LINK = 'https://docs.gitlab.com/ee/development/feature_categorization/#rspec-examples' # @!method feature_category?(node) @@ -40,24 +49,32 @@ module RuboCop ) PATTERN + def on_top_level_example_group(node) + check_feature_category(node, optional: false) + end + def on_block(node) + check_feature_category(node, optional: true) + end + + def external_dependency_checksum + FeatureCategories.config_checksum + end + + private + + def check_feature_category(node, optional:) value_node = feature_category_value(node) - return unless value_node + return if optional && !value_node feature_categories.check( value_node: value_node, document_link: DOCUMENT_LINK ) do |message| - add_offense(value_node, message: message) + add_offense(value_node || node, message: message) end end - def external_dependency_checksum - FeatureCategories.config_checksum - end - - private - def feature_categories @feature_categories ||= FeatureCategories.new(FeatureCategories.available_with_custom) diff --git a/rubocop/cop/rspec/missing_feature_category.rb b/rubocop/cop/rspec/missing_feature_category.rb deleted file mode 100644 index 0f517473982..00000000000 --- a/rubocop/cop/rspec/missing_feature_category.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -require 'rubocop/cop/rspec/base' -require 'rubocop/cop/rspec/mixin/top_level_group' - -module RuboCop - module Cop - module RSpec - # Ensures that top level example group contains a feature category. - # - # @example - # - # # bad - # RSpec.describe 'foo' do - # end - # - # # good - # RSpec.describe 'foo', feature_category: :wiki do - # end - class MissingFeatureCategory < RuboCop::Cop::RSpec::Base - include RuboCop::Cop::RSpec::TopLevelGroup - - MSG = 'Please add missing feature category. ' \ - 'See https://docs.gitlab.com/ee/development/feature_categorization/#rspec-examples.' - - # @!method feature_category?(node) - def_node_matcher :feature_category?, <<~PATTERN - (block - (send ... - (hash <(pair (sym :feature_category) _) ...>) - ) - ... - ) - PATTERN - - def on_top_level_example_group(node) - return if feature_category?(node) - - add_offense(node.children.first) - end - end - end - end -end diff --git a/rubocop/feature_categories.rb b/rubocop/feature_categories.rb index 3aa8a50839c..5e02d974e7b 100644 --- a/rubocop/feature_categories.rb +++ b/rubocop/feature_categories.rb @@ -47,21 +47,27 @@ module RuboCop end def check(value_node:, document_link:) - return yield(MSG_SYMBOL) unless value_node.sym_type? - return if categories.include?(value_node.value.to_s) + if value_node + if !value_node.sym_type? + yield MSG_SYMBOL + elsif !categories.include?(value_node.value.to_s) # rubocop:disable Rails/NegateInclude + yield format_message(value_node.value, document_link: document_link) + end + else + yield format_message(nil, document_link: document_link) + end + end - message = format( - MSG, - msg_suggestion: suggestion_message(value_node), - document_link: document_link) + private - yield(message) + def format_message(value, document_link:) + format(MSG, msg_suggestion: suggestion_message(value), document_link: document_link) end - def suggestion_message(value_node) + def suggestion_message(value) spell = DidYouMean::SpellChecker.new(dictionary: categories) - suggestions = spell.correct(value_node.value) + suggestions = spell.correct(value) return if suggestions.none? format(MSG_DID_YOU_MEAN, suggestion: suggestions.first) diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 2300e81cb55..4c148f8e776 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -166,36 +166,36 @@ RSpec.describe 'Admin updates settings', feature_category: :shared do end it 'changes dormant users', :js do - expect(page).to have_unchecked_field('Deactivate dormant users after a period of inactivity') + expect(page).to have_unchecked_field(_('Deactivate dormant users after a period of inactivity')) expect(current_settings.deactivate_dormant_users).to be_falsey page.within(find('[data-testid="account-limit"]')) do - check 'application_setting_deactivate_dormant_users' - click_button 'Save changes' + check _('Deactivate dormant users after a period of inactivity') + click_button _('Save changes') end - expect(page).to have_content "Application settings saved successfully" + expect(page).to have_content _('Application settings saved successfully') page.refresh + expect(page).to have_checked_field(_('Deactivate dormant users after a period of inactivity')) expect(current_settings.deactivate_dormant_users).to be_truthy - expect(page).to have_checked_field('Deactivate dormant users after a period of inactivity') end it 'change dormant users period', :js do expect(page).to have_field(_('Days of inactivity before deactivation'), disabled: true) page.within(find('[data-testid="account-limit"]')) do - check 'application_setting_deactivate_dormant_users' - fill_in _('application_setting_deactivate_dormant_users_period'), with: '90' - click_button 'Save changes' + check _('Deactivate dormant users after a period of inactivity') + fill_in _('Days of inactivity before deactivation'), with: '180' + click_button _('Save changes') end - expect(page).to have_content "Application settings saved successfully" + expect(page).to have_content _('Application settings saved successfully') page.refresh - expect(page).to have_field _('Days of inactivity before deactivation'), with: '90' + expect(page).to have_field(_('Days of inactivity before deactivation'), disabled: false, with: '180') end it 'displays dormant users period field validation error', :js do diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb index c503c18be8d..2095453ac29 100644 --- a/spec/features/issues/todo_spec.rb +++ b/spec/features/issues/todo_spec.rb @@ -8,6 +8,7 @@ RSpec.describe 'Manually create a todo item from issue', :js, feature_category: let!(:user) { create(:user, :no_super_sidebar) } before do + stub_feature_flags(notifications_todos_buttons: false) project.add_maintainer(user) sign_in(user) visit project_issue_path(project, issue) diff --git a/spec/features/issues/user_toggles_subscription_spec.rb b/spec/features/issues/user_toggles_subscription_spec.rb index af8a31afd5f..713a169f061 100644 --- a/spec/features/issues/user_toggles_subscription_spec.rb +++ b/spec/features/issues/user_toggles_subscription_spec.rb @@ -11,6 +11,7 @@ RSpec.describe "User toggles subscription", :js, feature_category: :team_plannin context 'user is not logged in' do before do stub_feature_flags(moved_mr_sidebar: false) + stub_feature_flags(notifications_todos_buttons: false) visit(project_issue_path(project, issue)) end @@ -22,6 +23,7 @@ RSpec.describe "User toggles subscription", :js, feature_category: :team_plannin context 'user is logged in' do before do stub_feature_flags(moved_mr_sidebar: false) + stub_feature_flags(notifications_todos_buttons: false) project.add_developer(user) sign_in(user) visit(project_issue_path(project, issue)) @@ -54,6 +56,7 @@ RSpec.describe "User toggles subscription", :js, feature_category: :team_plannin context 'user is logged in without edit permission' do before do stub_feature_flags(moved_mr_sidebar: false) + stub_feature_flags(notifications_todos_buttons: false) sign_in(user2) visit(project_issue_path(project, issue)) @@ -73,4 +76,24 @@ RSpec.describe "User toggles subscription", :js, feature_category: :team_plannin expect(subscription_button).to have_css("button.is-checked") end end + + context 'with notifications_todos_buttons feature flag enabled' do + before do + stub_feature_flags(moved_mr_sidebar: true) + stub_feature_flags(notifications_todos_buttons: true) + sign_in(user2) + + visit(project_issue_path(project, issue)) + end + + it 'toggles subscription' do + subscription_button = find('[data-testid="subscribe-button"]') + + expect(page).to have_selector("button[title='Notifications off']") + subscription_button.click + wait_for_requests + + expect(page).to have_selector("button[title='Notifications on']") + end + end end diff --git a/spec/features/markdown/math_spec.rb b/spec/features/markdown/math_spec.rb index 0bc8f2146e9..0d12aade807 100644 --- a/spec/features/markdown/math_spec.rb +++ b/spec/features/markdown/math_spec.rb @@ -48,47 +48,92 @@ RSpec.describe 'Math rendering', :js, feature_category: :team_planning do end end - it 'renders lazy load button' do - description = <<~MATH - ```math - \Huge \sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} - ``` - MATH - - create_and_visit_issue_with_description(description) + describe 'applying limits on math rendering' do + let(:lazy_load_description) do + <<~MATH + ```math + \Huge \sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} + ``` + MATH + end - page.within '.description > .md' do - expect(page).to have_selector('.js-lazy-render-math-container', text: /math block exceeds 1000 characters/) + let(:excessive_expansion_description) do + <<~MATH + ```math + #{'\\mod e ' * 150} + ``` + MATH end - end - it 'allows many expansions', :js do - description = <<~MATH - ```math - #{'\\mod e ' * 100} - ``` - MATH + context 'when limits should be applied (default)' do + it 'renders lazy load button' do + create_and_visit_issue_with_description(lazy_load_description) - create_and_visit_issue_with_description(description) + page.within '.description > .md' do + expect(page).to have_selector('.js-lazy-render-math-container', text: /math block exceeds 1000 characters/) + end + end - page.within '.description > .md' do - expect(page).not_to have_selector('.katex-error') + it 'allows many expansions', :js do + description = <<~MATH + ```math + #{'\\mod e ' * 100} + ``` + MATH + + create_and_visit_issue_with_description(description) + + page.within '.description > .md' do + expect(page).not_to have_selector('.katex-error') + end + end + + it 'shows error message when too many expansions', :js do + create_and_visit_issue_with_description(excessive_expansion_description) + + page.within '.description > .md' do + click_button 'Display anyway' + + expect(page).to have_selector('.katex-error', text: /Too many expansions/) + end + end + + it 'renders without any limits on wiki page', :js do + wiki_page = build(:wiki_page, { container: project, content: lazy_load_description }) + wiki_page.create message: 'math test commit' # rubocop:disable Rails/SaveBang + wiki_page = project.wiki.find_page(wiki_page.slug) + + visit project_wiki_path(project, wiki_page) + + wait_for_requests + + page.within '.js-wiki-page-content' do + expect(page).not_to have_selector('.js-lazy-render-math') + end + end end - end - it 'shows error message when too many expansions', :js do - description = <<~MATH - ```math - #{'\\mod e ' * 150} - ``` - MATH + context 'when limits are disabled' do + before do + stub_application_setting(math_rendering_limits_enabled: false) + end - create_and_visit_issue_with_description(description) + it 'does not render lazy load button' do + create_and_visit_issue_with_description(lazy_load_description) - page.within '.description > .md' do - click_button 'Display anyway' + page.within '.description > .md' do + expect(page) + .not_to have_selector('.js-lazy-render-math-container', text: /math block exceeds 1000 characters/) + end + end - expect(page).to have_selector('.katex-error', text: /Too many expansions/) + it 'does not show error message when too many expansions', :js do + create_and_visit_issue_with_description(excessive_expansion_description) + + page.within '.description > .md' do + expect(page).not_to have_selector('.katex-error', text: /Too many expansions/) + end + end end end @@ -121,26 +166,6 @@ RSpec.describe 'Math rendering', :js, feature_category: :team_planning do end end - it 'renders without any limits on wiki page', :js do - description = <<~MATH - ```math - \Huge \sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} - ``` - MATH - - wiki_page = build(:wiki_page, { container: project, content: description }) - wiki_page.create message: 'math test commit' # rubocop:disable Rails/SaveBang - wiki_page = project.wiki.find_page(wiki_page.slug) - - visit project_wiki_path(project, wiki_page) - - wait_for_requests - - page.within '.js-wiki-page-content' do - expect(page).not_to have_selector('.js-lazy-render-math') - end - end - it 'uses math-content-display for display math', :js do description = <<~MATH ```math diff --git a/spec/fixtures/structure.sql b/spec/fixtures/structure.sql deleted file mode 100644 index 421fb6c3593..00000000000 --- a/spec/fixtures/structure.sql +++ /dev/null @@ -1,108 +0,0 @@ -CREATE INDEX missing_index ON events USING btree (created_at, author_id); - -CREATE UNIQUE INDEX wrong_index ON table_name (column_name, column_name_2); - -CREATE UNIQUE INDEX "index" ON achievements USING btree (namespace_id, lower(name)); - -CREATE INDEX index_namespaces_public_groups_name_id ON namespaces USING btree (name, id) WHERE (((type)::text = 'Group'::text) AND (visibility_level = 20)); - -CREATE UNIQUE INDEX index_on_deploy_keys_id_and_type_and_public ON keys USING btree (id, type) WHERE (public = true); - -CREATE INDEX index_users_on_public_email_excluding_null_and_empty ON users USING btree (public_email) WHERE (((public_email)::text <> ''::text) AND (public_email IS NOT NULL)); - -CREATE TABLE test_table ( - id bigint NOT NULL, - integer_column integer, - integer_with_default_column integer DEFAULT 1, - smallint_column smallint, - smallint_with_default_column smallint DEFAULT 0 NOT NULL, - numeric_column numeric NOT NULL, - numeric_with_default_column numeric DEFAULT 1.0 NOT NULL, - boolean_colum boolean, - boolean_with_default_colum boolean DEFAULT true NOT NULL, - double_precision_column double precision, - double_precision_with_default_column double precision DEFAULT 1.0, - varying_column character varying, - varying_with_default_column character varying DEFAULT 'DEFAULT'::character varying NOT NULL, - varying_with_limit_column character varying(255), - varying_with_limit_and_default_column character varying(255) DEFAULT 'DEFAULT'::character varying, - text_column text NOT NULL, - text_with_default_column text DEFAULT ''::text NOT NULL, - array_column character varying(255)[] NOT NULL, - array_with_default_column character varying(255)[] DEFAULT '{one,two}'::character varying[] NOT NULL, - jsonb_column jsonb, - jsonb_with_default_column jsonb DEFAULT '[]'::jsonb NOT NULL, - timestamptz_column timestamp with time zone, - timestamptz_with_default_column timestamp(6) with time zone DEFAULT now(), - timestamp_column timestamp(6) without time zone NOT NULL, - timestamp_with_default_column timestamp(6) without time zone DEFAULT '2022-01-23 00:00:00+00'::timestamp without time zone NOT NULL, - date_column date, - date_with_default_column date DEFAULT '2023-04-05', - inet_column inet NOT NULL, - inet_with_default_column inet DEFAULT '0.0.0.0'::inet NOT NULL, - macaddr_column macaddr, - macaddr_with_default_column macaddr DEFAULT '00-00-00-00-00-000'::macaddr NOT NULL, - uuid_column uuid NOT NULL, - uuid_with_default_column uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, - bytea_column bytea, - bytea_with_default_column bytea DEFAULT '\xDEADBEEF'::bytea, - unmapped_column_type anyarray, - partition_key bigint DEFAULT 1 NOT NULL, - created_at timestamp with time zone DEFAULT now() NOT NULL -) PARTITION BY HASH (partition_key, created_at); - -CREATE TABLE ci_project_mirrors ( - id bigint NOT NULL, - project_id integer NOT NULL, - namespace_id integer NOT NULL -); - -CREATE TABLE wrong_table ( - id bigint NOT NULL, - description character varying(255) NOT NULL -); - -CREATE TABLE extra_table_columns ( - id bigint NOT NULL, - name character varying(255) NOT NULL -); - -CREATE TABLE missing_table ( - id bigint NOT NULL, - description text NOT NULL -); - -CREATE TABLE missing_table_columns ( - id bigint NOT NULL, - email character varying(255) NOT NULL -); - -CREATE TABLE operations_user_lists ( - id bigint NOT NULL, - project_id bigint NOT NULL, - created_at timestamp with time zone NOT NULL, - updated_at timestamp with time zone NOT NULL, - iid integer NOT NULL, - name character varying(255) NOT NULL, - user_xids text DEFAULT ''::text NOT NULL -); - -CREATE TRIGGER trigger AFTER INSERT ON public.t1 FOR EACH ROW EXECUTE FUNCTION t1(); - -CREATE TRIGGER wrong_trigger BEFORE UPDATE ON public.t2 FOR EACH ROW EXECUTE FUNCTION my_function(); - -CREATE TRIGGER missing_trigger_1 BEFORE INSERT OR UPDATE ON public.t3 FOR EACH ROW EXECUTE FUNCTION t3(); - -CREATE TRIGGER projects_loose_fk_trigger AFTER DELETE ON projects REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records(); - -ALTER TABLE web_hooks - ADD CONSTRAINT web_hooks_project_id_fkey FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; - -ALTER TABLE ONLY issues - ADD CONSTRAINT wrong_definition_fk FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL; - -ALTER TABLE ONLY issues - ADD CONSTRAINT missing_fk FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL; - -ALTER TABLE ONLY bulk_import_configurations - ADD CONSTRAINT fk_rails_536b96bff1 FOREIGN KEY (bulk_import_id) REFERENCES bulk_imports(id) ON DELETE CASCADE; diff --git a/spec/frontend/ci/pipelines_page/components/pipeline_labels_spec.js b/spec/frontend/ci/pipelines_page/components/pipeline_labels_spec.js index 2468316d3fc..6b0d5b18f7d 100644 --- a/spec/frontend/ci/pipelines_page/components/pipeline_labels_spec.js +++ b/spec/frontend/ci/pipelines_page/components/pipeline_labels_spec.js @@ -168,6 +168,8 @@ describe('Pipeline label component', () => { it('should render the train badge when the pipeline is a merge train pipeline', () => { const mergeTrainPipeline = defaultProps.pipeline; mergeTrainPipeline.flags.merge_train_pipeline = true; + // a merge train pipeline is also a merged results pipeline + mergeTrainPipeline.flags.merged_result_pipeline = true; createComponent({ ...mergeTrainPipeline, @@ -186,4 +188,17 @@ describe('Pipeline label component', () => { expect(findTrainTag().exists()).toBe(false); }); + + it('should not render the merged results badge when the pipeline is a merge train pipeline', () => { + const mergeTrainPipeline = defaultProps.pipeline; + mergeTrainPipeline.flags.merge_train_pipeline = true; + // a merge train pipeline is also a merged results pipeline + mergeTrainPipeline.flags.merged_result_pipeline = true; + + createComponent({ + ...mergeTrainPipeline, + }); + + expect(findMergedResultsTag().exists()).toBe(false); + }); }); diff --git a/spec/lib/banzai/filter/math_filter_spec.rb b/spec/lib/banzai/filter/math_filter_spec.rb index e4ebebc0fde..3fa0f9028e8 100644 --- a/spec/lib/banzai/filter/math_filter_spec.rb +++ b/spec/lib/banzai/filter/math_filter_spec.rb @@ -207,12 +207,21 @@ RSpec.describe Banzai::Filter::MathFilter, feature_category: :team_planning do expect(doc.search('[data-math-style="display"]').count).to eq(1) end - it 'limits how many elements can be marked as math' do - stub_const('Banzai::Filter::MathFilter::RENDER_NODES_LIMIT', 2) + context 'when limiting how many elements can be marked as math' do + subject { pipeline_filter('$`2+2`$ + $3+3$ + $$4+4$$') } - doc = pipeline_filter('$`2+2`$ + $3+3$ + $$4+4$$') + it 'enforces limits by default' do + stub_const('Banzai::Filter::MathFilter::RENDER_NODES_LIMIT', 2) + + expect(subject.search('.js-render-math').count).to eq(2) + end - expect(doc.search('.js-render-math').count).to eq(2) + it 'does not limit when math_rendering_limits_enabled is false' do + stub_application_setting(math_rendering_limits_enabled: false) + stub_const('Banzai::Filter::MathFilter::RENDER_NODES_LIMIT', 2) + + expect(subject.search('.js-render-math').count).to eq(3) + end end it 'protects against malicious backtracking' do diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 52aa6085722..78bf410075b 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -297,6 +297,9 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do it { is_expected.to validate_inclusion_of(:package_registry_allow_anyone_to_pull_option).in_array([true, false]) } + it { is_expected.to allow_value([true, false]).for(:math_rendering_limits_enabled) } + it { is_expected.not_to allow_value(nil).for(:math_rendering_limits_enabled) } + context 'when deactivate_dormant_users is enabled' do before do stub_application_setting(deactivate_dormant_users: true) diff --git a/spec/presenters/member_presenter_spec.rb b/spec/presenters/member_presenter_spec.rb index 7850399b711..7223c98d5f7 100644 --- a/spec/presenters/member_presenter_spec.rb +++ b/spec/presenters/member_presenter_spec.rb @@ -2,13 +2,44 @@ require 'spec_helper' +# Creation is necessary due to relations and the need to check in the presenter +# +# rubocop:disable RSpec/FactoryBot/AvoidCreate RSpec.describe MemberPresenter, feature_category: :groups_and_projects do - let_it_be(:member) { build(:group_member) } - let(:presenter) { described_class.new(member) } + let_it_be(:root_group) { create(:group) } + let_it_be(:subgroup) { create(:group, parent: root_group) } + let_it_be(:user) { create(:user) } + + let_it_be(:root_member) { create(:group_member, :reporter, group: root_group, user: user) } + let_it_be(:subgroup_member) { create(:group_member, :reporter, group: subgroup, user: user) } + + let(:presenter) { described_class.new(root_member) } describe '#last_owner?' do it 'raises `NotImplementedError`' do expect { presenter.last_owner? }.to raise_error(NotImplementedError) end end + + describe '#valid_level_roles' do + it 'does not return levels lower than user highest membership in the hierarchy' do + expect(described_class.new(subgroup_member).valid_level_roles).to eq( + 'Reporter' => Gitlab::Access::REPORTER, + 'Developer' => Gitlab::Access::DEVELOPER, + 'Maintainer' => Gitlab::Access::MAINTAINER, + 'Owner' => Gitlab::Access::OWNER + ) + end + + it 'returns all roles for the root group' do + expect(described_class.new(root_member).valid_level_roles).to eq( + 'Guest' => Gitlab::Access::GUEST, + 'Reporter' => Gitlab::Access::REPORTER, + 'Developer' => Gitlab::Access::DEVELOPER, + 'Maintainer' => Gitlab::Access::MAINTAINER, + 'Owner' => Gitlab::Access::OWNER + ) + end + end end +# rubocop:enable RSpec/FactoryBot/AvoidCreate diff --git a/spec/requests/api/invitations_spec.rb b/spec/requests/api/invitations_spec.rb index 4eecf421add..dc02e830027 100644 --- a/spec/requests/api/invitations_spec.rb +++ b/spec/requests/api/invitations_spec.rb @@ -379,8 +379,24 @@ RSpec.describe API::Invitations, feature_category: :user_profile do end describe 'POST /projects/:id/invitations' do - it_behaves_like 'POST /:source_type/:id/invitations', 'project' do - let(:source) { project } + context 'with admin_group_member FF disabled' do + before do + stub_feature_flags(admin_group_member: false) + end + + it_behaves_like 'POST /:source_type/:id/invitations', 'project' do + let(:source) { project } + end + end + + context 'with admin_group_member FF enabled' do + before do + stub_feature_flags(admin_group_member: true) + end + + it_behaves_like 'POST /:source_type/:id/invitations', 'project' do + let(:source) { project } + end end it 'does not exceed expected queries count for emails', :request_store, :use_sql_query_cache do @@ -442,8 +458,24 @@ RSpec.describe API::Invitations, feature_category: :user_profile do end describe 'POST /groups/:id/invitations' do - it_behaves_like 'POST /:source_type/:id/invitations', 'group' do - let(:source) { group } + context 'with admin_group_member FF disabled' do + before do + stub_feature_flags(admin_group_member: false) + end + + it_behaves_like 'POST /:source_type/:id/invitations', 'group' do + let(:source) { group } + end + end + + context 'with admin_group_member FF enabled' do + before do + stub_feature_flags(admin_group_member: true) + end + + it_behaves_like 'POST /:source_type/:id/invitations', 'group' do + let(:source) { group } + end end it 'does not exceed expected queries count for emails', :request_store, :use_sql_query_cache do @@ -555,14 +587,46 @@ RSpec.describe API::Invitations, feature_category: :user_profile do end describe 'GET /projects/:id/invitations' do - it_behaves_like 'GET /:source_type/:id/invitations', 'project' do - let(:source) { project } + context 'with admin_group_member FF disabled' do + before do + stub_feature_flags(admin_group_member: false) + end + + it_behaves_like 'GET /:source_type/:id/invitations', 'project' do + let(:source) { project } + end + end + + context 'with admin_group_member FF enabled' do + before do + stub_feature_flags(admin_group_member: true) + end + + it_behaves_like 'GET /:source_type/:id/invitations', 'project' do + let(:source) { project } + end end end describe 'GET /groups/:id/invitations' do - it_behaves_like 'GET /:source_type/:id/invitations', 'group' do - let(:source) { group } + context 'with admin_group_member FF disabled' do + before do + stub_feature_flags(admin_group_member: false) + end + + it_behaves_like 'GET /:source_type/:id/invitations', 'group' do + let(:source) { group } + end + end + + context 'with admin_group_member FF enabled' do + before do + stub_feature_flags(admin_group_member: true) + end + + it_behaves_like 'GET /:source_type/:id/invitations', 'group' do + let(:source) { group } + end end end @@ -648,14 +712,46 @@ RSpec.describe API::Invitations, feature_category: :user_profile do end describe 'DELETE /projects/:id/inviations/:email' do - it_behaves_like 'DELETE /:source_type/:id/invitations/:email', 'project' do - let(:source) { project } + context 'with admin_group_member FF disabled' do + before do + stub_feature_flags(admin_group_member: false) + end + + it_behaves_like 'DELETE /:source_type/:id/invitations/:email', 'project' do + let(:source) { project } + end + end + + context 'with admin_group_member FF enabled' do + before do + stub_feature_flags(admin_group_member: true) + end + + it_behaves_like 'DELETE /:source_type/:id/invitations/:email', 'project' do + let(:source) { project } + end end end describe 'DELETE /groups/:id/inviations/:email' do - it_behaves_like 'DELETE /:source_type/:id/invitations/:email', 'group' do - let(:source) { group } + context 'with admin_group_member FF disabled' do + before do + stub_feature_flags(admin_group_member: false) + end + + it_behaves_like 'DELETE /:source_type/:id/invitations/:email', 'group' do + let(:source) { group } + end + end + + context 'with admin_group_member FF enabled' do + before do + stub_feature_flags(admin_group_member: true) + end + + it_behaves_like 'DELETE /:source_type/:id/invitations/:email', 'group' do + let(:source) { group } + end end end @@ -764,14 +860,26 @@ RSpec.describe API::Invitations, feature_category: :user_profile do end describe 'PUT /projects/:id/invitations' do - it_behaves_like 'PUT /:source_type/:id/invitations/:email', 'project' do - let(:source) { project } + context 'with admin_group_member FF disabled' do + before do + stub_feature_flags(admin_group_member: false) + end + + it_behaves_like 'PUT /:source_type/:id/invitations/:email', 'project' do + let(:source) { project } + end end end describe 'PUT /groups/:id/invitations' do - it_behaves_like 'PUT /:source_type/:id/invitations/:email', 'group' do - let(:source) { group } + context 'with admin_group_member FF enabled' do + before do + stub_feature_flags(admin_group_member: true) + end + + it_behaves_like 'PUT /:source_type/:id/invitations/:email', 'group' do + let(:source) { group } + end end end end diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index 07e3e642b85..8dab9d555cf 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -803,10 +803,6 @@ RSpec.describe API::Members, feature_category: :groups_and_projects do end describe 'POST /projects/:id/members' do - it_behaves_like 'POST /:source_type/:id/members', 'project' do - let(:source) { project } - end - context 'adding owner to project' do it_behaves_like 'a 403 response when user does not have rights to manage members of a specific access level' do let(:route) do @@ -830,16 +826,48 @@ RSpec.describe API::Members, feature_category: :groups_and_projects do end end - it_behaves_like 'POST /:source_type/:id/members', 'group' do - let(:source) { group } - end + context 'with admin_group_member FF disabled' do + before do + stub_feature_flags(admin_group_member: false) + end - it_behaves_like 'PUT /:source_type/:id/members/:user_id', 'project' do - let(:source) { project } + it_behaves_like 'POST /:source_type/:id/members', 'project' do + let(:source) { project } + end + + it_behaves_like 'POST /:source_type/:id/members', 'group' do + let(:source) { group } + end + + it_behaves_like 'PUT /:source_type/:id/members/:user_id', 'project' do + let(:source) { project } + end + + it_behaves_like 'PUT /:source_type/:id/members/:user_id', 'group' do + let(:source) { group } + end end - it_behaves_like 'PUT /:source_type/:id/members/:user_id', 'group' do - let(:source) { group } + context 'with admin_group_member FF enabled' do + before do + stub_feature_flags(admin_group_member: true) + end + + it_behaves_like 'POST /:source_type/:id/members', 'project' do + let(:source) { project } + end + + it_behaves_like 'POST /:source_type/:id/members', 'group' do + let(:source) { group } + end + + it_behaves_like 'PUT /:source_type/:id/members/:user_id', 'project' do + let(:source) { project } + end + + it_behaves_like 'PUT /:source_type/:id/members/:user_id', 'group' do + let(:source) { group } + end end it_behaves_like 'DELETE /:source_type/:id/members/:user_id', 'project' do diff --git a/spec/rubocop/cop/rspec/invalid_feature_category_spec.rb b/spec/rubocop/cop/rspec/feature_category_spec.rb index 1055275752d..05e3cae012e 100644 --- a/spec/rubocop/cop/rspec/invalid_feature_category_spec.rb +++ b/spec/rubocop/cop/rspec/feature_category_spec.rb @@ -3,9 +3,10 @@ require 'rubocop_spec_helper' require 'rspec-parameterized' -require_relative '../../../../rubocop/cop/rspec/invalid_feature_category' +require_relative '../../../../rubocop/feature_categories' +require_relative '../../../../rubocop/cop/rspec/feature_category' -RSpec.describe RuboCop::Cop::RSpec::InvalidFeatureCategory, feature_category: :tooling do +RSpec.describe RuboCop::Cop::RSpec::FeatureCategory, feature_category: :tooling do shared_examples 'feature category validation' do |valid_category| it 'flags invalid feature category in top level example group' do expect_offense(<<~RUBY, invalid: invalid_category) @@ -63,21 +64,14 @@ RSpec.describe RuboCop::Cop::RSpec::InvalidFeatureCategory, feature_category: :t let(:invalid_category) { :invalid_category } - context 'with categories defined in config/feature_categories.yml' do - where(:valid_category) do - YAML.load_file(rails_root_join('config/feature_categories.yml')) - end + context 'with defined in config/feature_categories.yml and custom categories' do + where(:valid_category) { RuboCop::FeatureCategories.available_with_custom.to_a } with_them do it_behaves_like 'feature category validation', params[:valid_category] end end - context 'with custom categories' do - it_behaves_like 'feature category validation', 'tooling' - it_behaves_like 'feature category validation', 'shared' - end - it 'flags invalid feature category for non-symbols' do expect_offense(<<~RUBY, invalid: invalid_category) RSpec.describe 'foo', feature_category: "%{invalid}" do @@ -91,9 +85,11 @@ RSpec.describe RuboCop::Cop::RSpec::InvalidFeatureCategory, feature_category: :t end it 'does not flag use of invalid categories in non-example code' do + valid_category = RuboCop::FeatureCategories.available.first + # See https://gitlab.com/gitlab-org/gitlab/-/issues/381882#note_1265865125 expect_no_offenses(<<~RUBY) - RSpec.describe 'A spec' do + RSpec.describe 'A spec', feature_category: :#{valid_category} do let(:api_handler) do Class.new(described_class) do namespace '/test' do @@ -112,6 +108,18 @@ RSpec.describe RuboCop::Cop::RSpec::InvalidFeatureCategory, feature_category: :t RUBY end + it 'flags missing feature category in top level example group' do + expect_offense(<<~RUBY) + RSpec.describe 'foo' do + ^^^^^^^^^^^^^^^^^^^^^^^ Please use a valid feature category. See https://docs.gitlab.com/ee/development/feature_categorization/#rspec-examples + end + + RSpec.describe 'foo', some: :tag do + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Please use a valid feature category. See https://docs.gitlab.com/ee/development/feature_categorization/#rspec-examples + end + RUBY + end + describe '#external_dependency_checksum' do it 'returns a SHA256 digest used by RuboCop to invalid cache' do expect(cop.external_dependency_checksum).to match(/^\h{64}$/) diff --git a/spec/rubocop/cop/rspec/missing_feature_category_spec.rb b/spec/rubocop/cop/rspec/missing_feature_category_spec.rb deleted file mode 100644 index 41b1d2b8580..00000000000 --- a/spec/rubocop/cop/rspec/missing_feature_category_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -require 'rubocop_spec_helper' -require_relative '../../../../rubocop/cop/rspec/missing_feature_category' - -RSpec.describe RuboCop::Cop::RSpec::MissingFeatureCategory, feature_category: :tooling do - it 'flags missing feature category in top level example group' do - expect_offense(<<~RUBY) - RSpec.describe 'foo' do - ^^^^^^^^^^^^^^^^^^^^ Please add missing feature category. See https://docs.gitlab.com/ee/development/feature_categorization/#rspec-examples. - end - - RSpec.describe 'foo', some: :tag do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Please add missing feature category. See https://docs.gitlab.com/ee/development/feature_categorization/#rspec-examples. - end - RUBY - end - - it 'does not flag if feature category is defined' do - expect_no_offenses(<<~RUBY) - RSpec.describe 'foo', feature_category: :foo do - end - - RSpec.describe 'foo', some: :tag, feature_category: :foo do - end - - RSpec.describe 'foo', feature_category: :foo, some: :tag do - end - RUBY - end -end diff --git a/spec/rubocop/feature_categories_spec.rb b/spec/rubocop/feature_categories_spec.rb index 85d1f4f8aff..ffe7ade82e2 100644 --- a/spec/rubocop/feature_categories_spec.rb +++ b/spec/rubocop/feature_categories_spec.rb @@ -45,6 +45,16 @@ RSpec.describe RuboCop::FeatureCategories, feature_category: :tooling do end end + context 'when value_node is nil' do + let(:value_node) { nil } + + it 'yields a message asking for a feature category with document link only' do + check.to yield_with_args(<<~MARKDOWN.chomp) + Please use a valid feature category. See https://example.com + MARKDOWN + end + end + context 'when value_node is not a symbol node' do before do allow(value_node).to receive(:sym_type?).and_return(false) @@ -55,7 +65,7 @@ RSpec.describe RuboCop::FeatureCategories, feature_category: :tooling do end end - context 'when categories contain the value the value_node has' do + context 'when category is found' do before do allow(value_node).to receive(:value).and_return(categories.first) end @@ -65,40 +75,27 @@ RSpec.describe RuboCop::FeatureCategories, feature_category: :tooling do end end - context 'when categories do not contain the value the value_node has' do + context 'when a similar category is found' do before do allow(value_node).to receive(:value).and_return('invalid_category') end - it 'yields a message asking for a feature category with document link' do + it 'yields a message asking for a feature category with suggestion and document link' do check.to yield_with_args(<<~MARKDOWN.chomp) Please use a valid feature category. Did you mean `:valid_category`? See https://example.com MARKDOWN end end - end - describe '#suggestion_message' do - let(:value_node) { instance_double(RuboCop::AST::SymbolNode) } - - context 'when categories do not contain the value the value_node has' do + context 'when no similar category is found' do before do - allow(value_node).to receive(:value).and_return('invalid_category') - end - - it 'returns a message suggesting a similar category name' do - expect(feature_categories.suggestion_message(value_node)) - .to eq('Did you mean `:valid_category`? ') + allow(value_node).to receive(:value).and_return('something_completely_different') end - context 'when the value the value_node has is too different' do - before do - allow(value_node).to receive(:value).and_return('GitLab') - end - - it 'returns nil' do - expect(feature_categories.suggestion_message(value_node)).to be_nil - end + it 'yields a message asking for a feature category with document link only' do + check.to yield_with_args(<<~MARKDOWN.chomp) + Please use a valid feature category. See https://example.com + MARKDOWN end end end diff --git a/spec/services/users/auto_ban_service_spec.rb b/spec/services/users/auto_ban_service_spec.rb new file mode 100644 index 00000000000..b989cec6a9d --- /dev/null +++ b/spec/services/users/auto_ban_service_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Users::AutoBanService, feature_category: :instance_resiliency do + let_it_be_with_reload(:user) { create(:user) } + let(:reason) { :auto_ban_reason } + + context 'when auto banning a user', :aggregate_failures do + subject(:auto_ban_user) { described_class.new(user: user, reason: reason).execute } + + context 'when successful' do + it 'returns success status' do + response = auto_ban_user + + expect(response[:status]).to eq(:success) + end + + it 'bans the user' do + expect { auto_ban_user }.to change { user.state }.from('active').to('banned') + end + + it 'creates a BannedUser' do + expect { auto_ban_user }.to change { Users::BannedUser.count }.by(1) + expect(Users::BannedUser.last.user_id).to eq(user.id) + end + + describe 'recording a custom attribute' do + it 'records a custom attribute' do + expect { auto_ban_user }.to change { UserCustomAttribute.count }.by(1) + expect(user.custom_attributes.by_key(UserCustomAttribute::AUTO_BANNED_BY).first.value).to eq(reason.to_s) + end + end + end + + context 'when failed' do + context 'when user is blocked' do + before do + user.block! + end + + it 'returns state error message' do + response = auto_ban_user + + expect(response[:status]).to eq(:error) + expect(response[:message]).to match('State cannot transition via \"ban\"') + end + + it 'does not modify the BannedUser record or user state' do + expect { auto_ban_user }.not_to change { Users::BannedUser.count } + expect { auto_ban_user }.not_to change { user.state } + end + end + end + end +end diff --git a/spec/tooling/danger/rubocop_inline_disable_suggestion_spec.rb b/spec/tooling/danger/rubocop_inline_disable_suggestion_spec.rb new file mode 100644 index 00000000000..94dd5192d74 --- /dev/null +++ b/spec/tooling/danger/rubocop_inline_disable_suggestion_spec.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require 'gitlab/dangerfiles/spec_helper' + +require_relative '../../../tooling/danger/rubocop_inline_disable_suggestion' +require_relative '../../../tooling/danger/project_helper' + +RSpec.describe Tooling::Danger::RubocopInlineDisableSuggestion, feature_category: :tooling do + include_context "with dangerfile" + + let(:fake_danger) { DangerSpecHelper.fake_danger } + let(:fake_project_helper) { instance_double('Tooling::Danger::ProjectHelper') } + let(:filename) { 'spec/foo_spec.rb' } + + let(:template) do + <<~SUGGESTION_MARKDOWN.chomp + + Consider removing this inline disabling and adhering to the rubocop rule. + If that isn't possible, please provide context as a reply for reviewers. + See [rubocop best practices](https://docs.gitlab.com/ee/development/rubocop_development_guide.html). + + ---- + + [Improve this message](https://gitlab.com/gitlab-org/gitlab/-/blob/master/tooling/danger/rubocop_inline_disable_suggestion.rb) + or [have feedback](https://gitlab.com/gitlab-org/gitlab/-/issues/428157)? + SUGGESTION_MARKDOWN + end + + let(:file_lines) do + <<~RUBY.split("\n") + def validate_credit_card?(project) + !current_user.has_required_credit_card_to_enable_shared_runners?(project) + return true if Gitlab.com? # rubocop:disable Some/Cop + end + + def show_buy_pipeline_minutes?(project, namespace) + return false unless ::Gitlab.com? # rubocop:disable Gitlab/AvoidGitlabInstanceChecks + + show_out_of_pipeline_minutes_notification?(project, namespace) + end + + def show_pipeline_minutes_notification_dot?(project, namespace) + return false unless ::Gitlab.com? # rubocop:disable Gitlab/AvoidGitlabInstanceChecks + return false if notification_dot_acknowledged? + + show_out_of_pipeline_minutes_notification?(project, namespace) + end + + def show_dot?(project, namespace) + return false unless ::Gitlab.com? # rubocop:disable Gitlab/AvoidGitlabInstanceChecks + return false if notification_dot_acknowledged? + + show_out_of_pipeline_minutes_notification?(project, namespace) + end + + def show_other_dot?(project, namespace) + return false unless ::Gitlab.com? # rubocop: disable Gitlab/AvoidGitlabInstanceChecks + return false if notification_dot_acknowledged? + + show_out_of_pipeline_minutes_notification?(project, namespace) + end + + def show_my_dot?(project, namespace) + return false unless ::Gitlab.com? # rubocop:todo Gitlab/AvoidGitlabInstanceChecks + return false if notification_dot_acknowledged? + + show_out_of_pipeline_minutes_notification?(project, namespace) + end + + def show_my_other_dot?(project, namespace) + return false unless ::Gitlab.com? # rubocop: todo Gitlab/AvoidGitlabInstanceChecks + return false if notification_dot_acknowledged? + + show_out_of_pipeline_minutes_notification?(project, namespace) + end + RUBY + end + + let(:changed_lines) do + <<~DIFF.split("\n") + + return true if Gitlab.com? # rubocop:disable Some/Cop + +end + + return false unless ::Gitlab.com? # rubocop:disable Gitlab/AvoidGitlabInstanceChecks + + return false unless ::Gitlab.com? # rubocop:disable Gitlab/AvoidGitlabInstanceChecks + + return false unless ::Gitlab.com? # rubocop:disable Gitlab/AvoidGitlabInstanceChecks + + return false unless ::Gitlab.com? # rubocop: disable Gitlab/AvoidGitlabInstanceChecks + + return false unless ::Gitlab.com? # rubocop:todo Gitlab/AvoidGitlabInstanceChecks + + return false unless ::Gitlab.com? # rubocop: todo Gitlab/AvoidGitlabInstanceChecks + DIFF + end + + subject(:rubocop) { fake_danger.new(helper: fake_helper) } + + before do + allow(rubocop).to receive(:project_helper).and_return(fake_project_helper) + allow(rubocop.helper).to receive(:changed_lines).with(filename).and_return(changed_lines) + allow(rubocop.project_helper).to receive(:file_lines).and_return(file_lines) + + rubocop.define_singleton_method(:add_suggestions_for) do |filename| + Tooling::Danger::RubocopInlineDisableSuggestion.new(filename, context: self).suggest + end + end + + it 'adds comments at the correct lines', :aggregate_failures do + [3, 7, 13, 20, 27, 34, 41].each do |line_number| + expect(rubocop).to receive(:markdown).with(template, file: filename, line: line_number) + end + + rubocop.add_suggestions_for(filename) + end +end diff --git a/tooling/danger/rubocop_inline_disable_suggestion.rb b/tooling/danger/rubocop_inline_disable_suggestion.rb new file mode 100644 index 00000000000..4d1bff9856b --- /dev/null +++ b/tooling/danger/rubocop_inline_disable_suggestion.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require_relative 'suggestion' + +module Tooling + module Danger + class RubocopInlineDisableSuggestion < Suggestion + MATCH = /^\+.*#\s*rubocop\s*:\s*(?:disable|todo)\s+/ + REPLACEMENT = nil + + SUGGESTION = <<~MESSAGE_MARKDOWN + Consider removing this inline disabling and adhering to the rubocop rule. + If that isn't possible, please provide context as a reply for reviewers. + See [rubocop best practices](https://docs.gitlab.com/ee/development/rubocop_development_guide.html). + + ---- + + [Improve this message](https://gitlab.com/gitlab-org/gitlab/-/blob/master/tooling/danger/rubocop_inline_disable_suggestion.rb) + or [have feedback](https://gitlab.com/gitlab-org/gitlab/-/issues/428157)? + MESSAGE_MARKDOWN + end + end +end |