diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-04 12:10:44 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-04 12:10:44 +0000 |
commit | 9d0b65f4b8aadf9eea57d40bc1627a8433699602 (patch) | |
tree | 1a327c2d99df613736106d3561524c5e2ede09ef | |
parent | ef863c1f85c474c13fd1bb9d84b00ad53f649ed0 (diff) | |
download | gitlab-ce-9d0b65f4b8aadf9eea57d40bc1627a8433699602.tar.gz |
Add latest changes from gitlab-org/gitlab@master
36 files changed, 324 insertions, 67 deletions
diff --git a/app/assets/javascripts/notebook/cells/output/index.vue b/app/assets/javascripts/notebook/cells/output/index.vue index 113d8cfc435..5f7ef4a4377 100644 --- a/app/assets/javascripts/notebook/cells/output/index.vue +++ b/app/assets/javascripts/notebook/cells/output/index.vue @@ -2,6 +2,7 @@ import CodeOutput from '../code/index.vue'; import HtmlOutput from './html.vue'; import ImageOutput from './image.vue'; +import LatexOutput from './latex.vue'; export default { props: { @@ -35,6 +36,8 @@ export default { return 'image/jpeg'; } else if (output.data['text/html']) { return 'text/html'; + } else if (output.data['text/latex']) { + return 'text/latex'; } else if (output.data['image/svg+xml']) { return 'image/svg+xml'; } @@ -59,6 +62,8 @@ export default { return ImageOutput; } else if (output.data['text/html']) { return HtmlOutput; + } else if (output.data['text/latex']) { + return LatexOutput; } else if (output.data['image/svg+xml']) { return HtmlOutput; } diff --git a/app/assets/javascripts/notebook/cells/output/latex.vue b/app/assets/javascripts/notebook/cells/output/latex.vue new file mode 100644 index 00000000000..db9e61dce82 --- /dev/null +++ b/app/assets/javascripts/notebook/cells/output/latex.vue @@ -0,0 +1,45 @@ +<script> +import 'mathjax/es5/tex-svg'; +import Prompt from '../prompt.vue'; + +export default { + name: 'LatexOutput', + components: { + Prompt, + }, + props: { + count: { + type: Number, + required: true, + }, + rawCode: { + type: String, + required: true, + }, + index: { + type: Number, + required: true, + }, + }, + computed: { + code() { + // MathJax will not parse out the inline delimeters "$$" correctly + // so we remove them from the raw code itself + const parsedCode = this.rawCode.replace(/\$\$/g, ''); + const svg = window.MathJax.tex2svg(parsedCode); + + // NOTE: This is used with `v-html` and not `v-safe-html` due to an + // issue with dompurify stripping out xlink attributes from use tags + return svg.outerHTML; + }, + }, +}; +</script> + +<template> + <div class="output"> + <prompt type="Out" :count="count" :show-output="index === 0" /> + <!-- eslint-disable --> + <div ref="maths" v-html="code"></div> + </div> +</template> diff --git a/app/assets/stylesheets/components/avatar.scss b/app/assets/stylesheets/components/avatar.scss index 67213eedca8..3c8abe43070 100644 --- a/app/assets/stylesheets/components/avatar.scss +++ b/app/assets/stylesheets/components/avatar.scss @@ -68,7 +68,7 @@ $avatar-sizes: ( ); $identicon-backgrounds: $identicon-red, $identicon-purple, $identicon-indigo, $identicon-blue, $identicon-teal, - $identicon-orange, $gray-darker; + $identicon-orange, $identicon-gray; %avatar-circle { float: left; @@ -125,8 +125,8 @@ $identicon-backgrounds: $identicon-red, $identicon-purple, $identicon-indigo, $i .identicon { text-align: center; vertical-align: top; - color: $gray-700; - background-color: $gray-darker; + color: $identicon-text-color; + background-color: $identicon-gray; // Sizes @each $size, $size-config in $avatar-sizes { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 808813599c5..5f0ba8ca9ce 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -629,12 +629,14 @@ $note-icon-gutter-width: 55px; /* * Identicon */ +$identicon-text-color: #525252 !default; $identicon-red: #ffebee !default; $identicon-purple: #f3e5f5 !default; $identicon-indigo: #e8eaf6 !default; $identicon-blue: #e3f2fd !default; $identicon-teal: #e0f2f1 !default; $identicon-orange: #fbe9e7 !default; +$identicon-gray: #eee !default; /* * Calendar diff --git a/app/controllers/repositories/lfs_api_controller.rb b/app/controllers/repositories/lfs_api_controller.rb index 9b7cbcb2dfe..2de29da4b45 100644 --- a/app/controllers/repositories/lfs_api_controller.rb +++ b/app/controllers/repositories/lfs_api_controller.rb @@ -103,18 +103,13 @@ module Repositories end def upload_headers - headers = { + { Authorization: authorization_header, # git-lfs v2.5.0 sets the Content-Type based on the uploaded file. This # ensures that Workhorse can intercept the request. - 'Content-Type': LFS_TRANSFER_CONTENT_TYPE + 'Content-Type': LFS_TRANSFER_CONTENT_TYPE, + 'Transfer-Encoding': 'chunked' } - - if Feature.enabled?(:lfs_chunked_encoding, project, default_enabled: true) - headers['Transfer-Encoding'] = 'chunked' - end - - headers end def lfs_check_batch_operation! diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 7866e3e3d9f..cc91e2cf809 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -335,7 +335,8 @@ module ApplicationSettingsHelper :group_export_limit, :group_download_export_limit, :wiki_page_max_content_bytes, - :container_registry_delete_tags_service_timeout + :container_registry_delete_tags_service_timeout, + :rate_limiting_response_text ] end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 9b9db7f93fd..58bfc1948b4 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -400,6 +400,10 @@ class ApplicationSetting < ApplicationRecord validates :ci_jwt_signing_key, rsa_key: true, allow_nil: true + validates :rate_limiting_response_text, + length: { maximum: 255, message: _('is too long (maximum is %{count} characters)') }, + allow_blank: true + attr_encrypted :asset_proxy_secret_key, mode: :per_attribute_iv, key: Settings.attr_encrypted_db_key_base_truncated, diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index 105889a364a..b68125d529a 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -172,7 +172,8 @@ module ApplicationSettingImplementation container_registry_delete_tags_service_timeout: 250, container_registry_expiration_policies_worker_capacity: 0, kroki_enabled: false, - kroki_url: nil + kroki_url: nil, + rate_limiting_response_text: nil } end diff --git a/app/views/admin/application_settings/_ip_limits.html.haml b/app/views/admin/application_settings/_ip_limits.html.haml index b06070d15d4..11ffe3f56e3 100644 --- a/app/views/admin/application_settings/_ip_limits.html.haml +++ b/app/views/admin/application_settings/_ip_limits.html.haml @@ -49,5 +49,12 @@ .form-group = f.label :throttle_authenticated_web_period_in_seconds, 'Authenticated web rate limit period in seconds', class: 'label-bold' = f.number_field :throttle_authenticated_web_period_in_seconds, class: 'form-control' + %hr + %h5 + = _('Response text') + .form-group + = f.label :rate_limiting_response_text, class: 'label-bold' do + = _('A plain-text response to show to clients that hit the rate limit.') + = f.text_area :rate_limiting_response_text, placeholder: ::Gitlab::Throttle::DEFAULT_RATE_LIMITING_RESPONSE_TEXT, class: 'form-control', rows: 5 = f.submit 'Save changes', class: "gl-button btn btn-success", data: { qa_selector: 'save_changes_button' } diff --git a/changelogs/unreleased/allow-custom-rate-limiting-response.yml b/changelogs/unreleased/allow-custom-rate-limiting-response.yml new file mode 100644 index 00000000000..059e2ac8caf --- /dev/null +++ b/changelogs/unreleased/allow-custom-rate-limiting-response.yml @@ -0,0 +1,5 @@ +--- +title: Allow custom response to be set when rate limits are exceeded +merge_request: 50693 +author: +type: added diff --git a/changelogs/unreleased/fix-identicon-dark-mode.yml b/changelogs/unreleased/fix-identicon-dark-mode.yml new file mode 100644 index 00000000000..98e2e59aa3b --- /dev/null +++ b/changelogs/unreleased/fix-identicon-dark-mode.yml @@ -0,0 +1,5 @@ +--- +title: Fix identicon text color in dark mode +merge_request: 49785 +author: "@yo" +type: fixed diff --git a/changelogs/unreleased/jdb-juptyer-notebooks-latex-support.yml b/changelogs/unreleased/jdb-juptyer-notebooks-latex-support.yml new file mode 100644 index 00000000000..0d505c0f33c --- /dev/null +++ b/changelogs/unreleased/jdb-juptyer-notebooks-latex-support.yml @@ -0,0 +1,5 @@ +--- +title: Add LaTeX support for Jupyter Notebooks +merge_request: 49497 +author: +type: fixed diff --git a/changelogs/unreleased/sh-remove-lfs-chunked-encoding-feature-flag.yml b/changelogs/unreleased/sh-remove-lfs-chunked-encoding-feature-flag.yml new file mode 100644 index 00000000000..f39b7061925 --- /dev/null +++ b/changelogs/unreleased/sh-remove-lfs-chunked-encoding-feature-flag.yml @@ -0,0 +1,5 @@ +--- +title: Remove lfs_chunked_encoding feature flag +merge_request: 50557 +author: +type: changed diff --git a/config/feature_flags/development/lfs_chunked_encoding.yml b/config/feature_flags/development/lfs_chunked_encoding.yml deleted file mode 100644 index 92f534d1000..00000000000 --- a/config/feature_flags/development/lfs_chunked_encoding.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: lfs_chunked_encoding -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48269 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/285581 -milestone: '13.6' -type: development -group: -default_enabled: true diff --git a/db/migrate/20201230161206_add_rate_limiting_response_text_to_application_settings.rb b/db/migrate/20201230161206_add_rate_limiting_response_text_to_application_settings.rb new file mode 100644 index 00000000000..647455f5f88 --- /dev/null +++ b/db/migrate/20201230161206_add_rate_limiting_response_text_to_application_settings.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class AddRateLimitingResponseTextToApplicationSettings < ActiveRecord::Migration[6.0] + DOWNTIME = false + + # rubocop:disable Migration/AddLimitToTextColumns + # limit is added in 20210101110640_set_limit_for_rate_limiting_response_text + def change + add_column :application_settings, :rate_limiting_response_text, :text + end + # rubocop:enable Migration/AddLimitToTextColumns +end diff --git a/db/migrate/20210101110640_set_limit_for_rate_limiting_response_text.rb b/db/migrate/20210101110640_set_limit_for_rate_limiting_response_text.rb new file mode 100644 index 00000000000..b72f2ae7d70 --- /dev/null +++ b/db/migrate/20210101110640_set_limit_for_rate_limiting_response_text.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class SetLimitForRateLimitingResponseText < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_text_limit :application_settings, :rate_limiting_response_text, 255 + end + + def down + remove_text_limit :application_settings, :rate_limiting_response_text + end +end diff --git a/db/schema_migrations/20201230161206 b/db/schema_migrations/20201230161206 new file mode 100644 index 00000000000..594ca5ea890 --- /dev/null +++ b/db/schema_migrations/20201230161206 @@ -0,0 +1 @@ +b3fcc73c6b61469d770e9eb9a14c88bb86398db4ab4b6dc5283718a147db0ac0
\ No newline at end of file diff --git a/db/schema_migrations/20210101110640 b/db/schema_migrations/20210101110640 new file mode 100644 index 00000000000..e21e6768e7b --- /dev/null +++ b/db/schema_migrations/20210101110640 @@ -0,0 +1 @@ +8aac4108b658a7a0646ec230dc2568cb51fea0535b13dfba8b8c9e6edb401d07
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index a9022190aeb..3693d6c0ea4 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -9376,12 +9376,14 @@ CREATE TABLE application_settings ( cloud_license_enabled boolean DEFAULT false NOT NULL, disable_feed_token boolean DEFAULT false NOT NULL, personal_access_token_prefix text, + rate_limiting_response_text text, CONSTRAINT app_settings_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)), CONSTRAINT check_17d9558205 CHECK ((char_length((kroki_url)::text) <= 1024)), CONSTRAINT check_2dba05b802 CHECK ((char_length(gitpod_url) <= 255)), CONSTRAINT check_51700b31b5 CHECK ((char_length(default_branch_name) <= 255)), CONSTRAINT check_57123c9593 CHECK ((char_length(help_page_documentation_base_url) <= 255)), CONSTRAINT check_718b4458ae CHECK ((char_length(personal_access_token_prefix) <= 20)), + CONSTRAINT check_7227fad848 CHECK ((char_length(rate_limiting_response_text) <= 255)), CONSTRAINT check_85a39b68ff CHECK ((char_length(encrypted_ci_jwt_signing_key_iv) <= 255)), CONSTRAINT check_9a719834eb CHECK ((char_length(secret_detection_token_revocation_url) <= 255)), CONSTRAINT check_9c6c447a13 CHECK ((char_length(maintenance_mode_message) <= 255)), diff --git a/doc/api/settings.md b/doc/api/settings.md index 5680687e87e..a71f49e724e 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -83,7 +83,8 @@ Example response: "raw_blob_request_limit": 300, "wiki_page_max_content_bytes": 52428800, "require_admin_approval_after_user_signup": false, - "personal_access_token_prefix": "GL-" + "personal_access_token_prefix": "GL-", + "rate_limiting_response_text": null } ``` @@ -176,7 +177,8 @@ Example response: "raw_blob_request_limit": 300, "wiki_page_max_content_bytes": 52428800, "require_admin_approval_after_user_signup": false, - "personal_access_token_prefix": "GL-" + "personal_access_token_prefix": "GL-", + "rate_limiting_response_text": null } ``` @@ -330,6 +332,7 @@ listed in the descriptions of the relevant settings. | `pseudonymizer_enabled` | boolean | no | **(PREMIUM)** When enabled, GitLab runs a background job that produces pseudonymized CSVs of the GitLab database to upload to your configured object storage directory. | `push_event_activities_limit` | integer | no | Number of changes (branches or tags) in a single push to determine whether individual push events or bulk push events are created. [Bulk push events are created](../user/admin_area/settings/push_event_activities_limit.md) if it surpasses that value. | | `push_event_hooks_limit` | integer | no | Number of changes (branches or tags) in a single push to determine whether webhooks and services fire or not. Webhooks and services aren't submitted if it surpasses that value. | +| `rate_limiting_response_text` | string | no | When rate limiting is enabled via the `throttle_*` settings, send this plain text response when a rate limit is exceeded. 'Retry later' is sent if this is blank. | | `raw_blob_request_limit` | integer | no | Max number of requests per minute for each raw path. Default: 300. To disable throttling set to 0.| | `recaptcha_enabled` | boolean | no | (**If enabled, requires:** `recaptcha_private_key` and `recaptcha_site_key`) Enable reCAPTCHA. | | `recaptcha_private_key` | string | required by: `recaptcha_enabled` | Private key for reCAPTCHA. | diff --git a/doc/install/README.md b/doc/install/README.md index f0aee9b6927..7b037da4c4a 100644 --- a/doc/install/README.md +++ b/doc/install/README.md @@ -79,9 +79,10 @@ package. ## Installing GitLab from source -If the Omnibus GitLab package is not available in your distribution, you can -install GitLab from source: Useful for unsupported systems like \*BSD. For an -overview of the directory structure, read the [structure documentation](installation.md#gitlab-directory-structure). +If the Omnibus GitLab package isn't available for your distribution, you can +install GitLab from source. This can be useful with unsupported systems, like +\*BSD. For an overview of the directory structure, see the +[structure documentation](installation.md#gitlab-directory-structure). [**> Install GitLab from source.**](installation.md) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index cced6d0a3f6..69983edc383 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -16,7 +16,7 @@ as the hardware requirements that are needed to install and use GitLab. - Ubuntu (16.04/18.04/20.04) - Debian (9/10) -- CentOS (6/7/8) +- CentOS (7/8) - openSUSE (Leap 15.1/Enterprise Server 12.2) - Red Hat Enterprise Linux (please use the CentOS packages and instructions) - Scientific Linux (please use the CentOS packages and instructions) @@ -35,6 +35,9 @@ For the installation options, see [the main installation page](README.md). Installation of GitLab on these operating systems is possible, but not supported. Please see the [installation from source guide](installation.md) and the [installation guides](https://about.gitlab.com/install/) for more information. +Please see [OS versions that are no longer supported](https://docs.gitlab.com/omnibus/package-information/deprecated_os.html) for Omnibus installs page +for a list of supported and unsupported OS versions as well as the last support GitLab version for that OS. + ### Microsoft Windows GitLab is developed for Linux-based operating systems. @@ -163,11 +166,11 @@ Support for [PostgreSQL 9.6 and 10 has been removed in GitLab 13.0](https://abou #### Additional requirements for GitLab Geo -If you're using [GitLab Geo](../administration/geo/index.md): - -- We strongly recommend running Omnibus-managed instances as they are actively - developed and tested. We aim to be compatible with most external (not managed - by Omnibus) databases (for example, [AWS Relational Database Service (RDS)](https://aws.amazon.com/rds/)) but we don't guarantee compatibility. +If you're using [GitLab Geo](../administration/geo/index.md), we strongly +recommend running Omnibus GitLab-managed instances, as we actively develop and +test based on those. We try to be compatible with most external (not managed by +Omnibus GitLab) databases (for example, [AWS Relational Database Service (RDS)](https://aws.amazon.com/rds/)), +but we can't guarantee compatibility. ## Puma settings diff --git a/doc/user/admin_area/settings/user_and_ip_rate_limits.md b/doc/user/admin_area/settings/user_and_ip_rate_limits.md index 3289a201890..72c05d1115d 100644 --- a/doc/user/admin_area/settings/user_and_ip_rate_limits.md +++ b/doc/user/admin_area/settings/user_and_ip_rate_limits.md @@ -25,6 +25,17 @@ By default, all Git operations are first tried unathenticated. Because of this, ![user-and-ip-rate-limits](img/user_and_ip_rate_limits.png) +## Response text + +A request that exceeds a rate limit will get a 429 response code and a +plain-text body, which by default is: + +```plaintext +Retry later +``` + +It is possible to customize this response text in the admin area. + ## Use an HTTP header to bypass rate limiting > [Introduced](https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/622) in GitLab 13.6. diff --git a/lib/gitlab/rack_attack.rb b/lib/gitlab/rack_attack.rb index 7c336153e32..b136023d5f2 100644 --- a/lib/gitlab/rack_attack.rb +++ b/lib/gitlab/rack_attack.rb @@ -10,8 +10,17 @@ module Gitlab def self.configure(rack_attack) # This adds some methods used by our throttles to the `Rack::Request` rack_attack::Request.include(Gitlab::RackAttack::Request) - # Send the Retry-After header so clients (e.g. python-gitlab) can make good choices about delays - Rack::Attack.throttled_response_retry_after_header = true + + # This is Rack::Attack::DEFAULT_THROTTLED_RESPONSE, modified to allow a custom response + Rack::Attack.throttled_response = lambda do |env| + # Send the Retry-After header so clients (e.g. python-gitlab) can make good choices about delays + match_data = env['rack.attack.match_data'] + now = match_data[:epoch_time] + retry_after = match_data[:period] - (now % match_data[:period]) + + [429, { 'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s }, [Gitlab::Throttle.rate_limiting_response_text]] + end + # Configure the throttles configure_throttles(rack_attack) diff --git a/lib/gitlab/throttle.rb b/lib/gitlab/throttle.rb index aebf8d92cb3..520075012e8 100644 --- a/lib/gitlab/throttle.rb +++ b/lib/gitlab/throttle.rb @@ -2,6 +2,8 @@ module Gitlab class Throttle + DEFAULT_RATE_LIMITING_RESPONSE_TEXT = 'Retry later' + def self.settings Gitlab::CurrentSettings.current_application_settings end @@ -46,5 +48,9 @@ module Gitlab { limit: limit_proc, period: period_proc } end + + def self.rate_limiting_response_text + (settings.rate_limiting_response_text.presence || DEFAULT_RATE_LIMITING_RESPONSE_TEXT) + "\n" + end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 88d9f3f3cbf..4de0b7732d2 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1304,6 +1304,9 @@ msgstr "" msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features" msgstr "" +msgid "A plain-text response to show to clients that hit the rate limit." +msgstr "" + msgid "A platform value can be web, mob or app." msgstr "" @@ -23928,6 +23931,9 @@ msgstr "" msgid "Response metrics (NGINX)" msgstr "" +msgid "Response text" +msgstr "" + msgid "Restart Terminal" msgstr "" diff --git a/package.json b/package.json index 741ca7b5285..b22a49071df 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "katex": "^0.10.0", "lodash": "^4.17.20", "marked": "^0.3.12", + "mathjax": "3", "mermaid": "^8.5.2", "mersenne-twister": "1.1.0", "minimatch": "^3.0.4", diff --git a/spec/frontend/diffs/components/diff_file_spec.js b/spec/frontend/diffs/components/diff_file_spec.js index 77d83b102aa..c715d779986 100644 --- a/spec/frontend/diffs/components/diff_file_spec.js +++ b/spec/frontend/diffs/components/diff_file_spec.js @@ -1,6 +1,9 @@ import Vuex from 'vuex'; import { shallowMount, createLocalVue } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import httpStatus from '~/lib/utils/http_status'; import createDiffsStore from '~/diffs/store/modules'; import createNotesStore from '~/notes/stores/modules'; import diffFileMockDataReadable from '../mock_data/diff_file'; @@ -118,14 +121,17 @@ const changeViewerType = (store, newType, index = 0) => describe('DiffFile', () => { let wrapper; let store; + let axiosMock; beforeEach(() => { + axiosMock = new MockAdapter(axios); ({ wrapper, store } = createComponent({ file: getReadableFile() })); }); afterEach(() => { wrapper.destroy(); wrapper = null; + axiosMock.restore(); }); describe('bus events', () => { @@ -353,8 +359,10 @@ describe('DiffFile', () => { describe('loading', () => { it('should have loading icon while loading a collapsed diffs', async () => { + const { load_collapsed_diff_url } = store.state.diffs.diffFiles[0]; + axiosMock.onGet(load_collapsed_diff_url).reply(httpStatus.OK, getReadableFile()); makeFileAutomaticallyCollapsed(store); - wrapper.vm.isLoadingCollapsedDiff = true; + wrapper.vm.requestDiff(); await wrapper.vm.$nextTick(); diff --git a/spec/frontend/notebook/cells/output/index_spec.js b/spec/frontend/notebook/cells/output/index_spec.js index d12f57e80c7..2985abf0f4f 100644 --- a/spec/frontend/notebook/cells/output/index_spec.js +++ b/spec/frontend/notebook/cells/output/index_spec.js @@ -18,12 +18,14 @@ describe('Output component', () => { }; beforeEach(() => { + // This is the output after rendering a jupyter notebook json = getJSONFixture('blob/notebook/basic.json'); }); describe('text output', () => { beforeEach((done) => { - createComponent(json.cells[2].outputs[0]); + const textType = json.cells[2]; + createComponent(textType.outputs[0]); setImmediate(() => { done(); @@ -41,7 +43,8 @@ describe('Output component', () => { describe('image output', () => { beforeEach((done) => { - createComponent(json.cells[3].outputs[0]); + const imageType = json.cells[3]; + createComponent(imageType.outputs[0]); setImmediate(() => { done(); @@ -55,23 +58,42 @@ describe('Output component', () => { describe('html output', () => { it('renders raw HTML', () => { - createComponent(json.cells[4].outputs[0]); + const htmlType = json.cells[4]; + createComponent(htmlType.outputs[0]); expect(vm.$el.querySelector('p')).not.toBeNull(); - expect(vm.$el.querySelectorAll('p').length).toBe(1); + expect(vm.$el.querySelectorAll('p')).toHaveLength(1); expect(vm.$el.textContent.trim()).toContain('test'); }); it('renders multiple raw HTML outputs', () => { - createComponent([json.cells[4].outputs[0], json.cells[4].outputs[0]]); + const htmlType = json.cells[4]; + createComponent([htmlType.outputs[0], htmlType.outputs[0]]); - expect(vm.$el.querySelectorAll('p').length).toBe(2); + expect(vm.$el.querySelectorAll('p')).toHaveLength(2); + }); + }); + + describe('LaTeX output', () => { + it('renders LaTeX', () => { + const output = { + data: { + 'text/latex': ['$$F(k) = \\int_{-\\infty}^{\\infty} f(x) e^{2\\pi i k} dx$$'], + 'text/plain': ['<IPython.core.display.Latex object>'], + }, + metadata: {}, + output_type: 'display_data', + }; + createComponent(output); + + expect(vm.$el.querySelector('.MathJax')).not.toBeNull(); }); }); describe('svg output', () => { beforeEach((done) => { - createComponent(json.cells[5].outputs[0]); + const svgType = json.cells[5]; + createComponent(svgType.outputs[0]); setImmediate(() => { done(); @@ -85,7 +107,8 @@ describe('Output component', () => { describe('default to plain text', () => { beforeEach((done) => { - createComponent(json.cells[6].outputs[0]); + const unknownType = json.cells[6]; + createComponent(unknownType.outputs[0]); setImmediate(() => { done(); @@ -102,7 +125,8 @@ describe('Output component', () => { }); it("renders as plain text when doesn't recognise other types", (done) => { - createComponent(json.cells[7].outputs[0]); + const unknownType = json.cells[7]; + createComponent(unknownType.outputs[0]); setImmediate(() => { expect(vm.$el.querySelector('pre')).not.toBeNull(); diff --git a/spec/frontend/notebook/cells/output/latex_spec.js b/spec/frontend/notebook/cells/output/latex_spec.js new file mode 100644 index 00000000000..848d2069421 --- /dev/null +++ b/spec/frontend/notebook/cells/output/latex_spec.js @@ -0,0 +1,40 @@ +import { shallowMount } from '@vue/test-utils'; +import LatexOutput from '~/notebook/cells/output/latex.vue'; +import Prompt from '~/notebook/cells/prompt.vue'; + +describe('LaTeX output cell', () => { + beforeEach(() => { + window.MathJax = { + tex2svg: jest.fn((code) => ({ outerHTML: code })), + }; + }); + + const inlineLatex = '$$F(k) = \\int_{-\\infty}^{\\infty} f(x) e^{2\\pi i k} dx$$'; + const count = 12345; + + const createComponent = (rawCode, index) => + shallowMount(LatexOutput, { + propsData: { + count, + index, + rawCode, + }, + }); + + it.each` + index | expectation + ${0} | ${true} + ${1} | ${false} + `('sets `Prompt.show-output` to $expectation when index is $index', ({ index, expectation }) => { + const wrapper = createComponent(inlineLatex, index); + const prompt = wrapper.find(Prompt); + + expect(prompt.props().count).toEqual(count); + expect(prompt.props().showOutput).toEqual(expectation); + }); + + it('strips the `$$` delimter from LaTeX', () => { + createComponent(inlineLatex, 0); + expect(window.MathJax.tex2svg).toHaveBeenCalledWith(expect.not.stringContaining('$$')); + }); +}); diff --git a/spec/lib/gitlab/rack_attack_spec.rb b/spec/lib/gitlab/rack_attack_spec.rb index d72863b0103..8fbf7494b64 100644 --- a/spec/lib/gitlab/rack_attack_spec.rb +++ b/spec/lib/gitlab/rack_attack_spec.rb @@ -22,8 +22,7 @@ RSpec.describe Gitlab::RackAttack, :aggregate_failures do stub_const("Rack::Attack", fake_rack_attack) stub_const("Rack::Attack::Request", fake_rack_attack_request) - # Expect rather than just allow, because this is actually fairly important functionality - expect(fake_rack_attack).to receive(:throttled_response_retry_after_header=).with(true) + allow(fake_rack_attack).to receive(:throttled_response=) allow(fake_rack_attack).to receive(:throttle) allow(fake_rack_attack).to receive(:track) allow(fake_rack_attack).to receive(:safelist) @@ -36,6 +35,12 @@ RSpec.describe Gitlab::RackAttack, :aggregate_failures do expect(fake_rack_attack_request).to include(described_class::Request) end + it 'configures the throttle response' do + described_class.configure(fake_rack_attack) + + expect(fake_rack_attack).to have_received(:throttled_response=).with(an_instance_of(Proc)) + end + it 'configures the safelist' do described_class.configure(fake_rack_attack) diff --git a/spec/lib/gitlab/throttle_spec.rb b/spec/lib/gitlab/throttle_spec.rb index 7462b2e1c38..50d723193ac 100644 --- a/spec/lib/gitlab/throttle_spec.rb +++ b/spec/lib/gitlab/throttle_spec.rb @@ -30,4 +30,32 @@ RSpec.describe Gitlab::Throttle do end end end + + describe '.rate_limiting_response_text' do + subject { described_class.rate_limiting_response_text } + + context 'when the setting is not present' do + before do + stub_application_setting(rate_limiting_response_text: '') + end + + it 'returns the default value with a trailing newline' do + expect(subject).to eq(described_class::DEFAULT_RATE_LIMITING_RESPONSE_TEXT + "\n") + end + end + + context 'when the setting is present' do + let(:response_text) do + 'Rate limit exceeded; see https://docs.gitlab.com/ee/user/gitlab_com/#gitlabcom-specific-rate-limits for more details' + end + + before do + stub_application_setting(rate_limiting_response_text: response_text) + end + + it 'returns the default value with a trailing newline' do + expect(subject).to eq(response_text + "\n") + end + end + end end diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 535d511a459..aeec18aee2b 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -193,10 +193,8 @@ RSpec.describe 'Git LFS API and storage' do subject(:request) { post_lfs_json batch_url(project), body, headers } let(:response) { request && super() } - let(:lfs_chunked_encoding) { true } before do - stub_feature_flags(lfs_chunked_encoding: lfs_chunked_encoding) project.lfs_objects << lfs_object end @@ -480,20 +478,6 @@ RSpec.describe 'Git LFS API and storage' do expect(headers['Transfer-Encoding']).to eq('chunked') end - context 'when lfs_chunked_encoding feature is disabled' do - let(:lfs_chunked_encoding) { false } - - it 'responds with upload hypermedia link' do - expect(json_response['objects']).to be_kind_of(Array) - expect(json_response['objects'].first).to include(sample_object) - expect(json_response['objects'].first['actions']['upload']['href']).to eq(objects_url(project, sample_oid, sample_size)) - - headers = json_response['objects'].first['actions']['upload']['header'] - expect(headers['Content-Type']).to eq('application/octet-stream') - expect(headers['Transfer-Encoding']).to be_nil - end - end - it_behaves_like 'process authorization header', renew_authorization: renew_authorization end diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb index c2e68df2c40..157fcac56fa 100644 --- a/spec/requests/rack_attack_global_spec.rb +++ b/spec/requests/rack_attack_global_spec.rb @@ -60,6 +60,24 @@ RSpec.describe 'Rack Attack global throttles' do expect_rejection { get url_that_does_not_require_authentication } end + context 'with custom response text' do + before do + stub_application_setting(rate_limiting_response_text: 'Custom response') + end + + it 'rejects requests over the rate limit' do + # At first, allow requests under the rate limit. + requests_per_period.times do + get url_that_does_not_require_authentication + expect(response).to have_gitlab_http_status(:ok) + end + + # the last straw + expect_rejection { get url_that_does_not_require_authentication } + expect(response.body).to eq("Custom response\n") + end + end + it 'allows requests after throttling and then waiting for the next period' do requests_per_period.times do get url_that_does_not_require_authentication diff --git a/spec/support/helpers/rack_attack_spec_helpers.rb b/spec/support/helpers/rack_attack_spec_helpers.rb index a8ae69885d8..d479420e87b 100644 --- a/spec/support/helpers/rack_attack_spec_helpers.rb +++ b/spec/support/helpers/rack_attack_spec_helpers.rb @@ -25,6 +25,7 @@ module RackAttackSpecHelpers yield expect(response).to have_gitlab_http_status(:too_many_requests) + expect(response).to have_header('Retry-After') end def expect_ok(&block) diff --git a/yarn.lock b/yarn.lock index 4b70e94b7f9..30fcba5be37 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3057,7 +3057,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@2, commander@^2.10.0, commander@^2.16.0, commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@~2.20.0: +commander@2, commander@^2.10.0, commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@~2.20.0: version "2.20.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== @@ -7476,11 +7476,11 @@ karma@^4.2.0: useragent "2.3.0" katex@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/katex/-/katex-0.10.0.tgz#da562e5d0d5cc3aa602e27af8a9b8710bfbce765" - integrity sha512-/WRvx+L1eVBrLwX7QzKU1dQuaGnE7E8hDvx3VWfZh9HbMiCfsKWJNnYZ0S8ZMDAfAyDSofdyXIrH/hujF1fYXg== + version "0.10.2" + resolved "https://registry.yarnpkg.com/katex/-/katex-0.10.2.tgz#39973edbb65eda5b6f9e7f41648781e557dd4932" + integrity sha512-cQOmyIRoMloCoSIOZ1+gEwsksdJZ1EW4SWm3QzxSza/QsnZr6D4U1V9S4q+B/OLm2OQ8TCBecQ8MaIfnScI7cw== dependencies: - commander "^2.16.0" + commander "^2.19.0" keyv@^3.0.0: version "3.1.0" @@ -8054,6 +8054,11 @@ marked@^0.3.12, marked@~0.3.6: resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790" integrity sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg== +mathjax@3: + version "3.1.2" + resolved "https://registry.yarnpkg.com/mathjax/-/mathjax-3.1.2.tgz#95c0d45ce2330ef7b6a815cebe7d61ecc26bbabd" + integrity sha512-BojKspBv4nNWzO1wC6VEI+g9gHDOhkaGHGgLxXkasdU4pwjdO5AXD5M/wcLPkXYPjZ/N+6sU8rjQTlyvN2cWiQ== + mathml-tag-names@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.1.tgz#6dff66c99d55ecf739ca53c492e626f1d12a33cc" |