diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-09-24 12:06:20 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-09-24 12:06:20 +0000 |
commit | 9c8edcd6163f03b5ffe3af3c8fbe0706e80c4306 (patch) | |
tree | 3acfff342020d2c5e18da300b9292318bdb3aefe | |
parent | bc89882970d6a14b1f72eb9c715fae90b26d066c (diff) | |
download | gitlab-ce-9c8edcd6163f03b5ffe3af3c8fbe0706e80c4306.tar.gz |
Add latest changes from gitlab-org/gitlab@master
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" |