Commit 2dbed5d6 authored by Robert Speicher's avatar Robert Speicher

Merge branch 'ce-to-ee-2018-10-02' into 'master'

CE upstream - 2018-10-02 18:21 UTC

Closes gitaly#1319

See merge request gitlab-org/gitlab-ee!7774
parents e101ac31 4f72e2b1
6.1.1
7.0.0
\ No newline at end of file
/**
* Highlights the current user in existing elements with a user ID data attribute.
*
* @param elements DOM elements that represent user mentions
*/
export default function highlightCurrentUser(elements) {
const currentUserId = gon && gon.current_user_id;
if (!currentUserId) {
return;
}
elements.forEach(element => {
if (parseInt(element.dataset.user, 10) === currentUserId) {
element.classList.add('current-user');
}
});
}
......@@ -2,6 +2,7 @@ import $ from 'jquery';
import syntaxHighlight from '~/syntax_highlight';
import renderMath from './render_math';
import renderMermaid from './render_mermaid';
import highlightCurrentUser from './highlight_current_user';
// Render GitLab flavoured Markdown
//
......@@ -11,6 +12,7 @@ $.fn.renderGFM = function renderGFM() {
syntaxHighlight(this.find('.js-syntax-highlight'));
renderMath(this.find('.js-render-math'));
renderMermaid(this.find('.js-render-mermaid'));
highlightCurrentUser(this.find('.gfm-project_member').get());
return this;
};
......
......@@ -31,11 +31,17 @@ function blockTagText(text, textArea, blockTag, selected) {
}
}
function moveCursor(textArea, tag, wrapped, removedLastNewLine) {
function moveCursor({ textArea, tag, wrapped, removedLastNewLine, select }) {
var pos;
if (!textArea.setSelectionRange) {
return;
}
if (select && select.length > 0) {
// calculate the part of the text to be selected
const startPosition = textArea.selectionStart - (tag.length - tag.indexOf(select));
const endPosition = startPosition + select.length;
return textArea.setSelectionRange(startPosition, endPosition);
}
if (textArea.selectionStart === textArea.selectionEnd) {
if (wrapped) {
pos = textArea.selectionStart - tag.length;
......@@ -51,7 +57,7 @@ function moveCursor(textArea, tag, wrapped, removedLastNewLine) {
}
}
export function insertMarkdownText(textArea, text, tag, blockTag, selected, wrap) {
export function insertMarkdownText({ textArea, text, tag, blockTag, selected, wrap, select }) {
var textToInsert, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine;
removedLastNewLine = false;
removedFirstNewLine = false;
......@@ -82,11 +88,16 @@ export function insertMarkdownText(textArea, text, tag, blockTag, selected, wrap
startChar = !wrap && !currentLineEmpty && textArea.selectionStart > 0 ? '\n' : '';
const textPlaceholder = '{text}';
if (selectedSplit.length > 1 && (!wrap || (blockTag != null && blockTag !== ''))) {
if (blockTag != null && blockTag !== '') {
textToInsert = blockTagText(text, textArea, blockTag, selected);
} else {
textToInsert = selectedSplit.map(function(val) {
if (tag.indexOf(textPlaceholder) > -1) {
return tag.replace(textPlaceholder, val);
}
if (val.indexOf(tag) === 0) {
return "" + (val.replace(tag, ''));
} else {
......@@ -94,6 +105,8 @@ export function insertMarkdownText(textArea, text, tag, blockTag, selected, wrap
}
}).join('\n');
}
} else if (tag.indexOf(textPlaceholder) > -1) {
textToInsert = tag.replace(textPlaceholder, selected);
} else {
textToInsert = "" + startChar + tag + selected + (wrap ? tag : ' ');
}
......@@ -107,17 +120,17 @@ export function insertMarkdownText(textArea, text, tag, blockTag, selected, wrap
}
insertText(textArea, textToInsert);
return moveCursor(textArea, tag, wrap, removedLastNewLine);
return moveCursor({ textArea, tag: tag.replace(textPlaceholder, selected), wrap, removedLastNewLine, select });
}
function updateText(textArea, tag, blockTag, wrap) {
function updateText({ textArea, tag, blockTag, wrap, select }) {
var $textArea, selected, text;
$textArea = $(textArea);
textArea = $textArea.get(0);
text = $textArea.val();
selected = selectedText(text, textArea);
$textArea.focus();
return insertMarkdownText(textArea, text, tag, blockTag, selected, wrap);
return insertMarkdownText({ textArea, text, tag, blockTag, selected, wrap, select });
}
function replaceRange(s, start, end, substitute) {
......@@ -127,7 +140,12 @@ function replaceRange(s, start, end, substitute) {
export function addMarkdownListeners(form) {
return $('.js-md', form).off('click').on('click', function() {
const $this = $(this);
return updateText($this.closest('.md-area').find('textarea'), $this.data('mdTag'), $this.data('mdBlock'), !$this.data('mdPrepend'));
return updateText({
textArea: $this.closest('.md-area').find('textarea'),
tag: $this.data('mdTag'),
blockTag: $this.data('mdBlock'),
wrap: !$this.data('mdPrepend'),
select: $this.data('mdSelect') });
});
}
......
......@@ -11,6 +11,7 @@ import commentForm from './comment_form.vue';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
import skeletonLoadingContainer from '../../vue_shared/components/notes/skeleton_note.vue';
import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
export default {
name: 'NotesApp',
......@@ -96,6 +97,9 @@ export default {
});
}
},
updated() {
this.$nextTick(() => highlightCurrentUser(this.$el.querySelectorAll('.gfm-project_member')));
},
methods: {
...mapActions({
fetchDiscussions: 'fetchDiscussions',
......
......@@ -105,6 +105,12 @@
button-title="Insert code"
icon="code"
/>
<toolbar-button
tag="[{text}](url)"
tag-select="url"
button-title="Add a link"
icon="link"
/>
<toolbar-button
:prepend="true"
tag="* "
......
......@@ -27,6 +27,11 @@
required: false,
default: '',
},
tagSelect: {
type: String,
required: false,
default: '',
},
prepend: {
type: Boolean,
required: false,
......@@ -40,6 +45,7 @@
<button
v-tooltip
:data-md-tag="tag"
:data-md-select="tagSelect"
:data-md-block="tagBlock"
:data-md-prepend="prepend"
:title="buttonTitle"
......
......@@ -11,6 +11,10 @@
padding: 0 2px;
background-color: $blue-100;
border-radius: $border-radius-default;
&.current-user {
background-color: $orange-100;
}
}
.gfm-color_chip {
......
......@@ -14,6 +14,8 @@ class Projects::ArtifactsController < Projects::ApplicationController
before_action :entry, only: [:file]
def download
return render_404 unless artifacts_file
send_upload(artifacts_file, attachment: artifacts_file.filename)
end
......@@ -100,7 +102,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
# rubocop: enable CodeReuse/ActiveRecord
def artifacts_file
@artifacts_file ||= build.artifacts_file
@artifacts_file ||= build.artifacts_file_for_type(params[:file_type] || :archive)
end
def entry
......
......@@ -255,7 +255,8 @@ module ApplicationSettingsHelper
:user_default_internal_regex,
:user_oauth_applications,
:version_check_enabled,
:web_ide_clientside_preview_enabled
:web_ide_clientside_preview_enabled,
:diff_max_patch_bytes
]
end
......
......@@ -329,11 +329,15 @@ module IssuablesHelper
end
def issuable_button_visibility(issuable, closed)
return 'hidden' if issuable_button_hidden?(issuable, closed)
end
def issuable_button_hidden?(issuable, closed)
case issuable
when Issue
issue_button_visibility(issuable, closed)
issue_button_hidden?(issuable, closed)
when MergeRequest
merge_request_button_visibility(issuable, closed)
merge_request_button_hidden?(issuable, closed)
end
end
......
......@@ -66,7 +66,11 @@ module IssuesHelper
end
def issue_button_visibility(issue, closed)
return 'hidden' if issue.closed? == closed
return 'hidden' if issue_button_hidden?(issue, closed)
end
def issue_button_hidden?(issue, closed)
issue.closed? == closed || (!closed && issue.discussion_locked)
end
def confidential_icon(issue)
......
......@@ -82,7 +82,11 @@ module MergeRequestsHelper
end
def merge_request_button_visibility(merge_request, closed)
return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork?
return 'hidden' if merge_request_button_hidden?(merge_request, closed)
end
def merge_request_button_hidden?(merge_request, closed)
merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork?
end
def merge_request_version_path(project, merge_request, merge_request_diff, start_sha = nil)
......
......@@ -183,6 +183,12 @@ class ApplicationSetting < ActiveRecord::Base
numericality: { less_than_or_equal_to: :gitaly_timeout_default },
if: :gitaly_timeout_default
validates :diff_max_patch_bytes,
presence: true,
numericality: { only_integer: true,
greater_than_or_equal_to: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
less_than_or_equal_to: Gitlab::Git::Diff::MAX_PATCH_BYTES_UPPER_BOUND }
validates :user_default_internal_regex, js_regex: true, allow_nil: true
SUPPORTED_KEY_TYPES.each do |type|
......@@ -294,7 +300,8 @@ class ApplicationSetting < ActiveRecord::Base
user_default_external: false,
user_default_internal_regex: nil,
user_show_add_ssh_key_message: true,
usage_stats_set_by_user_id: nil
usage_stats_set_by_user_id: nil,
diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES
}
end
......
......@@ -526,6 +526,13 @@ module Ci
self.job_artifacts.update_all(expire_at: nil)
end
def artifacts_file_for_type(type)
file = job_artifacts.find_by(file_type: Ci::JobArtifact.file_types[type])&.file
# TODO: to be removed once legacy artifacts is removed
file ||= legacy_artifacts_file if type == :archive
file
end
def coverage_regex
super || project.try(:build_coverage_regex)
end
......
......@@ -17,6 +17,7 @@ module Ci
metadata: nil,
trace: nil,
junit: 'junit.xml',
codequality: 'codequality.json',
sast: 'gl-sast-report.json',
dependency_scanning: 'gl-dependency-scanning-report.json',
container_scanning: 'gl-container-scanning-report.json',
......@@ -28,6 +29,7 @@ module Ci
metadata: :gzip,
trace: :raw,
junit: :gzip,
codequality: :gzip,
sast: :gzip,
dependency_scanning: :gzip,
container_scanning: :gzip,
......@@ -76,7 +78,8 @@ module Ci
sast: 5, ## EE-specific
dependency_scanning: 6, ## EE-specific
container_scanning: 7, ## EE-specific
dast: 8 ## EE-specific
dast: 8, ## EE-specific
codequality: 9 ## EE-specific
}
enum file_format: {
......
......@@ -5,8 +5,10 @@ module Storage
extend ActiveSupport::Concern
def move_dir
if any_project_has_container_registry_tags?
raise Gitlab::UpdatePathError.new('Namespace cannot be moved, because at least one project has tags in container registry')
proj_with_tags = first_project_with_container_registry_tags
if proj_with_tags
raise Gitlab::UpdatePathError.new("Namespace #{name} (#{id}) cannot be moved because at least one project (e.g. #{proj_with_tags.name} (#{proj_with_tags.id})) has tags in container registry")
end
parent_was = if parent_changed? && parent_id_was.present?
......
......@@ -136,6 +136,10 @@ class Namespace < ActiveRecord::Base
all_projects.any?(&:has_container_registry_tags?)
end
def first_project_with_container_registry_tags
all_projects.find(&:has_container_registry_tags?)
end
def send_update_instructions
projects.each do |project|
project.send_move_instructions("#{full_path_was}/#{project.path}")
......
......@@ -1381,6 +1381,18 @@ class Project < ActiveRecord::Base
end
end
# Filters `users` to return only authorized users of the project
def members_among(users)
if users.is_a?(ActiveRecord::Relation) && !users.loaded?
authorized_users.merge(users)
else
return [] if users.empty?
user_ids = authorized_users.where(users: { id: users.map(&:id) }).pluck(:id)
users.select { |user| user_ids.include?(user.id) }
end
end
def default_branch
@default_branch ||= repository.root_ref if repository.exists?
end
......
= form_for @application_setting, url: admin_application_settings_path(anchor: 'js-merge-request-settings'), html: { class: 'fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
= f.label :diff_max_patch_bytes, 'Maximum diff patch size (Bytes)', class: 'label-light'
= f.number_field :diff_max_patch_bytes, class: 'form-control'
%span.form-text.text-muted
Diff files surpassing this limit will be presented as 'too large'
and won't be expandable.
= link_to icon('question-circle'),
help_page_path('user/admin_area/diff_limits',
anchor: 'maximum-diff-patch-size')
= f.submit _('Save changes'), class: 'btn btn-success'
......@@ -24,6 +24,17 @@
.settings-content
= render 'account_and_limit'
%section.settings.as-diff-limits.no-animate#js-merge-request-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
= _('Diff limits')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded_by_default? ? _('Collapse') : _('Expand')
%p
= _('Diff content limits')
.settings-content
= render 'diff_limits'
%section.settings.as-signup.no-animate#js-signup-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
......
......@@ -18,14 +18,15 @@
Preview
%li.md-header-toolbar.active
= markdown_toolbar_button({ icon: "bold", data: { "md-tag" => "**" }, title: "Add bold text" })
= markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "*" }, title: "Add italic text" })
= markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: "Insert a quote" })
= markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: "Insert code" })
= markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "* ", "md-prepend" => true }, title: "Add a bullet list" })
= markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" })
= markdown_toolbar_button({ icon: "task-done", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" })
%button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: "Go full screen", data: { container: "body" } }
= markdown_toolbar_button({ icon: "bold", data: { "md-tag" => "**" }, title: s_("MarkdownToolbar|Add bold text") })
= markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "*" }, title: s_("MarkdownToolbar|Add italic text") })
= markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: s_("MarkdownToolbar|Insert a quote") })
= markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: s_("MarkdownToolbar|Insert code") })
= markdown_toolbar_button({ icon: "link", data: { "md-tag" => "[{text}](url)", "md-select" => "url" }, title: s_("MarkdownToolbar|Add a link") })
= markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "* ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a bullet list") })
= markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a numbered list") })
= markdown_toolbar_button({ icon: "task-done", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a task list") })
%button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: s_("MarkdownToolbar|Go full screen"), data: { container: "body" } }
= sprite_icon("screen-full")
.md-write-holder
......
.group-empty-state.row.align-items-center.justify-content-center.qa-groups-empty-state
.group-empty-state.row.align-items-center.justify-content-center
.icon.text-center.order-md-2
= custom_icon("icon_empty_groups")
......
= form_tag request.path, method: :get, class: "group-filter-form js-group-filter-form", id: 'group-filter-form' do |f|
= search_field_tag :filter, params[:filter], placeholder: s_('GroupsTree|Search by name'), class: 'group-filter-form-field form-control js-groups-list-filter', spellcheck: false, id: 'group-filter-form-field', tabindex: "2"
= search_field_tag :filter, params[:filter], placeholder: s_('GroupsTree|Search by name'), class: 'group-filter-form-field form-control js-groups-list-filter qa-groups-filter', spellcheck: false, id: 'group-filter-form-field', tabindex: "2"
- is_current_user = issuable_author_is_current_user(issuable)
- display_issuable_type = issuable_display_type(issuable)
- button_method = issuable_close_reopen_button_method(issuable)
- are_close_and_open_buttons_hidden = issuable_button_hidden?(issuable, true) && issuable_button_hidden?(issuable, false)
- if can_update
- if is_current_user
- if is_current_user
- if can_update
= link_to "Close #{display_issuable_type}", close_issuable_path(issuable), method: button_method,
class: "d-none d-sm-none d-md-block btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)}", title: "Close #{display_issuable_type}"
- else
= render 'shared/issuable/close_reopen_report_toggle', issuable: issuable
- if can_reopen && is_current_user
- if can_reopen
= link_to "Reopen #{display_issuable_type}", reopen_issuable_path(issuable), method: button_method,
class: "d-none d-sm-none d-md-block btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}"
- else
- if can_update && !are_close_and_open_buttons_hidden
= render 'shared/issuable/close_reopen_report_toggle', issuable: issuable
- else
= link_to 'Report abuse', new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
class: 'd-none d-sm-none d-md-block btn btn-grouped btn-close-color', title: 'Report abuse'
---
title: Add link button to markdown editor toolbar
merge_request: 18579
author: Jan Beckmann
type: added
---
title: Hides Close Merge request btn on merged Merge request
merge_request: 21840
author: Jacopo Beschi @jacopo-beschi
type: fixed
---
title: Fix migration to avoid an exception during upgrade
merge_request: 22055
author:
type: fixed
---
title: Use local tiller for Auto DevOps
merge_request: 22036
author:
type: changed
---
title: Show SHA for pre-release versions on the help page
merge_request: 22026
author:
type: changed
---
title: Make single diff patch limit configurable
merge_request: 21886
author:
type: added
---
title: Improve logging when username update fails due to registry tags
merge_request: 22038
author:
type: other
---
title: Highlight current user in comments
merge_request: 21406
author:
type: changed
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddDiffMaxPatchBytesToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default(:application_settings,
:diff_max_patch_bytes,
:integer,
default: 100.kilobytes,
allow_null: false)
end
def down
remove_column(:application_settings, :diff_max_patch_bytes)
end
end
......@@ -5,6 +5,8 @@ class RenameLoginRootNamespaces < ActiveRecord::Migration
DOWNTIME = false
disable_ddl_transaction!
# We're taking over the /login namespace as part of a fix for the Jira integration
def up
disable_statement_timeout do
......
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180924070647) do
ActiveRecord::Schema.define(version: 20180924141949) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -217,6 +217,7 @@ ActiveRecord::Schema.define(version: 20180924070647) do
t.integer "custom_project_templates_group_id"
t.integer "usage_stats_set_by_user_id"
t.integer "receive_max_input_size"
t.integer "diff_max_patch_bytes", default: 102400, null: false
end
create_table "approvals", force: :cascade do |t|
......
......@@ -56,6 +56,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Enforcing Terms of Service](../user/admin_area/settings/terms.md)
- [Third party offers](../user/admin_area/settings/third_party_offers.md)
- [Compliance](compliance.md): A collection of features from across the application that you may configure to help ensure that your GitLab instance and DevOps workflow meet compliance standards.
- [Diff limits](../user/admin_area/diff_limits.md): Configure the diff rendering size limits of branch comparison pages.
#### Customizing GitLab's appearance
......
......@@ -2,13 +2,10 @@
Currently we rely on different sources to present diffs, these include:
- Rugged gem
- Gitaly service
- Database (through `merge_request_diff_files`)
- Redis (cached highlighted diffs)
We're constantly moving Rugged calls to Gitaly and the progress can be followed through [Gitaly repo](https://gitlab.com/gitlab-org/gitaly).
## Architecture overview
### Merge request diffs
......@@ -19,8 +16,9 @@ we fetch the comparison information using `Gitlab::Git::Compare`, which fetches
The diffs fetching process _limits_ single file diff sizes and the overall size of the whole diff through a series of constant values. Raw diff files are
then persisted on `merge_request_diff_files` table.
Even though diffs higher than 10kb are collapsed (`Gitlab::Git::Diff::COLLAPSE_LIMIT`), we still keep them on Postgres. However, diff files over _safety limits_
(see the [Diff limits section](#diff-limits)) are _not_ persisted.
Even though diffs larger than 10% of the value of `ApplicationSettings#diff_max_patch_bytes` are collapsed,
we still keep them on Postgres. However, diff files larger than defined _safety limits_
(see the [Diff limits section](#diff-limits)) are _not_ persisted in the database.
In order to present diffs information on the Merge Request diffs page, we:
......@@ -102,23 +100,20 @@ Gitaly will only return the safe amount of data to be persisted on `merge_reques
Limits that act onto each diff file of a collection. Files number, lines number and files size are considered.
```ruby
Gitlab::Git::Diff::COLLAPSE_LIMIT = 10.kilobytes
```
#### Expandable patches (collapsed)
File diff will be collapsed (but be expandable) if it is larger than 10 kilobytes.
Diff patches are collapsed when surpassing 10% of the value set in `ApplicationSettings#diff_max_patch_bytes`.
That is, it's equivalent to 10kb if the maximum allowed value is 100kb.
The diff will still be persisted and expandable if the patch size doesn't
surpass `ApplicationSettings#diff_max_patch_bytes`.
*Note:* Although this nomenclature (Collapsing) is also used on Gitaly, this limit is only used on GitLab (hardcoded - not sent to Gitaly).
Gitaly will only return `Diff.Collapsed` (RPC) when surpassing collection limits.
```ruby
Gitlab::Git::Diff::SIZE_LIMIT = 100.kilobytes
```
File diff will not be rendered if it's larger than 100 kilobytes.
#### Not expandable patches (too large)
*Note:* This limit is currently hardcoded and applied on Gitaly and the RPC returns `Diff.TooLarge` when this limit is surpassed.
Although we're still also applying it on GitLab, we should remove the redundancy from GitLab once we're confident with the Gitaly integration.
The patch not be rendered if it's larger than `ApplicationSettings#diff_max_patch_bytes`.
Users will see a `This source diff could not be displayed because it is too large` message.
```ruby
Commit::DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000
......
# Diff limits administration
NOTE: **Note:**
Merge requests and branch comparison views will be affected.
CAUTION: **Caution:**
These settings are currently under experimental state. They'll
increase the resource consumption of your instance and should
be edited mindfully.
1. Access **Admin area > Settings > General**
1. Expand **Diff limits**
### Maximum diff patch size
This is the content size each diff file (patch) is allowed to reach before
it's collapsed, without the possibility of being expanded. A link redirecting
to the blob view will be presented for the patches that surpass this limit.
Patches surpassing 10% of this content size will be automatically collapsed,
but expandable (a link to expand the diff will be presented).
......@@ -11,7 +11,7 @@ module Gitlab
include Validatable
include Attributable
ALLOWED_KEYS = %i[junit sast dependency_scanning container_scanning dast].freeze
ALLOWED_KEYS = %i[junit codequality sast dependency_scanning container_scanning dast].freeze
attributes ALLOWED_KEYS
......@@ -21,6 +21,7 @@ module Gitlab
with_options allow_nil: true do
validates :junit, array_of_strings_or_string: true
validates :codequality, array_of_strings_or_string: true
validates :sast, array_of_strings_or_string: true
validates :dependency_scanning, array_of_strings_or_string: true
validates :container_scanning, array_of_strings_or_string: true
......
......@@ -49,7 +49,7 @@ variables:
POSTGRES_DB: $CI_ENVIRONMENT_SLUG
KUBERNETES_VERSION: 1.8.6
HELM_VERSION: 2.10.0
HELM_VERSION: 2.11.0
DOCKER_DRIVER: overlay2
......@@ -239,7 +239,7 @@ review:
- install_dependencies
- download_chart
- ensure_namespace
- install_tiller
- initialize_tiller
- create_secret
- deploy
- persist_environment_url
......@@ -265,6 +265,7 @@ stop_review:
GIT_STRATEGY: none
script:
- install_dependencies
- initialize_tiller
- delete
environment:
name: review/$CI_COMMIT_REF_NAME
......@@ -299,7 +300,7 @@ staging:
- install_dependencies
- download_chart
- ensure_namespace
- install_tiller
- initialize_tiller
- create_secret
- deploy
environment:
......@@ -323,7 +324,7 @@ canary:
- install_dependencies
- download_chart
- ensure_namespace
- install_tiller
- initialize_tiller
- create_secret
- deploy canary
environment:
......@@ -344,7 +345,7 @@ canary:
- install_dependencies
- download_chart
- ensure_namespace
- install_tiller
- initialize_tiller
- create_secret
- deploy
- delete canary
......@@ -392,7 +393,7 @@ production_manual:
- install_dependencies
- download_chart
- ensure_namespace
- install_tiller
- initialize_tiller
- create_secret
- deploy rollout $ROLLOUT_PERCENTAGE
- scale stable $((100-ROLLOUT_PERCENTAGE))
......@@ -651,7 +652,12 @@ rollout 100%:
curl "https://kubernetes-helm.storage.googleapis.com/helm-v${HELM_VERSION}-linux-amd64.tar.gz" | tar zx
mv linux-amd64/helm /usr/bin/
mv linux-amd64/tiller /usr/bin/
helm version --client
tiller -version
helm init --client-only
helm plugin install https://github.com/adamreese/helm-local
curl -L -o /usr/bin/kubectl "https://storage.googleapis.com/kubernetes-release/release/v${KUBERNETES_VERSION}/bin/linux/amd64/kubectl"
chmod +x /usr/bin/kubectl
......@@ -758,10 +764,13 @@ rollout 100%:
echo ""
}
function install_tiller() {
function initialize_tiller() {
echo "Checking Tiller..."
helm init --upgrade
kubectl rollout status -n "$TILLER_NAMESPACE" -w "deployment/tiller-deploy"
helm local start
helm local status
export HELM_HOST=":44134"
if ! helm version --debug; then
echo "Failed to init Tiller."
return 1
......
......@@ -19,13 +19,17 @@ module Gitlab
alias_method :expanded?, :expanded
SERIALIZE_KEYS = %i(diff new_path old_path a_mode b_mode new_file renamed_file deleted_file too_large).freeze
# The default maximum content size to display a diff patch.
#
# If this value ever changes, make sure to create a migration to update
# current records, and default of `ApplicationSettings#diff_max_patch_bytes`.
DEFAULT_MAX_PATCH_BYTES = 100.kilobytes
# The maximum size of a diff to display.
SIZE_LIMIT = 100.kilobytes
# This is a limitation applied on the source (Gitaly), therefore we don't allow
# persisting limits over that.
MAX_PATCH_BYTES_UPPER_BOUND = 500.kilobytes
# The maximum size before a diff is collapsed.
COLLAPSE_LIMIT = 10.kilobytes
SERIALIZE_KEYS = %i(diff new_path old_path a_mode b_mode new_file renamed_file deleted_file too_large).freeze
class << self
def between(repo, head, base, options = {}, *paths)
......@@ -105,6 +109,26 @@ module Gitlab
def binary_message(old_path, new_path)
"Binary files #{old_path} and #{new_path} differ\n"
end
# Returns the limit of bytes a single diff file can reach before it
# appears as 'collapsed' for end-users.
# By convention, it's 10% of the persisted `diff_max_patch_bytes`.
#
# Example: If we have 100k for the `diff_max_patch_bytes`, it will be 10k by
# default.
#
# Patches surpassing this limit should still be persisted in the database.
def patch_safe_limit_bytes
patch_hard_limit_bytes / 10
end
# Returns the limit for a single diff file (patch).
#
# Patches surpassing this limit shouldn't be persisted in the database
# and will be presented as 'too large' for end-users.
def patch_hard_limit_bytes
Gitlab::CurrentSettings.diff_max_patch_bytes
end
end
def initialize(raw_diff, expanded: true)
......@@ -150,7 +174,7 @@ module Gitlab
def too_large?
if @too_large.nil?
@too_large = @diff.bytesize >= SIZE_LIMIT
@too_large = @diff.bytesize >= self.class.patch_hard_limit_bytes
else
@too_large
end
......@@ -168,7 +192,7 @@ module Gitlab
def collapsed?
return @collapsed if defined?(@collapsed)
@collapsed = !expanded && @diff.bytesize >= COLLAPSE_LIMIT
@collapsed = !expanded && @diff.bytesize >= self.class.patch_safe_limit_bytes
end
def collapse!
......@@ -219,30 +243,6 @@ module Gitlab
collapse!
end
end
# If the patch surpasses any of the diff limits it calls the appropiate
# prune method and returns true. Otherwise returns false.
def prune_large_patch(patch)
size = 0
patch.each_hunk do |hunk|
hunk.each_line do |line|
size += line.content.bytesize
if size >= SIZE_LIMIT
too_large!
return true # rubocop:disable Cop/AvoidReturnFromBlocks
end
end
end
if !expanded && size >= COLLAPSE_LIMIT
collapse!
return true
end
false
end
end
end
end
......@@ -19,7 +19,7 @@ module Gitlab
limits[:safe_max_files] = [limits[:max_files], DEFAULT_LIMITS[:max_files]].min
limits[:safe_max_lines] = [limits[:max_lines], DEFAULT_LIMITS[:max_lines]].min
limits[:safe_max_bytes] = limits[:safe_max_files] * 5.kilobytes # Average 5 KB per file
limits[:max_patch_bytes] = Gitlab::Git::Diff::SIZE_LIMIT
limits[:max_patch_bytes] = Gitlab::Git::Diff.patch_hard_limit_bytes
OpenStruct.new(limits)
end
......
......@@ -2643,6 +2643,12 @@ msgstr ""
msgid "Detect host keys"
msgstr ""
msgid "Diff content limits"
msgstr ""
msgid "Diff limits"
msgstr ""
msgid "Diffs|No file name available"
msgstr ""
......@@ -4595,6 +4601,33 @@ msgstr ""
msgid "Markdown enabled"
msgstr ""
msgid "MarkdownToolbar|Add a bullet list"
msgstr ""
msgid "MarkdownToolbar|Add a link"
msgstr ""
msgid "MarkdownToolbar|Add a numbered list"
msgstr ""
msgid "MarkdownToolbar|Add a task list"
msgstr ""
msgid "MarkdownToolbar|Add bold text"
msgstr ""
msgid "MarkdownToolbar|Add italic text"
msgstr ""
msgid "MarkdownToolbar|Go full screen"
msgstr ""
msgid "MarkdownToolbar|Insert a quote"
msgstr ""
msgid "MarkdownToolbar|Insert code"
msgstr ""
msgid "Maven Metadata"
msgstr ""
......
......@@ -6,12 +6,7 @@ module QA
module GroupsFilter
def self.included(base)
base.view 'app/views/shared/groups/_search_form.html.haml' do
element :groups_filter, 'search_field_tag :filter'
element :groups_filter_placeholder, 'Search by name'
end
base.view 'app/views/shared/groups/_empty_state.html.haml' do
element :groups_empty_state
element :groups_filter
end
base.view 'app/assets/javascripts/groups/components/groups.vue' do
......@@ -21,13 +16,22 @@ module QA
private
def filter_by_name(name)
def has_filtered_group?(name)
# Filter and submit to reload the page and only retrieve the filtered results
find_element(:groups_filter).set(name).send_keys(:return)
# Since we submitted after filtering, the presence of
# groups_list_tree_container means we have the complete filtered list
# of groups
wait(reload: false) do
page.has_css?(element_selector_css(:groups_empty_state)) ||
page.has_css?(element_selector_css(:groups_list_tree_container))
end
fill_in 'Search by name', with: name
# If there are no groups we'll know immediately because we filtered the list
return false if page.has_text?('No groups or projects matched your search', wait: 0)
# The name will be present as filter input so we check for a link, not text
page.has_link?(name, wait: 0)
end
end
end
......
# frozen_string_literal: true
module QA
module Page
module Dashboard
......@@ -14,9 +16,7 @@ module QA
end
def has_group?(name)
filter_by_name(name)
page.has_link?(name)
has_filtered_group?(name)
end
def go_to_group(name)
......
# frozen_string_literal: true
module QA
module Page
module Group
......@@ -25,11 +27,7 @@ module QA
end
def has_subgroup?(name)
filter_by_name(name)
page.has_text?(/#{name}|No groups or projects matched your search/, wait: 60)
page.has_text?(name, wait: 0)
has_filtered_group?(name)
end
def go_to_new_subgroup
......
......@@ -19,10 +19,42 @@ describe Projects::ArtifactsController do
end
describe 'GET download' do
subject { get :download, namespace_id: project.namespace, project_id: project, job_id: job, file_type: file_type }
context 'when no file type is supplied' do
let(:file_type) { nil }
it 'sends the artifacts file' do
expect(controller).to receive(:send_file).with(job.artifacts_file.path, hash_including(disposition: 'attachment')).and_call_original
get :download, namespace_id: project.namespace, project_id: project, job_id: job
subject
end
end
context 'when a file type is supplied' do
context 'when an invalid file type is supplied' do
let(:file_type) { 'invalid' }
it 'returns 404' do
subject
expect(response).to have_gitlab_http_status(404)
end
end
context 'when codequality file type is supplied' do
let(:file_type) { 'codequality' }
before do
create(:ci_job_artifact, :codequality, job: job)
end
it 'sends the codequality report' do
expect(controller).to receive(:send_file).with(job.job_artifacts_codequality.file.path, hash_including(disposition: 'attachment')).and_call_original
subject
end
end
end
end
......
......@@ -117,6 +117,16 @@ FactoryBot.define do
end
end
trait :codequality do
file_type :codequality
file_format :gzip
after(:build) do |artifact, evaluator|
artifact.file = fixture_file_upload(
Rails.root.join('spec/fixtures/codequality/codequality.json.gz'), 'application/x-gzip')
end
end
trait :correct_checksum do
after(:build) do |artifact, evaluator|
artifact.file_sha256 = Digest::SHA256.file(artifact.file.path).hexdigest
......
......@@ -13,6 +13,10 @@ FactoryBot.define do
state :opened
end
trait :locked do
discussion_locked true
end
trait :closed do
state :closed
closed_at { Time.now }
......
......@@ -56,6 +56,24 @@ describe 'Issuables Close/Reopen/Report toggle' do
end
it_behaves_like 'an issuable close/reopen/report toggle'
context 'when the issue is closed and locked' do
let(:issuable) { create(:issue, :closed, :locked, project: project) }
it 'hides the reopen button' do
expect(page).not_to have_link('Reopen issue')
end
context 'when the issue author is the current user' do
before do
issuable.update(author: user)
end
it 'hides the reopen button' do
expect(page).not_to have_link('Reopen issue')
end
end
end
end
context 'when user doesnt have permission to update' do
......@@ -93,6 +111,28 @@ describe 'Issuables Close/Reopen/Report toggle' do
end
it_behaves_like 'an issuable close/reopen/report toggle'
context 'when the merge request is merged' do
let(:issuable) { create(:merge_request, :merged, source_project: project) }
it 'shows only the `Report abuse` and `Edit` button' do
expect(page).to have_link('Report abuse')
expect(page).to have_link('Edit')
expect(page).not_to have_link('Close merge request')
expect(page).not_to have_link('Reopen merge request')
end
context 'when the merge request author is the current user' do
let(:issuable) { create(:merge_request, :merged, source_project: project, author: user) }
it 'shows only the `Edit` button' do
expect(page).to have_link('Edit')
expect(page).not_to have_link('Report abuse')
expect(page).not_to have_link('Close merge request')
expect(page).not_to have_link('Reopen merge request')
end
end
end
end
context 'when user doesnt have permission to update' do
......
......@@ -3,6 +3,12 @@ require 'spec_helper'
describe 'Create notes on issues', :js do
let(:user) { create(:user) }
def submit_comment(text)
fill_in 'note[note]', with: text
click_button 'Comment'
wait_for_requests
end
shared_examples 'notes with reference' do
let(:issue) { create(:issue, project: project) }
let(:note_text) { "Check #{mention.to_reference}" }
......@@ -12,10 +18,7 @@ describe 'Create notes on issues', :js do
sign_in(user)
visit project_issue_path(project, issue)
fill_in 'note[note]', with: note_text
click_button 'Comment'
wait_for_requests
submit_comment(note_text)
end
it 'creates a note with reference and cross references the issue' do
......@@ -74,4 +77,16 @@ describe 'Create notes on issues', :js do
let(:mention) { create(:merge_request, source_project: project) }
end
end
it 'highlights the current user in a comment' do
project = create(:project)
issue = create(:issue, project: project)
project.add_developer(user)
sign_in(user)
visit project_issue_path(project, issue)
submit_comment("@#{user.username} note to self")
expect(page).to have_selector '.gfm-project_member.current-user', text: user.username
end
end
This diff is collapsed.
import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
describe('highlightCurrentUser', () => {
let rootElement;
let elements;
beforeEach(() => {
setFixtures(`
<div id="dummy-root-element">
<div data-user="1">@first</div>
<div data-user="2">@second</div>
</div>
`);
rootElement = document.getElementById('dummy-root-element');
elements = rootElement.querySelectorAll('[data-user]');
});
describe('without current user', () => {
beforeEach(() => {
window.gon = window.gon || {};
window.gon.current_user_id = null;
});
afterEach(() => {
delete window.gon.current_user_id;
});
it('does not highlight the user', () => {
const initialHtml = rootElement.outerHTML;
highlightCurrentUser(elements);
expect(rootElement.outerHTML).toBe(initialHtml);
});
});
describe('with current user', () => {
beforeEach(() => {
window.gon = window.gon || {};
window.gon.current_user_id = 2;
});
afterEach(() => {
delete window.gon.current_user_id;
});
it('highlights current user', () => {
highlightCurrentUser(elements);
expect(elements.length).toBe(2);
expect(elements[0]).not.toHaveClass('current-user');
expect(elements[1]).toHaveClass('current-user');
});
});
});
......@@ -21,7 +21,7 @@ describe('init markdown', () => {
textArea.selectionStart = 0;
textArea.selectionEnd = 0;
insertMarkdownText(textArea, textArea.value, '*', null, '', false);
insertMarkdownText({ textArea, text: textArea.value, tag: '*', blockTag: null, selected: '', wrap: false });
expect(textArea.value).toEqual(`${initialValue}* `);
});
......@@ -32,7 +32,7 @@ describe('init markdown', () => {
textArea.value = initialValue;
textArea.setSelectionRange(initialValue.length, initialValue.length);
insertMarkdownText(textArea, textArea.value, '*', null, '', false);
insertMarkdownText({ textArea, text: textArea.value, tag: '*', blockTag: null, selected: '', wrap: false });
expect(textArea.value).toEqual(`${initialValue}\n* `);
});
......@@ -43,7 +43,7 @@ describe('init markdown', () => {
textArea.value = initialValue;
textArea.setSelectionRange(initialValue.length, initialValue.length);
insertMarkdownText(textArea, textArea.value, '*', null, '', false);
insertMarkdownText({ textArea, text: textArea.value, tag: '*', blockTag: null, selected: '', wrap: false });
expect(textArea.value).toEqual(`${initialValue}* `);
});
......@@ -54,9 +54,70 @@ describe('init markdown', () => {
textArea.value = initialValue;
textArea.setSelectionRange(initialValue.length, initialValue.length);
insertMarkdownText(textArea, textArea.value, '*', null, '', false);
insertMarkdownText({ textArea, text: textArea.value, tag: '*', blockTag: null, selected: '', wrap: false });
expect(textArea.value).toEqual(`${initialValue}* `);
});
});
describe('with selection', () => {
const text = 'initial selected value';
const selected = 'selected';
beforeEach(() => {
textArea.value = text;
const selectedIndex = text.indexOf(selected);
textArea.setSelectionRange(selectedIndex, selectedIndex + selected.length);
});
it('applies the tag to the selected value', () => {
insertMarkdownText({ textArea, text: textArea.value, tag: '*', blockTag: null, selected, wrap: true });
expect(textArea.value).toEqual(text.replace(selected, `*${selected}*`));
});
it('replaces the placeholder in the tag', () => {
insertMarkdownText({ textArea, text: textArea.value, tag: '[{text}](url)', blockTag: null, selected, wrap: false });
expect(textArea.value).toEqual(text.replace(selected, `[${selected}](url)`));
});
describe('and text to be selected', () => {
const tag = '[{text}](url)';
const select = 'url';
it('selects the text', () => {
insertMarkdownText({ textArea,
text: textArea.value,
tag,
blockTag: null,
selected,
wrap: false,
select });
const expectedText = text.replace(selected, `[${selected}](url)`);
expect(textArea.value).toEqual(expectedText);
expect(textArea.selectionStart).toEqual(expectedText.indexOf(select));
expect(textArea.selectionEnd).toEqual(expectedText.indexOf(select) + select.length);
});
it('selects the right text when multiple tags are present', () => {
const initialValue = `${tag} ${tag} ${selected}`;
textArea.value = initialValue;
const selectedIndex = initialValue.indexOf(selected);
textArea.setSelectionRange(selectedIndex, selectedIndex + selected.length);
insertMarkdownText({ textArea,
text: textArea.value,
tag,
blockTag: null,
selected,
wrap: false,
select });
const expectedText = initialValue.replace(selected, `[${selected}](url)`);
expect(textArea.value).toEqual(expectedText);
expect(textArea.selectionStart).toEqual(expectedText.lastIndexOf(select));
expect(textArea.selectionEnd).toEqual(expectedText.lastIndexOf(select) + select.length);
});
});
});
});
......@@ -153,7 +153,7 @@ describe('Markdown field component', () => {
const textarea = vm.$el.querySelector('textarea');
textarea.setSelectionRange(0, 0);
vm.$el.querySelectorAll('.js-md')[4].click();
vm.$el.querySelectorAll('.js-md')[5].click();
Vue.nextTick(() => {
expect(
......@@ -168,7 +168,7 @@ describe('Markdown field component', () => {
const textarea = vm.$el.querySelector('textarea');
textarea.setSelectionRange(0, 50);
vm.$el.querySelectorAll('.js-md')[4].click();
vm.$el.querySelectorAll('.js-md')[5].click();
Vue.nextTick(() => {
expect(
......
......@@ -18,7 +18,7 @@ describe('Markdown field header component', () => {
});
it('renders markdown buttons', () => {
expect(vm.$el.querySelectorAll('.js-md').length).toBe(7);
expect(vm.$el.querySelectorAll('.js-md').length).toBe(8);
});
it('renders `write` link as active when previewMarkdown is false', () => {
......
......@@ -33,6 +33,7 @@ describe Gitlab::Ci::Config::Entry::Reports do
where(:keyword, :file) do
:junit | 'junit.xml'
:codequality | 'codequality.json'
:sast | 'gl-sast-report.json'
:dependency_scanning | 'gl-dependency-scanning-report.json'
:container_scanning | 'gl-container-scanning-report.json'
......
......@@ -592,4 +592,19 @@ describe ApplicationSetting do
it { is_expected.to eq(result) }
end
end
context 'diff limit settings' do
describe '#diff_max_patch_bytes' do
context 'validations' do
it { is_expected.to validate_presence_of(:diff_max_patch_bytes) }
it do
is_expected.to validate_numericality_of(:diff_max_patch_bytes)
.only_integer
.is_greater_than_or_equal_to(Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES)
.is_less_than_or_equal_to(Gitlab::Git::Diff::MAX_PATCH_BYTES_UPPER_BOUND)
end
end
end
end
end
......@@ -1279,6 +1279,19 @@ describe Ci::Build do
end
end
describe '#artifacts_file_for_type' do
let(:build) { create(:ci_build, :artifacts) }
let(:file_type) { :archive }
subject { build.artifacts_file_for_type(file_type) }
it 'queries artifacts for type' do
expect(build).to receive_message_chain(:job_artifacts, :find_by).with(file_type: Ci::JobArtifact.file_types[file_type])
subject
end
end
describe '#merge_request' do
def create_mr(build, pipeline, factory: :merge_request, created_at: Time.now)
create(factory, source_project: pipeline.project,
......
......@@ -34,7 +34,7 @@ describe Ci::JobArtifact do
describe '.erasable' do
subject { described_class.erasable }
context 'when there is am erasable artifact' do
context 'when there is an erasable artifact' do
let!(:artifact) { create(:ci_job_artifact, :junit) }
it { is_expected.to eq([artifact]) }
......
......@@ -82,6 +82,27 @@ describe Namespace do
it { expect(namespace.human_name).to eq(namespace.owner_name) }
end
describe '#first_project_with_container_registry_tags' do
let(:container_repository) { create(:container_repository) }
let!(:project) { create(:project, namespace: namespace, container_repositories: [container_repository]) }
before do
stub_container_registry_config(enabled: true)
end
it 'returns the project' do
stub_container_registry_tags(repository: :any, tags: ['tag'])
expect(namespace.first_project_with_container_registry_tags).to eq(project)
end
it 'returns no project' do
stub_container_registry_tags(repository: :any, tags: nil)
expect(namespace.first_project_with_container_registry_tags).to be_nil
end
end
describe '.search' do
let(:namespace) { create(:namespace) }
......@@ -184,7 +205,8 @@ describe Namespace do
end
it 'raises an error about not movable project' do
expect { namespace.move_dir }.to raise_error(/Namespace cannot be moved/)
expect { namespace.move_dir }.to raise_error(Gitlab::UpdatePathError,
/Namespace .* cannot be moved/)
end
end
end
......
......@@ -4310,6 +4310,49 @@ describe Project do
end
end
context '#members_among' do
let(:users) { create_list(:user, 3) }
set(:group) { create(:group) }
set(:project) { create(:project, namespace: group) }
before do
project.add_guest(users.first)
project.group.add_maintainer(users.last)
end
context 'when users is an Array' do
it 'returns project members among the users' do
expect(project.members_among(users)).to eq([users.first, users.last])
end
it 'maintains input order' do
expect(project.members_among(users.reverse)).to eq([users.last, users.first])
end
it 'returns empty array if users is empty' do
result = project.members_among([])
expect(result).to be_empty
end
end
context 'when users is a relation' do
it 'returns project members among the users' do
result = project.members_among(User.where(id: users.map(&:id)))
expect(result).to be_a(ActiveRecord::Relation)
expect(result).to eq([users.first, users.last])
end
it 'returns empty relation if users is empty' do
result = project.members_among(User.none)
expect(result).to be_a(ActiveRecord::Relation)
expect(result).to be_empty
end
end
end
def rugged_config
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
project.repository.rugged.config
......
......@@ -112,7 +112,7 @@ describe IssuePolicy do
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project, assignees: [assignee], author: author) }
let(:issue_no_assignee) { create(:issue, project: project) }
let(:issue_locked) { create(:issue, project: project, discussion_locked: true, author: author, assignees: [assignee]) }
let(:issue_locked) { create(:issue, :locked, project: project, author: author, assignees: [assignee]) }
before do
project.add_guest(guest)
......
......@@ -68,7 +68,8 @@ describe API::Settings, 'Settings' do
enforce_terms: true,
terms: 'Hello world!',
performance_bar_allowed_group_path: group.full_path,
instance_statistics_visibility_private: true
instance_statistics_visibility_private: true,
diff_max_patch_bytes: 150_000
expect(response).to have_gitlab_http_status(200)
expect(json_response['default_projects_limit']).to eq(3)
......@@ -94,6 +95,7 @@ describe API::Settings, 'Settings' do
expect(json_response['terms']).to eq('Hello world!')
expect(json_response['performance_bar_allowed_group_id']).to eq(group.id)
expect(json_response['instance_statistics_visibility_private']).to be(true)
expect(json_response['diff_max_patch_bytes']).to eq(150_000)
end
end
......
......@@ -26,7 +26,8 @@ describe Ci::RetryBuildService do
erased_at auto_canceled_by job_artifacts job_artifacts_archive
job_artifacts_metadata job_artifacts_trace job_artifacts_junit
job_artifacts_sast job_artifacts_dependency_scanning
job_artifacts_container_scanning job_artifacts_dast].freeze
job_artifacts_container_scanning job_artifacts_dast
job_artifacts_codequality].freeze
IGNORE_ACCESSORS =
%i[type lock_version target_url base_tags trace_sections
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment