Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2019-09-24 12:06:20 +00:00
parent bc89882970
commit 9c8edcd616
24 changed files with 260 additions and 130 deletions

View File

@ -1,4 +1,4 @@
/* eslint-disable no-param-reassign, prefer-template, no-void, consistent-return */
/* eslint-disable no-param-reassign, no-void, consistent-return */
import AccessorUtilities from './lib/utils/accessor';
@ -10,7 +10,7 @@ export default class Autosave {
if (key.join != null) {
key = key.join('/');
}
this.key = 'autosave/' + key;
this.key = `autosave/${key}`;
this.field.data('autosave', this);
this.restore();
this.field.on('input', () => this.save());

View File

@ -26,7 +26,7 @@ $.fn.requiresInput = function requiresInput() {
const values = _.map($(fieldSelector, $form), field => field.value);
// Disable the button if any required fields are empty
if (values.length && _.any(values, _.isEmpty)) {
if (values.length && _.some(values, _.isEmpty)) {
$button.disable();
} else {
$button.enable();

View File

@ -1,4 +1,4 @@
/* eslint-disable func-names, no-var, no-else-return, consistent-return, prefer-template, one-var, no-return-assign, no-unused-expressions, no-sequences */
/* eslint-disable func-names, no-var, no-else-return, consistent-return, one-var, no-return-assign, no-unused-expressions, no-sequences */
import $ from 'jquery';
@ -49,13 +49,13 @@ export default class ImageFile {
activateViewMode(viewMode) {
$('.view-modes-menu li', this.file)
.removeClass('active')
.filter('.' + viewMode)
.filter(`.${viewMode}`)
.addClass('active');
return $('.view:visible:not(.' + viewMode + ')', this.file).fadeOut(
return $(`.view:visible:not(.${viewMode})`, this.file).fadeOut(
200,
(function(_this) {
return function() {
$('.view.' + viewMode, _this.file).fadeIn(200);
$(`.view.${viewMode}`, _this.file).fadeIn(200);
return _this.initView(viewMode);
};
})(this),
@ -139,8 +139,8 @@ export default class ImageFile {
}
});
return _this.requestImageInfo($('img', wrap), (width, height) => {
$('.image-info .meta-width', wrap).text(width + 'px');
$('.image-info .meta-height', wrap).text(height + 'px');
$('.image-info .meta-width', wrap).text(`${width}px`);
$('.image-info .meta-height', wrap).text(`${height}px`);
return $('.image-info', wrap).removeClass('hide');
});
};

View File

@ -1,4 +1,4 @@
/* eslint-disable func-names, no-underscore-dangle, no-var, one-var, vars-on-top, no-shadow, no-cond-assign, no-return-assign, no-else-return, camelcase, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func */
/* eslint-disable func-names, no-underscore-dangle, no-var, one-var, vars-on-top, no-shadow, no-cond-assign, no-return-assign, no-else-return, camelcase, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, no-param-reassign, no-loop-func */
import $ from 'jquery';
import _ from 'underscore';
@ -272,7 +272,7 @@ GitLabDropdown = (function() {
NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-item';
SELECTABLE_CLASSES = '.dropdown-content li:not(' + NON_SELECTABLE_CLASSES + ', .option-hidden)';
SELECTABLE_CLASSES = `.dropdown-content li:not(${NON_SELECTABLE_CLASSES}, .option-hidden)`;
CURSOR_SELECT_SCROLL_PADDING = 5;
@ -359,9 +359,9 @@ GitLabDropdown = (function() {
instance: this,
elements: (function(_this) {
return function() {
selector = '.dropdown-content li:not(' + NON_SELECTABLE_CLASSES + ')';
selector = `.dropdown-content li:not(${NON_SELECTABLE_CLASSES})`;
if (_this.dropdown.find('.dropdown-toggle-page').length) {
selector = '.dropdown-page-one ' + selector;
selector = `.dropdown-page-one ${selector}`;
}
return $(selector, this.instance.dropdown);
};
@ -377,7 +377,7 @@ GitLabDropdown = (function() {
if (_this.filterInput.val() !== '') {
selector = SELECTABLE_CLASSES;
if (_this.dropdown.find('.dropdown-toggle-page').length) {
selector = '.dropdown-page-one ' + selector;
selector = `.dropdown-page-one ${selector}`;
}
if ($(_this.el).is('input')) {
currentIndex = -1;
@ -693,7 +693,7 @@ GitLabDropdown = (function() {
.split('')
.map((character, i) => {
if (indexOf.call(occurrences, i) !== -1) {
return '<b>' + character + '</b>';
return `<b>${character}</b>`;
} else {
return character;
}
@ -738,9 +738,7 @@ GitLabDropdown = (function() {
} else if (value != null) {
field = this.dropdown
.parent()
.find(
"input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, "\\'") + "']",
);
.find(`input[name='${fieldName}'][value='${value.toString().replace(/'/g, "\\'")}']`);
}
if (this.options.isSelectable && !this.options.isSelectable(selectedObject, el)) {
@ -766,11 +764,11 @@ GitLabDropdown = (function() {
} else {
isMarking = true;
if (!this.options.multiSelect || el.hasClass('dropdown-clear-active')) {
this.dropdown.find('.' + ACTIVE_CLASS).removeClass(ACTIVE_CLASS);
this.dropdown.find(`.${ACTIVE_CLASS}`).removeClass(ACTIVE_CLASS);
if (!isInput) {
this.dropdown
.parent()
.find("input[name='" + fieldName + "']")
.find(`input[name='${fieldName}']`)
.remove();
}
}
@ -809,7 +807,7 @@ GitLabDropdown = (function() {
var $input;
// Create hidden input for form
if (single) {
$('input[name="' + fieldName + '"]').remove();
$(`input[name="${fieldName}"]`).remove();
}
$input = $('<input>')
@ -837,12 +835,12 @@ GitLabDropdown = (function() {
var $el, selector;
// If we pass an option index
if (typeof index !== 'undefined') {
selector = SELECTABLE_CLASSES + ':eq(' + index + ') a';
selector = `${SELECTABLE_CLASSES}:eq(${index}) a`;
} else {
selector = '.dropdown-content .is-focused';
}
if (this.dropdown.find('.dropdown-toggle-page').length) {
selector = '.dropdown-page-one ' + selector;
selector = `.dropdown-page-one ${selector}`;
}
// simulate a click on the first link
$el = $(selector, this.dropdown);
@ -861,7 +859,7 @@ GitLabDropdown = (function() {
ARROW_KEY_CODES = [38, 40];
selector = SELECTABLE_CLASSES;
if (this.dropdown.find('.dropdown-toggle-page').length) {
selector = '.dropdown-page-one ' + selector;
selector = `.dropdown-page-one ${selector}`;
}
return $('body').on(
'keydown',

View File

@ -1,4 +1,4 @@
/* eslint-disable no-useless-return, func-names, no-var, no-underscore-dangle, one-var, prefer-template, no-new, consistent-return, no-shadow, no-param-reassign, vars-on-top, no-lonely-if, no-else-return, dot-notation, no-empty */
/* eslint-disable no-useless-return, func-names, no-var, no-underscore-dangle, one-var, no-new, consistent-return, no-shadow, no-param-reassign, vars-on-top, no-lonely-if, no-else-return, dot-notation, no-empty */
/* global Issuable */
/* global ListLabel */
@ -70,7 +70,7 @@ export default class LabelsSelect {
$loading = $block.find('.block-loading').fadeOut();
fieldName = $dropdown.data('fieldName');
initialSelected = $selectbox
.find('input[name="' + $dropdown.data('fieldName') + '"]')
.find(`input[name="${$dropdown.data('fieldName')}"]`)
.map(function() {
return this.value;
})
@ -92,7 +92,7 @@ export default class LabelsSelect {
var data, selected;
selected = $dropdown
.closest('.selectbox')
.find("input[name='" + fieldName + "']")
.find(`input[name='${fieldName}']`)
.map(function() {
return this.value;
})
@ -267,11 +267,7 @@ export default class LabelsSelect {
if (
$form.find(
"input[type='hidden'][name='" +
this.fieldName +
"'][value='" +
dropdownValue +
"']",
`input[type='hidden'][name='${this.fieldName}'][value='${dropdownValue}']`,
).length
) {
selectedClass.push('is-active');
@ -284,8 +280,7 @@ export default class LabelsSelect {
}
if (label.color) {
colorEl =
"<span class='dropdown-label-box' style='background: " + label.color + "'></span>";
colorEl = `<span class='dropdown-label-box' style='background: ${label.color}'></span>`;
} else {
colorEl = '';
}

View File

@ -1,4 +1,4 @@
/* eslint-disable func-names, no-var, no-param-reassign, one-var, operator-assignment, no-else-return, prefer-template, consistent-return */
/* eslint-disable func-names, no-var, no-param-reassign, one-var, operator-assignment, no-else-return, consistent-return */
import $ from 'jquery';
import { insertText } from '~/lib/utils/common_utils';
@ -237,7 +237,7 @@ export function insertMarkdownText({
}
if (removedFirstNewLine) {
textToInsert = '\n' + textToInsert;
textToInsert = `\n${textToInsert}`;
}
if (removedLastNewLine) {

View File

@ -1,4 +1,4 @@
/* eslint-disable func-names, no-var, no-underscore-dangle, no-param-reassign, prefer-template, consistent-return, one-var, no-else-return */
/* eslint-disable func-names, no-var, no-underscore-dangle, no-param-reassign, consistent-return, one-var, no-else-return */
import $ from 'jquery';
@ -106,7 +106,7 @@ LineHighlighter.prototype.clickHandler = function(event) {
};
LineHighlighter.prototype.clearHighlight = function() {
return $('.' + this.highlightLineClass).removeClass(this.highlightLineClass);
return $(`.${this.highlightLineClass}`).removeClass(this.highlightLineClass);
};
// Convert a URL hash String into line numbers
@ -137,7 +137,7 @@ LineHighlighter.prototype.hashToRange = function(hash) {
//
// lineNumber - Line number to highlight
LineHighlighter.prototype.highlightLine = function(lineNumber) {
return $('#LC' + lineNumber).addClass(this.highlightLineClass);
return $(`#LC${lineNumber}`).addClass(this.highlightLineClass);
};
// Highlight all lines within a range
@ -162,9 +162,9 @@ LineHighlighter.prototype.highlightRange = function(range) {
LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) {
var hash;
if (lastLineNumber) {
hash = '#L' + firstLineNumber + '-' + lastLineNumber;
hash = `#L${firstLineNumber}-${lastLineNumber}`;
} else {
hash = '#L' + firstLineNumber;
hash = `#L${firstLineNumber}`;
}
this._hash = hash;
return this.__setLocationHash__(hash);

View File

@ -1,4 +1,4 @@
/* eslint-disable no-else-return, prefer-template */
/* eslint-disable no-else-return */
import $ from 'jquery';
import '~/gl_dropdown';
@ -24,7 +24,7 @@ export default class NamespaceSelect {
if (selected.id == null) {
return selected.text;
} else {
return selected.kind + ': ' + selected.full_path;
return `${selected.kind}: ${selected.full_path}`;
}
},
data(term, dataCallback) {
@ -44,7 +44,7 @@ export default class NamespaceSelect {
if (namespace.id == null) {
return namespace.text;
} else {
return namespace.kind + ': ' + namespace.full_path;
return `${namespace.kind}: ${namespace.full_path}`;
}
},
renderRow: this.renderRow,

View File

@ -1,4 +1,4 @@
/* eslint-disable func-names, no-var, one-var, no-loop-func, consistent-return, prefer-template, camelcase */
/* eslint-disable func-names, no-var, one-var, no-loop-func, consistent-return, camelcase */
import $ from 'jquery';
import { __ } from '../locale';
@ -223,7 +223,7 @@ export default (function() {
shortrefs = commit.refs;
// Truncate if longer than 15 chars
if (shortrefs.length > 17) {
shortrefs = shortrefs.substr(0, 15) + '…';
shortrefs = `${shortrefs.substr(0, 15)}`;
}
text = r.text(x + 4, y, shortrefs).attr({
'text-anchor': 'start',

View File

@ -1,4 +1,4 @@
/* eslint-disable func-names, no-var, one-var, consistent-return, no-return-assign, prefer-template, no-shadow, no-else-return, @gitlab/i18n/no-non-i18n-strings */
/* eslint-disable func-names, no-var, one-var, consistent-return, no-return-assign, no-shadow, no-else-return, @gitlab/i18n/no-non-i18n-strings */
import $ from 'jquery';
import RefSelectDropdown from './ref_select_dropdown';
@ -70,10 +70,10 @@ export default class NewBranchForm {
case !/\/{2,}/g.test(value):
return 'consecutive slashes';
default:
return "'" + value + "'";
return `'${value}'`;
}
});
return restriction.prefix + ' ' + formatted.join(restriction.conjunction);
return `${restriction.prefix} ${formatted.join(restriction.conjunction)}`;
};
validator = (function(_this) {
return function(errors, restriction) {

View File

@ -1,6 +1,6 @@
/* eslint-disable no-restricted-properties, func-names, no-var, camelcase,
no-unused-expressions, one-var, default-case,
prefer-template, consistent-return, no-alert, no-return-assign,
consistent-return, no-alert, no-return-assign,
no-param-reassign, no-else-return, vars-on-top,
no-shadow, no-useless-escape, class-methods-use-this */
@ -490,7 +490,7 @@ export default class Notes {
diffAvatarContainer = row
.prevAll('.line_holder')
.first()
.find('.js-avatar-container.' + lineType + '_line');
.find(`.js-avatar-container.${lineType}_line`);
// is this the first note of discussion?
discussionContainer = $(`.notes[data-discussion-id="${noteEntity.discussion_id}"]`);
if (!discussionContainer.length) {
@ -506,16 +506,14 @@ export default class Notes {
} else {
// Merge new discussion HTML in
var $notes = $discussion.find(`.notes[data-discussion-id="${noteEntity.discussion_id}"]`);
var contentContainerClass =
'.' +
$notes
.closest('.notes-content')
.attr('class')
.split(' ')
.join('.');
var contentContainerClass = $notes
.closest('.notes-content')
.attr('class')
.split(' ')
.join('.');
row
.find(contentContainerClass + ' .content')
.find(`.${contentContainerClass} .content`)
.append($notes.closest('.content').children());
}
} else {
@ -722,7 +720,7 @@ export default class Notes {
this.revertNoteEditForm($targetNote);
$noteEntityEl.renderGFM();
// Find the note's `li` element by ID and replace it with the updated HTML
$note_li = $('.note-row-' + noteEntity.id);
$note_li = $(`.note-row-${noteEntity.id}`);
$note_li.replaceWith($noteEntityEl);
this.setupNewNote($noteEntityEl);

View File

@ -1,4 +1,4 @@
/* eslint-disable func-names, no-var, one-var, camelcase, no-param-reassign, prefer-template, no-return-assign */
/* eslint-disable func-names, no-var, one-var, camelcase, no-param-reassign, no-return-assign */
import $ from 'jquery';
import _ from 'underscore';
@ -66,8 +66,8 @@ export default (function() {
class: 'person',
style: 'display: block;',
});
author_name = $('<h4>' + author.author_name + '</h4>');
author_email = $('<p class="graph-author-email">' + author.author_email + '</p>');
author_name = $(`<h4>${author.author_name}</h4>`);
author_email = $(`<p class="graph-author-email">${author.author_email}</p>`);
author_commit_info_span = $('<span/>', {
class: 'commits',
});

View File

@ -1,4 +1,4 @@
/* eslint-disable func-names, no-restricted-syntax, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, no-return-assign, prefer-template, no-else-return, no-shadow */
/* eslint-disable func-names, no-restricted-syntax, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, no-return-assign, no-else-return, no-shadow */
import $ from 'jquery';
import _ from 'underscore';
@ -118,14 +118,11 @@ export const ContributorsGraph = (function() {
};
ContributorsGraph.prototype.draw_x_axis = function() {
return (
this.svg
.append('g')
.attr('class', 'x axis')
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
.attr('transform', 'translate(0, ' + this.height + ')')
.call(this.x_axis)
);
return this.svg
.append('g')
.attr('class', 'x axis')
.attr('transform', `translate(0, ${this.height})`)
.call(this.x_axis);
};
ContributorsGraph.prototype.draw_y_axis = function() {
@ -200,8 +197,7 @@ export const ContributorsMasterGraph = (function(superClass) {
.attr('height', this.height + this.MARGIN.top + this.MARGIN.bottom)
.attr('class', 'tint-box')
.append('g')
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
.attr('transform', 'translate(' + this.MARGIN.left + ',' + this.MARGIN.top + ')');
.attr('transform', `translate(${this.MARGIN.left},${this.MARGIN.top})`);
return this.svg;
};
@ -348,8 +344,7 @@ export const ContributorsAuthorGraph = (function(superClass) {
.attr('height', this.height + this.MARGIN.top + this.MARGIN.bottom)
.attr('class', 'spark')
.append('g')
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
.attr('transform', 'translate(' + this.MARGIN.left + ',' + this.MARGIN.top + ')');
.attr('transform', `translate(${this.MARGIN.left},${this.MARGIN.top})`);
return this.svg;
};

View File

@ -1,4 +1,4 @@
/* eslint-disable func-names, no-var, prefer-template */
/* eslint-disable func-names, no-var */
import $ from 'jquery';
import BranchGraph from '../../../network/branch_graph';
@ -14,7 +14,7 @@ export default (function() {
this.branch_graph = new BranchGraph($('.network-graph'), opts);
vph = $(window).height() - 250;
$('.network-graph').css({
height: vph + 'px',
height: `${vph}px`,
});
}

View File

@ -1,4 +1,4 @@
/* eslint-disable func-names, no-var, consistent-return, one-var, no-cond-assign, prefer-template, no-return-assign */
/* eslint-disable func-names, no-var, consistent-return, one-var, no-cond-assign, no-return-assign */
import $ from 'jquery';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
@ -112,7 +112,7 @@ export default class ProjectFindFile {
if (searchText) {
matches = fuzzaldrinPlus.match(filePath, searchText);
}
blobItemUrl = this.options.blobUrlTemplate + '/' + encodeURIComponent(filePath);
blobItemUrl = `${this.options.blobUrlTemplate}/${encodeURIComponent(filePath)}`;
html = ProjectFindFile.makeHtml(filePath, matches, blobItemUrl);
results.push(this.element.find('.tree-table > tbody').append(html));
}

View File

@ -1,4 +1,4 @@
/* eslint-disable func-names, no-var, consistent-return, one-var, prefer-template, no-else-return, no-param-reassign */
/* eslint-disable func-names, no-var, consistent-return, one-var, no-else-return, no-param-reassign */
import $ from 'jquery';
import _ from 'underscore';
@ -247,7 +247,7 @@ Sidebar.prototype.isOpen = function() {
};
Sidebar.prototype.getBlock = function(name) {
return this.sidebar.find('.block.' + name);
return this.sidebar.find(`.block.${name}`);
};
export default Sidebar;

View File

@ -1,4 +1,4 @@
/* eslint-disable no-return-assign, one-var, no-var, consistent-return, prefer-template, class-methods-use-this, no-lonely-if, vars-on-top */
/* eslint-disable no-return-assign, one-var, no-var, consistent-return, class-methods-use-this, no-lonely-if, vars-on-top */
import $ from 'jquery';
import { escape, throttle } from 'underscore';
@ -416,7 +416,7 @@ export class SearchAutocomplete {
inputs = Object.keys(this.originalState);
for (i = 0, len = inputs.length; i < len; i += 1) {
input = inputs[i];
this.getElement('#' + input).val(this.originalState[input]);
this.getElement(`#${input}`).val(this.originalState[input]);
}
}
@ -426,7 +426,7 @@ export class SearchAutocomplete {
results = [];
for (i = 0, len = inputs.length; i < len; i += 1) {
input = inputs[i];
results.push(this.getElement('#' + input).val(''));
results.push(this.getElement(`#${input}`).val(''));
}
return results;
}

View File

@ -1,4 +1,4 @@
/* eslint-disable func-names, one-var, no-var, prefer-rest-params, vars-on-top, consistent-return, no-shadow, no-else-return, no-self-compare, prefer-template, no-unused-expressions, yoda, prefer-spread, camelcase, no-param-reassign */
/* eslint-disable func-names, one-var, no-var, prefer-rest-params, vars-on-top, consistent-return, no-shadow, no-else-return, no-self-compare, no-unused-expressions, yoda, prefer-spread, camelcase, no-param-reassign */
/* global Issuable */
/* global emitSidebarEvent */
@ -428,8 +428,7 @@ function UsersSelect(currentUser, els, options = {}) {
const isActive = $el.hasClass('is-active');
const previouslySelected = $dropdown
.closest('.selectbox')
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
.find("input[name='" + $dropdown.data('fieldName') + "'][value!=0]");
.find(`input[name='${$dropdown.data('fieldName')}'][value!=0]`);
// Enables support for limiting the number of users selected
// Automatically removes the first on the list if more users are selected
@ -448,7 +447,7 @@ function UsersSelect(currentUser, els, options = {}) {
// Remove unassigned selection (if it was previously selected)
const unassignedSelected = $dropdown
.closest('.selectbox')
.find("input[name='" + $dropdown.data('fieldName') + "'][value=0]");
.find(`input[name='${$dropdown.data('fieldName')}'][value=0]`);
if (unassignedSelected) {
unassignedSelected.remove();
@ -502,7 +501,7 @@ function UsersSelect(currentUser, els, options = {}) {
} else if (!$dropdown.hasClass('js-multiselect')) {
selected = $dropdown
.closest('.selectbox')
.find("input[name='" + $dropdown.data('fieldName') + "']")
.find(`input[name='${$dropdown.data('fieldName')}']`)
.val();
return assignTo(selected);
}
@ -544,7 +543,7 @@ function UsersSelect(currentUser, els, options = {}) {
updateLabel: $dropdown.data('dropdownTitle'),
renderRow(user) {
var avatar, img, username;
username = user.username ? '@' + user.username : '';
username = user.username ? `@${user.username}` : '';
avatar = user.avatar_url ? user.avatar_url : gon.default_avatar_url;
let selected = false;
@ -555,7 +554,7 @@ function UsersSelect(currentUser, els, options = {}) {
const { fieldName } = this;
const field = $dropdown
.closest('.selectbox')
.find("input[name='" + fieldName + "'][value='" + user.id + "']");
.find(`input[name='${fieldName}'][value='${user.id}']`);
if (field.length) {
selected = true;
@ -571,7 +570,7 @@ function UsersSelect(currentUser, els, options = {}) {
)}</a></li>`;
} else {
// 0 margin, because it's now handled by a wrapper
img = "<img src='" + avatar + "' class='avatar avatar-inline m-0' width='32' />";
img = `<img src='${avatar}' class='avatar avatar-inline m-0' width='32' />`;
}
return _this.renderRow(options.issuableType, user, selected, username, img);
@ -715,7 +714,7 @@ UsersSelect.prototype.formatResult = function(user) {
${_.escape(user.name)}
</div>
<div class='user-username dropdown-menu-user-username text-secondary'>
${!user.invite ? '@' + _.escape(user.username) : ''}
${!user.invite ? `@${_.escape(user.username)}` : ''}
</div>
</div>
</div>

View File

@ -0,0 +1,5 @@
---
title: Use GetBlobs RPC for uri type
merge_request: 16824
author:
type: performance

View File

@ -20,16 +20,12 @@ module Banzai
def call
return doc if context[:system_note]
@uri_types = {}
clear_memoization(:linkable_files)
doc.search('a:not(.gfm)').each do |el|
process_link_attr el.attribute('href')
end
load_uri_types
doc.css('img, video').each do |el|
process_link_attr el.attribute('src')
process_link_attr el.attribute('data-src')
linkable_attributes.each do |attr|
process_link_attr(attr)
end
doc
@ -37,16 +33,81 @@ module Banzai
protected
def load_uri_types
return unless linkable_files?
return {} unless repository
clear_memoization(:linkable_attributes)
@uri_types = request_path.present? ? get_uri_types([request_path]) : {}
paths = linkable_attributes.flat_map do |attr|
[get_uri(attr).to_s, relative_file_path(get_uri(attr))]
end
paths.reject!(&:blank?)
paths.uniq!
@uri_types.merge!(get_uri_types(paths))
end
def linkable_files?
strong_memoize(:linkable_files) do
context[:project_wiki].nil? && repository.try(:exists?) && !repository.empty?
end
end
def process_link_attr(html_attr)
return if html_attr.blank?
return if html_attr.value.start_with?('//')
def linkable_attributes
strong_memoize(:linkable_attributes) do
attrs = []
attrs += doc.search('a:not(.gfm)').map do |el|
el.attribute('href')
end
attrs += doc.search('img, video').flat_map do |el|
[el.attribute('src'), el.attribute('data-src')]
end
attrs.reject do |attr|
attr.blank? || attr.value.start_with?('//')
end
end
end
def get_uri_types(paths)
return {} if paths.empty?
uri_types = Hash[paths.collect { |name| [name, nil] }]
get_blob_types(paths).each do |name, type|
if type == :blob
blob = ::Blob.decorate(Gitlab::Git::Blob.new(name: name), project)
uri_types[name] = blob.image? || blob.video? ? :raw : :blob
else
uri_types[name] = type
end
end
uri_types
end
def get_blob_types(paths)
revision_paths = paths.collect do |path|
[current_commit.sha, path.chomp("/")]
end
Gitlab::GitalyClient::BlobService.new(repository).get_blob_types(revision_paths, 1)
end
def get_uri(html_attr)
uri = URI(html_attr.value)
uri if uri.relative? && uri.path.present?
rescue URI::Error, Addressable::URI::InvalidURIError
end
def process_link_attr(html_attr)
if html_attr.value.start_with?('/uploads/')
process_link_to_upload_attr(html_attr)
elsif linkable_files? && repo_visible_to_user?
@ -81,6 +142,7 @@ module Banzai
def process_link_to_repository_attr(html_attr)
uri = URI(html_attr.value)
if uri.relative? && uri.path.present?
html_attr.value = rebuild_relative_uri(uri).to_s
end
@ -89,7 +151,7 @@ module Banzai
end
def rebuild_relative_uri(uri)
file_path = relative_file_path(uri)
file_path = nested_file_path_if_exists(uri)
uri.path = [
relative_url_root,
@ -102,13 +164,29 @@ module Banzai
uri
end
def relative_file_path(uri)
path = Addressable::URI.unescape(uri.path).delete("\0")
request_path = Addressable::URI.unescape(context[:requested_path])
nested_path = build_relative_path(path, request_path)
def nested_file_path_if_exists(uri)
path = cleaned_file_path(uri)
nested_path = relative_file_path(uri)
file_exists?(nested_path) ? nested_path : path
end
def cleaned_file_path(uri)
Addressable::URI.unescape(uri.path).delete("\0").chomp("/")
end
def relative_file_path(uri)
return if uri.nil?
build_relative_path(cleaned_file_path(uri), request_path)
end
def request_path
return unless context[:requested_path]
Addressable::URI.unescape(context[:requested_path]).chomp("/")
end
# Convert a relative path into its correct location based on the currently
# requested path
#
@ -136,6 +214,7 @@ module Banzai
return path[1..-1] if path.start_with?('/')
parts = request_path.split('/')
parts.pop if uri_type(request_path) != :tree
path.sub!(%r{\A\./}, '')
@ -149,14 +228,11 @@ module Banzai
end
def file_exists?(path)
path.present? && !!uri_type(path)
path.present? && uri_type(path).present?
end
def uri_type(path)
# https://gitlab.com/gitlab-org/gitlab-foss/issues/58657
Gitlab::GitalyClient.allow_n_plus_1_calls do
@uri_types[path] ||= current_commit.uri_type(path)
end
@uri_types[path] == :unknown ? "" : @uri_types[path]
end
def current_commit

View File

@ -76,6 +76,30 @@ module Gitlab
GitalyClient::BlobsStitcher.new(response)
end
def get_blob_types(revision_paths, limit = -1)
return {} if revision_paths.empty?
request_revision_paths = revision_paths.map do |rev, path|
Gitaly::GetBlobsRequest::RevisionPath.new(revision: rev, path: encode_binary(path))
end
request = Gitaly::GetBlobsRequest.new(
repository: @gitaly_repo,
revision_paths: request_revision_paths,
limit: limit
)
response = GitalyClient.call(
@gitaly_repo.storage_name,
:blob_service,
:get_blobs,
request,
timeout: GitalyClient.fast_timeout
)
map_blob_types(response)
end
def get_new_lfs_pointers(revision, limit, not_in, dynamic_timeout = nil)
request = Gitaly::GetNewLFSPointersRequest.new(
repository: @gitaly_repo,
@ -132,6 +156,16 @@ module Gitlab
end
end
end
def map_blob_types(response)
types = {}
response.each do |msg|
types[msg.path.dup.force_encoding('utf-8')] = msg.type.downcase
end
types
end
end
end
end

View File

@ -234,6 +234,12 @@ describe 'Issue Boards', :js do
expect(find('.board:nth-child(2)')).to have_content(development.title)
expect(find('.board:nth-child(2)')).to have_content(planning.title)
# Make sure list positions are preserved after a reload
visit project_board_path(project, board)
expect(find('.board:nth-child(2)')).to have_content(development.title)
expect(find('.board:nth-child(2)')).to have_content(planning.title)
end
it 'dragging does not duplicate list' do

View File

@ -1,4 +1,4 @@
/* eslint-disable no-var, prefer-template, no-else-return, dot-notation, no-return-assign, no-new, no-underscore-dangle */
/* eslint-disable no-var, no-else-return, dot-notation, no-return-assign, no-new, no-underscore-dangle */
import $ from 'jquery';
import LineHighlighter from '~/line_highlighter';
@ -8,10 +8,10 @@ describe('LineHighlighter', function() {
preloadFixtures('static/line_highlighter.html');
clickLine = function(number, eventData = {}) {
if ($.isEmptyObject(eventData)) {
return $('#L' + number).click();
return $(`#L${number}`).click();
} else {
const e = $.Event('click', eventData);
return $('#L' + number).trigger(e);
return $(`#L${number}`).trigger(e);
}
};
beforeEach(function() {
@ -42,9 +42,9 @@ describe('LineHighlighter', function() {
var line;
new LineHighlighter({ hash: '#L5-25' });
expect($('.' + this.css).length).toBe(21);
expect($(`.${this.css}`).length).toBe(21);
for (line = 5; line <= 25; line += 1) {
expect($('#LC' + line)).toHaveClass(this.css);
expect($(`#LC${line}`)).toHaveClass(this.css);
}
});
@ -130,7 +130,7 @@ describe('LineHighlighter', function() {
});
expect($('#LC13')).toHaveClass(this.css);
expect($('.' + this.css).length).toBe(1);
expect($(`.${this.css}`).length).toBe(1);
});
it('sets the hash', function() {
@ -152,9 +152,9 @@ describe('LineHighlighter', function() {
shiftKey: true,
});
expect($('.' + this.css).length).toBe(6);
expect($(`.${this.css}`).length).toBe(6);
for (line = 15; line <= 20; line += 1) {
expect($('#LC' + line)).toHaveClass(this.css);
expect($(`#LC${line}`)).toHaveClass(this.css);
}
});
@ -165,9 +165,9 @@ describe('LineHighlighter', function() {
shiftKey: true,
});
expect($('.' + this.css).length).toBe(6);
expect($(`.${this.css}`).length).toBe(6);
for (line = 5; line <= 10; line += 1) {
expect($('#LC' + line)).toHaveClass(this.css);
expect($(`#LC${line}`)).toHaveClass(this.css);
}
});
});
@ -188,9 +188,9 @@ describe('LineHighlighter', function() {
shiftKey: true,
});
expect($('.' + this.css).length).toBe(6);
expect($(`.${this.css}`).length).toBe(6);
for (line = 5; line <= 10; line += 1) {
expect($('#LC' + line)).toHaveClass(this.css);
expect($(`#LC${line}`)).toHaveClass(this.css);
}
});
@ -200,9 +200,9 @@ describe('LineHighlighter', function() {
shiftKey: true,
});
expect($('.' + this.css).length).toBe(6);
expect($(`.${this.css}`).length).toBe(6);
for (line = 10; line <= 15; line += 1) {
expect($('#LC' + line)).toHaveClass(this.css);
expect($(`#LC${line}`)).toHaveClass(this.css);
}
});
});

View File

@ -3,6 +3,9 @@
require 'spec_helper'
describe Banzai::Filter::RelativeLinkFilter do
include GitHelpers
include RepoHelpers
def filter(doc, contexts = {})
contexts.reverse_merge!({
commit: commit,
@ -34,6 +37,12 @@ describe Banzai::Filter::RelativeLinkFilter do
%(<div>#{element}</div>)
end
def allow_gitaly_n_plus_1
Gitlab::GitalyClient.allow_n_plus_1_calls do
yield
end
end
let(:project) { create(:project, :repository, :public) }
let(:user) { create(:user) }
let(:group) { nil }
@ -44,6 +53,19 @@ describe Banzai::Filter::RelativeLinkFilter do
let(:requested_path) { '/' }
let(:only_path) { true }
it 'does not trigger a gitaly n+1', :request_store do
raw_doc = ""
allow_gitaly_n_plus_1 do
30.times do |i|
create_file_in_repo(project, ref, ref, "new_file_#{i}", "x" )
raw_doc += link("new_file_#{i}")
end
end
expect { filter(raw_doc) }.to change { Gitlab::GitalyClient.get_request_count }.by(2)
end
shared_examples :preserve_unchanged do
it 'does not modify any relative URL in anchor' do
doc = filter(link('README.md'))
@ -244,7 +266,8 @@ describe Banzai::Filter::RelativeLinkFilter do
end
context 'when ref name contains special chars' do
let(:ref) {'mark#\'@],+;-._/#@!$&()+down'}
let(:ref) { 'mark#\'@],+;-._/#@!$&()+down' }
let(:path) { 'files/images/logo-black.png' }
it 'correctly escapes the ref' do
# Addressable won't escape the '#', so we do this manually
@ -252,8 +275,9 @@ describe Banzai::Filter::RelativeLinkFilter do
# Stub this method so the branch doesn't actually need to be in the repo
allow_any_instance_of(described_class).to receive(:uri_type).and_return(:raw)
allow_any_instance_of(described_class).to receive(:get_uri_types).and_return({ path: :tree })
doc = filter(link('files/images/logo-black.png'))
doc = filter(link(path))
expect(doc.at_css('a')['href'])
.to eq "/#{project_path}/raw/#{ref_escaped}/files/images/logo-black.png"