summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-09-24 12:06:20 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-09-24 12:06:20 +0000
commit9c8edcd6163f03b5ffe3af3c8fbe0706e80c4306 (patch)
tree3acfff342020d2c5e18da300b9292318bdb3aefe
parentbc89882970d6a14b1f72eb9c715fae90b26d066c (diff)
downloadgitlab-ce-9c8edcd6163f03b5ffe3af3c8fbe0706e80c4306.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/autosave.js4
-rw-r--r--app/assets/javascripts/behaviors/requires_input.js2
-rw-r--r--app/assets/javascripts/commit/image_file.js12
-rw-r--r--app/assets/javascripts/gl_dropdown.js28
-rw-r--r--app/assets/javascripts/labels_select.js15
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js4
-rw-r--r--app/assets/javascripts/line_highlighter.js10
-rw-r--r--app/assets/javascripts/namespace_select.js6
-rw-r--r--app/assets/javascripts/network/branch_graph.js4
-rw-r--r--app/assets/javascripts/new_branch_form.js6
-rw-r--r--app/assets/javascripts/notes.js20
-rw-r--r--app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js6
-rw-r--r--app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js21
-rw-r--r--app/assets/javascripts/pages/projects/network/network.js4
-rw-r--r--app/assets/javascripts/project_find_file.js4
-rw-r--r--app/assets/javascripts/right_sidebar.js4
-rw-r--r--app/assets/javascripts/search_autocomplete.js6
-rw-r--r--app/assets/javascripts/users_select.js17
-rw-r--r--changelogs/unreleased/jc-optimize-uri-type.yml5
-rw-r--r--lib/banzai/filter/relative_link_filter.rb116
-rw-r--r--lib/gitlab/gitaly_client/blob_service.rb34
-rw-r--r--spec/features/boards/boards_spec.rb6
-rw-r--r--spec/javascripts/line_highlighter_spec.js28
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb28
24 files changed, 260 insertions, 130 deletions
diff --git a/app/assets/javascripts/autosave.js b/app/assets/javascripts/autosave.js
index e8c59fab609..7652b67ae1e 100644
--- a/app/assets/javascripts/autosave.js
+++ b/app/assets/javascripts/autosave.js
@@ -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());
diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js
index d8056e48d4e..7cf18d1fd83 100644
--- a/app/assets/javascripts/behaviors/requires_input.js
+++ b/app/assets/javascripts/behaviors/requires_input.js
@@ -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();
diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js
index 0e031de0dbc..6c04e0beb4d 100644
--- a/app/assets/javascripts/commit/image_file.js
+++ b/app/assets/javascripts/commit/image_file.js
@@ -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');
});
};
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index b0b061f41fd..437c4941fda 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -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',
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index ca7f6736714..88218c3c918 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -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 = '';
}
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index fec90956a86..2e0270ee42f 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -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) {
diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js
index 4db63c841a9..b6b96fe7bd5 100644
--- a/app/assets/javascripts/line_highlighter.js
+++ b/app/assets/javascripts/line_highlighter.js
@@ -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);
diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js
index af55106d48c..8671f0fd783 100644
--- a/app/assets/javascripts/namespace_select.js
+++ b/app/assets/javascripts/namespace_select.js
@@ -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,
diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js
index 230d8f04b5e..a0ba2193d90 100644
--- a/app/assets/javascripts/network/branch_graph.js
+++ b/app/assets/javascripts/network/branch_graph.js
@@ -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',
diff --git a/app/assets/javascripts/new_branch_form.js b/app/assets/javascripts/new_branch_form.js
index 945472a9be6..9f9db21d65b 100644
--- a/app/assets/javascripts/new_branch_form.js
+++ b/app/assets/javascripts/new_branch_form.js
@@ -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) {
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 82a95969a01..3715a91d599 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -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);
diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js
index 76613394af6..5b873e6b909 100644
--- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js
+++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js
@@ -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',
});
diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js
index e37cd83280d..86794800f87 100644
--- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js
+++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js
@@ -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;
};
diff --git a/app/assets/javascripts/pages/projects/network/network.js b/app/assets/javascripts/pages/projects/network/network.js
index 226d63f05c4..43417fa9702 100644
--- a/app/assets/javascripts/pages/projects/network/network.js
+++ b/app/assets/javascripts/pages/projects/network/network.js
@@ -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`,
});
}
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
index f9f4948277d..2c375b39c1f 100644
--- a/app/assets/javascripts/project_find_file.js
+++ b/app/assets/javascripts/project_find_file.js
@@ -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));
}
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index 0cc7a22325b..87454ee056f 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -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;
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
index 2f37dcec197..f6722ff7bca 100644
--- a/app/assets/javascripts/search_autocomplete.js
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -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;
}
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index ce587615bbb..666b3f025f5 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -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>
diff --git a/changelogs/unreleased/jc-optimize-uri-type.yml b/changelogs/unreleased/jc-optimize-uri-type.yml
new file mode 100644
index 00000000000..41625abe072
--- /dev/null
+++ b/changelogs/unreleased/jc-optimize-uri-type.yml
@@ -0,0 +1,5 @@
+---
+title: Use GetBlobs RPC for uri type
+merge_request: 16824
+author:
+type: performance
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index e8001889ca3..8799b0b9a80 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -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
diff --git a/lib/gitlab/gitaly_client/blob_service.rb b/lib/gitlab/gitaly_client/blob_service.rb
index 8ccefb00d20..5cde06bb6aa 100644
--- a/lib/gitlab/gitaly_client/blob_service.rb
+++ b/lib/gitlab/gitaly_client/blob_service.rb
@@ -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
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 57dc9de62fb..25aa1c1fb7d 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -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
diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js
index a75470b4db8..f8f835ffdef 100644
--- a/spec/javascripts/line_highlighter_spec.js
+++ b/spec/javascripts/line_highlighter_spec.js
@@ -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);
}
});
});
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index f8b3748c663..8e55f12ddc5 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -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"