Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-01-04 12:10:44 +00:00
parent ef863c1f85
commit 9d0b65f4b8
36 changed files with 324 additions and 67 deletions

View file

@ -2,6 +2,7 @@
import CodeOutput from '../code/index.vue'; import CodeOutput from '../code/index.vue';
import HtmlOutput from './html.vue'; import HtmlOutput from './html.vue';
import ImageOutput from './image.vue'; import ImageOutput from './image.vue';
import LatexOutput from './latex.vue';
export default { export default {
props: { props: {
@ -35,6 +36,8 @@ export default {
return 'image/jpeg'; return 'image/jpeg';
} else if (output.data['text/html']) { } else if (output.data['text/html']) {
return 'text/html'; return 'text/html';
} else if (output.data['text/latex']) {
return 'text/latex';
} else if (output.data['image/svg+xml']) { } else if (output.data['image/svg+xml']) {
return 'image/svg+xml'; return 'image/svg+xml';
} }
@ -59,6 +62,8 @@ export default {
return ImageOutput; return ImageOutput;
} else if (output.data['text/html']) { } else if (output.data['text/html']) {
return HtmlOutput; return HtmlOutput;
} else if (output.data['text/latex']) {
return LatexOutput;
} else if (output.data['image/svg+xml']) { } else if (output.data['image/svg+xml']) {
return HtmlOutput; return HtmlOutput;
} }

View file

@ -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>

View file

@ -68,7 +68,7 @@ $avatar-sizes: (
); );
$identicon-backgrounds: $identicon-red, $identicon-purple, $identicon-indigo, $identicon-blue, $identicon-teal, $identicon-backgrounds: $identicon-red, $identicon-purple, $identicon-indigo, $identicon-blue, $identicon-teal,
$identicon-orange, $gray-darker; $identicon-orange, $identicon-gray;
%avatar-circle { %avatar-circle {
float: left; float: left;
@ -125,8 +125,8 @@ $identicon-backgrounds: $identicon-red, $identicon-purple, $identicon-indigo, $i
.identicon { .identicon {
text-align: center; text-align: center;
vertical-align: top; vertical-align: top;
color: $gray-700; color: $identicon-text-color;
background-color: $gray-darker; background-color: $identicon-gray;
// Sizes // Sizes
@each $size, $size-config in $avatar-sizes { @each $size, $size-config in $avatar-sizes {

View file

@ -629,12 +629,14 @@ $note-icon-gutter-width: 55px;
/* /*
* Identicon * Identicon
*/ */
$identicon-text-color: #525252 !default;
$identicon-red: #ffebee !default; $identicon-red: #ffebee !default;
$identicon-purple: #f3e5f5 !default; $identicon-purple: #f3e5f5 !default;
$identicon-indigo: #e8eaf6 !default; $identicon-indigo: #e8eaf6 !default;
$identicon-blue: #e3f2fd !default; $identicon-blue: #e3f2fd !default;
$identicon-teal: #e0f2f1 !default; $identicon-teal: #e0f2f1 !default;
$identicon-orange: #fbe9e7 !default; $identicon-orange: #fbe9e7 !default;
$identicon-gray: #eee !default;
/* /*
* Calendar * Calendar

View file

@ -103,18 +103,13 @@ module Repositories
end end
def upload_headers def upload_headers
headers = { {
Authorization: authorization_header, Authorization: authorization_header,
# git-lfs v2.5.0 sets the Content-Type based on the uploaded file. This # git-lfs v2.5.0 sets the Content-Type based on the uploaded file. This
# ensures that Workhorse can intercept the request. # 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 end
def lfs_check_batch_operation! def lfs_check_batch_operation!

View file

@ -335,7 +335,8 @@ module ApplicationSettingsHelper
:group_export_limit, :group_export_limit,
:group_download_export_limit, :group_download_export_limit,
:wiki_page_max_content_bytes, :wiki_page_max_content_bytes,
:container_registry_delete_tags_service_timeout :container_registry_delete_tags_service_timeout,
:rate_limiting_response_text
] ]
end end

View file

@ -400,6 +400,10 @@ class ApplicationSetting < ApplicationRecord
validates :ci_jwt_signing_key, validates :ci_jwt_signing_key,
rsa_key: true, allow_nil: true 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, attr_encrypted :asset_proxy_secret_key,
mode: :per_attribute_iv, mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base_truncated, key: Settings.attr_encrypted_db_key_base_truncated,

View file

@ -172,7 +172,8 @@ module ApplicationSettingImplementation
container_registry_delete_tags_service_timeout: 250, container_registry_delete_tags_service_timeout: 250,
container_registry_expiration_policies_worker_capacity: 0, container_registry_expiration_policies_worker_capacity: 0,
kroki_enabled: false, kroki_enabled: false,
kroki_url: nil kroki_url: nil,
rate_limiting_response_text: nil
} }
end end

View file

@ -49,5 +49,12 @@
.form-group .form-group
= f.label :throttle_authenticated_web_period_in_seconds, 'Authenticated web rate limit period in seconds', class: 'label-bold' = 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' = 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' } = f.submit 'Save changes', class: "gl-button btn btn-success", data: { qa_selector: 'save_changes_button' }

View file

@ -0,0 +1,5 @@
---
title: Allow custom response to be set when rate limits are exceeded
merge_request: 50693
author:
type: added

View file

@ -0,0 +1,5 @@
---
title: Fix identicon text color in dark mode
merge_request: 49785
author: "@yo"
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Add LaTeX support for Jupyter Notebooks
merge_request: 49497
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Remove lfs_chunked_encoding feature flag
merge_request: 50557
author:
type: changed

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1 @@
b3fcc73c6b61469d770e9eb9a14c88bb86398db4ab4b6dc5283718a147db0ac0

View file

@ -0,0 +1 @@
8aac4108b658a7a0646ec230dc2568cb51fea0535b13dfba8b8c9e6edb401d07

View file

@ -9376,12 +9376,14 @@ CREATE TABLE application_settings (
cloud_license_enabled boolean DEFAULT false NOT NULL, cloud_license_enabled boolean DEFAULT false NOT NULL,
disable_feed_token boolean DEFAULT false NOT NULL, disable_feed_token boolean DEFAULT false NOT NULL,
personal_access_token_prefix text, 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 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_17d9558205 CHECK ((char_length((kroki_url)::text) <= 1024)),
CONSTRAINT check_2dba05b802 CHECK ((char_length(gitpod_url) <= 255)), CONSTRAINT check_2dba05b802 CHECK ((char_length(gitpod_url) <= 255)),
CONSTRAINT check_51700b31b5 CHECK ((char_length(default_branch_name) <= 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_57123c9593 CHECK ((char_length(help_page_documentation_base_url) <= 255)),
CONSTRAINT check_718b4458ae CHECK ((char_length(personal_access_token_prefix) <= 20)), 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_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_9a719834eb CHECK ((char_length(secret_detection_token_revocation_url) <= 255)),
CONSTRAINT check_9c6c447a13 CHECK ((char_length(maintenance_mode_message) <= 255)), CONSTRAINT check_9c6c447a13 CHECK ((char_length(maintenance_mode_message) <= 255)),

View file

@ -83,7 +83,8 @@ Example response:
"raw_blob_request_limit": 300, "raw_blob_request_limit": 300,
"wiki_page_max_content_bytes": 52428800, "wiki_page_max_content_bytes": 52428800,
"require_admin_approval_after_user_signup": false, "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, "raw_blob_request_limit": 300,
"wiki_page_max_content_bytes": 52428800, "wiki_page_max_content_bytes": 52428800,
"require_admin_approval_after_user_signup": false, "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. | `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_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. | | `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.| | `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_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. | | `recaptcha_private_key` | string | required by: `recaptcha_enabled` | Private key for reCAPTCHA. |

View file

@ -79,9 +79,10 @@ package.
## Installing GitLab from source ## Installing GitLab from source
If the Omnibus GitLab package is not available in your distribution, you can If the Omnibus GitLab package isn't available for your distribution, you can
install GitLab from source: Useful for unsupported systems like \*BSD. For an install GitLab from source. This can be useful with unsupported systems, like
overview of the directory structure, read the [structure documentation](installation.md#gitlab-directory-structure). \*BSD. For an overview of the directory structure, see the
[structure documentation](installation.md#gitlab-directory-structure).
[**> Install GitLab from source.**](installation.md) [**> Install GitLab from source.**](installation.md)

View file

@ -16,7 +16,7 @@ as the hardware requirements that are needed to install and use GitLab.
- Ubuntu (16.04/18.04/20.04) - Ubuntu (16.04/18.04/20.04)
- Debian (9/10) - Debian (9/10)
- CentOS (6/7/8) - CentOS (7/8)
- openSUSE (Leap 15.1/Enterprise Server 12.2) - openSUSE (Leap 15.1/Enterprise Server 12.2)
- Red Hat Enterprise Linux (please use the CentOS packages and instructions) - Red Hat Enterprise Linux (please use the CentOS packages and instructions)
- Scientific 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. 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 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 ### Microsoft Windows
GitLab is developed for Linux-based operating systems. 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 #### Additional requirements for GitLab Geo
If you're using [GitLab Geo](../administration/geo/index.md): If you're using [GitLab Geo](../administration/geo/index.md), we strongly
recommend running Omnibus GitLab-managed instances, as we actively develop and
- We strongly recommend running Omnibus-managed instances as they are actively test based on those. We try to be compatible with most external (not managed by
developed and tested. We aim to be compatible with most external (not managed Omnibus GitLab) databases (for example, [AWS Relational Database Service (RDS)](https://aws.amazon.com/rds/)),
by Omnibus) databases (for example, [AWS Relational Database Service (RDS)](https://aws.amazon.com/rds/)) but we don't guarantee compatibility. but we can't guarantee compatibility.
## Puma settings ## Puma settings

View file

@ -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) ![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 ## Use an HTTP header to bypass rate limiting
> [Introduced](https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/622) in GitLab 13.6. > [Introduced](https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/622) in GitLab 13.6.

View file

@ -10,8 +10,17 @@ module Gitlab
def self.configure(rack_attack) def self.configure(rack_attack)
# This adds some methods used by our throttles to the `Rack::Request` # This adds some methods used by our throttles to the `Rack::Request`
rack_attack::Request.include(Gitlab::RackAttack::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 the throttles
configure_throttles(rack_attack) configure_throttles(rack_attack)

View file

@ -2,6 +2,8 @@
module Gitlab module Gitlab
class Throttle class Throttle
DEFAULT_RATE_LIMITING_RESPONSE_TEXT = 'Retry later'
def self.settings def self.settings
Gitlab::CurrentSettings.current_application_settings Gitlab::CurrentSettings.current_application_settings
end end
@ -46,5 +48,9 @@ module Gitlab
{ limit: limit_proc, period: period_proc } { limit: limit_proc, period: period_proc }
end end
def self.rate_limiting_response_text
(settings.rate_limiting_response_text.presence || DEFAULT_RATE_LIMITING_RESPONSE_TEXT) + "\n"
end
end end
end end

View file

@ -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" msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features"
msgstr "" 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." msgid "A platform value can be web, mob or app."
msgstr "" msgstr ""
@ -23928,6 +23931,9 @@ msgstr ""
msgid "Response metrics (NGINX)" msgid "Response metrics (NGINX)"
msgstr "" msgstr ""
msgid "Response text"
msgstr ""
msgid "Restart Terminal" msgid "Restart Terminal"
msgstr "" msgstr ""

View file

@ -108,6 +108,7 @@
"katex": "^0.10.0", "katex": "^0.10.0",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"marked": "^0.3.12", "marked": "^0.3.12",
"mathjax": "3",
"mermaid": "^8.5.2", "mermaid": "^8.5.2",
"mersenne-twister": "1.1.0", "mersenne-twister": "1.1.0",
"minimatch": "^3.0.4", "minimatch": "^3.0.4",

View file

@ -1,6 +1,9 @@
import Vuex from 'vuex'; import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils'; 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 createDiffsStore from '~/diffs/store/modules';
import createNotesStore from '~/notes/stores/modules'; import createNotesStore from '~/notes/stores/modules';
import diffFileMockDataReadable from '../mock_data/diff_file'; import diffFileMockDataReadable from '../mock_data/diff_file';
@ -118,14 +121,17 @@ const changeViewerType = (store, newType, index = 0) =>
describe('DiffFile', () => { describe('DiffFile', () => {
let wrapper; let wrapper;
let store; let store;
let axiosMock;
beforeEach(() => { beforeEach(() => {
axiosMock = new MockAdapter(axios);
({ wrapper, store } = createComponent({ file: getReadableFile() })); ({ wrapper, store } = createComponent({ file: getReadableFile() }));
}); });
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null; wrapper = null;
axiosMock.restore();
}); });
describe('bus events', () => { describe('bus events', () => {
@ -353,8 +359,10 @@ describe('DiffFile', () => {
describe('loading', () => { describe('loading', () => {
it('should have loading icon while loading a collapsed diffs', async () => { 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); makeFileAutomaticallyCollapsed(store);
wrapper.vm.isLoadingCollapsedDiff = true; wrapper.vm.requestDiff();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();

View file

@ -18,12 +18,14 @@ describe('Output component', () => {
}; };
beforeEach(() => { beforeEach(() => {
// This is the output after rendering a jupyter notebook
json = getJSONFixture('blob/notebook/basic.json'); json = getJSONFixture('blob/notebook/basic.json');
}); });
describe('text output', () => { describe('text output', () => {
beforeEach((done) => { beforeEach((done) => {
createComponent(json.cells[2].outputs[0]); const textType = json.cells[2];
createComponent(textType.outputs[0]);
setImmediate(() => { setImmediate(() => {
done(); done();
@ -41,7 +43,8 @@ describe('Output component', () => {
describe('image output', () => { describe('image output', () => {
beforeEach((done) => { beforeEach((done) => {
createComponent(json.cells[3].outputs[0]); const imageType = json.cells[3];
createComponent(imageType.outputs[0]);
setImmediate(() => { setImmediate(() => {
done(); done();
@ -55,23 +58,42 @@ describe('Output component', () => {
describe('html output', () => { describe('html output', () => {
it('renders raw HTML', () => { 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.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'); expect(vm.$el.textContent.trim()).toContain('test');
}); });
it('renders multiple raw HTML outputs', () => { 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', () => { describe('svg output', () => {
beforeEach((done) => { beforeEach((done) => {
createComponent(json.cells[5].outputs[0]); const svgType = json.cells[5];
createComponent(svgType.outputs[0]);
setImmediate(() => { setImmediate(() => {
done(); done();
@ -85,7 +107,8 @@ describe('Output component', () => {
describe('default to plain text', () => { describe('default to plain text', () => {
beforeEach((done) => { beforeEach((done) => {
createComponent(json.cells[6].outputs[0]); const unknownType = json.cells[6];
createComponent(unknownType.outputs[0]);
setImmediate(() => { setImmediate(() => {
done(); done();
@ -102,7 +125,8 @@ describe('Output component', () => {
}); });
it("renders as plain text when doesn't recognise other types", (done) => { 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(() => { setImmediate(() => {
expect(vm.$el.querySelector('pre')).not.toBeNull(); expect(vm.$el.querySelector('pre')).not.toBeNull();

View file

@ -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('$$'));
});
});

View file

@ -22,8 +22,7 @@ RSpec.describe Gitlab::RackAttack, :aggregate_failures do
stub_const("Rack::Attack", fake_rack_attack) stub_const("Rack::Attack", fake_rack_attack)
stub_const("Rack::Attack::Request", fake_rack_attack_request) stub_const("Rack::Attack::Request", fake_rack_attack_request)
# Expect rather than just allow, because this is actually fairly important functionality allow(fake_rack_attack).to receive(:throttled_response=)
expect(fake_rack_attack).to receive(:throttled_response_retry_after_header=).with(true)
allow(fake_rack_attack).to receive(:throttle) allow(fake_rack_attack).to receive(:throttle)
allow(fake_rack_attack).to receive(:track) allow(fake_rack_attack).to receive(:track)
allow(fake_rack_attack).to receive(:safelist) 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) expect(fake_rack_attack_request).to include(described_class::Request)
end 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 it 'configures the safelist' do
described_class.configure(fake_rack_attack) described_class.configure(fake_rack_attack)

View file

@ -30,4 +30,32 @@ RSpec.describe Gitlab::Throttle do
end end
end 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 end

View file

@ -193,10 +193,8 @@ RSpec.describe 'Git LFS API and storage' do
subject(:request) { post_lfs_json batch_url(project), body, headers } subject(:request) { post_lfs_json batch_url(project), body, headers }
let(:response) { request && super() } let(:response) { request && super() }
let(:lfs_chunked_encoding) { true }
before do before do
stub_feature_flags(lfs_chunked_encoding: lfs_chunked_encoding)
project.lfs_objects << lfs_object project.lfs_objects << lfs_object
end end
@ -480,20 +478,6 @@ RSpec.describe 'Git LFS API and storage' do
expect(headers['Transfer-Encoding']).to eq('chunked') expect(headers['Transfer-Encoding']).to eq('chunked')
end 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 it_behaves_like 'process authorization header', renew_authorization: renew_authorization
end end

View file

@ -60,6 +60,24 @@ RSpec.describe 'Rack Attack global throttles' do
expect_rejection { get url_that_does_not_require_authentication } expect_rejection { get url_that_does_not_require_authentication }
end 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 it 'allows requests after throttling and then waiting for the next period' do
requests_per_period.times do requests_per_period.times do
get url_that_does_not_require_authentication get url_that_does_not_require_authentication

View file

@ -25,6 +25,7 @@ module RackAttackSpecHelpers
yield yield
expect(response).to have_gitlab_http_status(:too_many_requests) expect(response).to have_gitlab_http_status(:too_many_requests)
expect(response).to have_header('Retry-After')
end end
def expect_ok(&block) def expect_ok(&block)

View file

@ -3057,7 +3057,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies: dependencies:
delayed-stream "~1.0.0" 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" version "2.20.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
@ -7476,11 +7476,11 @@ karma@^4.2.0:
useragent "2.3.0" useragent "2.3.0"
katex@^0.10.0: katex@^0.10.0:
version "0.10.0" version "0.10.2"
resolved "https://registry.yarnpkg.com/katex/-/katex-0.10.0.tgz#da562e5d0d5cc3aa602e27af8a9b8710bfbce765" resolved "https://registry.yarnpkg.com/katex/-/katex-0.10.2.tgz#39973edbb65eda5b6f9e7f41648781e557dd4932"
integrity sha512-/WRvx+L1eVBrLwX7QzKU1dQuaGnE7E8hDvx3VWfZh9HbMiCfsKWJNnYZ0S8ZMDAfAyDSofdyXIrH/hujF1fYXg== integrity sha512-cQOmyIRoMloCoSIOZ1+gEwsksdJZ1EW4SWm3QzxSza/QsnZr6D4U1V9S4q+B/OLm2OQ8TCBecQ8MaIfnScI7cw==
dependencies: dependencies:
commander "^2.16.0" commander "^2.19.0"
keyv@^3.0.0: keyv@^3.0.0:
version "3.1.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" resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790"
integrity sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg== 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: mathml-tag-names@^2.1.0:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.1.tgz#6dff66c99d55ecf739ca53c492e626f1d12a33cc" resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.1.tgz#6dff66c99d55ecf739ca53c492e626f1d12a33cc"