diff options
122 files changed, 959 insertions, 435 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 8298bb08b2d..a8ab6c9666a 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.43.0 +0.44.0 diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 4f01345ee3b..622764107ad 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -1,8 +1,8 @@ /* eslint-disable class-methods-use-this */ -/* global Flash */ import _ from 'underscore'; import Cookies from 'js-cookie'; import { isInIssuePage, updateTooltipTitle } from './lib/utils/common_utils'; +import Flash from './flash'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd'; diff --git a/app/assets/javascripts/blob/balsamiq_viewer.js b/app/assets/javascripts/blob/balsamiq_viewer.js index 8641a6fdae6..062577af385 100644 --- a/app/assets/javascripts/blob/balsamiq_viewer.js +++ b/app/assets/javascripts/blob/balsamiq_viewer.js @@ -1,9 +1,8 @@ -/* global Flash */ - +import Flash from '../flash'; import BalsamiqViewer from './balsamiq/balsamiq_viewer'; function onError() { - const flash = new window.Flash('Balsamiq file could not be loaded.'); + const flash = new Flash('Balsamiq file could not be loaded.'); return flash; } diff --git a/app/assets/javascripts/blob/file_template_mediator.js b/app/assets/javascripts/blob/file_template_mediator.js index a20c6ca7a21..583e5faa506 100644 --- a/app/assets/javascripts/blob/file_template_mediator.js +++ b/app/assets/javascripts/blob/file_template_mediator.js @@ -1,6 +1,5 @@ /* eslint-disable class-methods-use-this */ -/* global Flash */ - +import Flash from '../flash'; import FileTemplateTypeSelector from './template_selectors/type_selector'; import BlobCiYamlSelector from './template_selectors/ci_yaml_selector'; import DockerfileSelector from './template_selectors/dockerfile_selector'; diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js index e0b73f13d36..54132e8537b 100644 --- a/app/assets/javascripts/blob/viewer/index.js +++ b/app/assets/javascripts/blob/viewer/index.js @@ -1,4 +1,4 @@ -/* global Flash */ +import Flash from '../../flash'; import { handleLocationHash } from '../../lib/utils/common_utils'; export default class BlobViewer { diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js index 815248f38ee..ef4093b59e3 100644 --- a/app/assets/javascripts/boards/boards_bundle.js +++ b/app/assets/javascripts/boards/boards_bundle.js @@ -1,10 +1,10 @@ /* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren */ /* global BoardService */ -/* global Flash */ import _ from 'underscore'; import Vue from 'vue'; import VueResource from 'vue-resource'; +import Flash from '../flash'; import FilteredSearchBoards from './filtered_search_boards'; import eventHub from './eventhub'; import './models/issue'; diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index 590b7be36e3..7f3afefc9cc 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -3,9 +3,9 @@ /* global MilestoneSelect */ /* global LabelsSelect */ /* global Sidebar */ -/* global Flash */ import Vue from 'vue'; +import Flash from '../../flash'; import eventHub from '../../sidebar/event_hub'; import AssigneeTitle from '../../sidebar/components/assignees/assignee_title'; import Assignees from '../../sidebar/components/assignees/assignees'; diff --git a/app/assets/javascripts/boards/components/modal/footer.js b/app/assets/javascripts/boards/components/modal/footer.js index a656f0546c0..de9e44cef35 100644 --- a/app/assets/javascripts/boards/components/modal/footer.js +++ b/app/assets/javascripts/boards/components/modal/footer.js @@ -1,7 +1,7 @@ /* eslint-disable no-new */ -/* global Flash */ import Vue from 'vue'; +import Flash from '../../../flash'; import './lists_dropdown'; const ModalStore = gl.issueBoards.ModalStore; diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js index 1e623cf58b7..1ad97211934 100644 --- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js +++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js @@ -1,7 +1,7 @@ /* eslint-disable no-new */ -/* global Flash */ import Vue from 'vue'; +import Flash from '../../../flash'; const Store = gl.issueBoards.BoardsStore; diff --git a/app/assets/javascripts/build_artifacts.js b/app/assets/javascripts/build_artifacts.js index 19388f1f9ae..ace89398943 100644 --- a/app/assets/javascripts/build_artifacts.js +++ b/app/assets/javascripts/build_artifacts.js @@ -1,30 +1,30 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, no-return-assign, max-len */ +/* eslint-disable func-names, prefer-arrow-callback, no-return-assign */ import { visitUrl } from './lib/utils/url_utility'; import { convertPermissionToBoolean } from './lib/utils/common_utils'; -window.BuildArtifacts = (function() { - function BuildArtifacts() { +export default class BuildArtifacts { + constructor() { this.disablePropagation(); this.setupEntryClick(); this.setupTooltips(); } - - BuildArtifacts.prototype.disablePropagation = function() { - $('.top-block').on('click', '.download', function(e) { + // eslint-disable-next-line class-methods-use-this + disablePropagation() { + $('.top-block').on('click', '.download', function (e) { return e.stopPropagation(); }); - return $('.tree-holder').on('click', 'tr[data-link] a', function(e) { + return $('.tree-holder').on('click', 'tr[data-link] a', function (e) { return e.stopImmediatePropagation(); }); - }; - - BuildArtifacts.prototype.setupEntryClick = function() { - return $('.tree-holder').on('click', 'tr[data-link]', function(e) { + } + // eslint-disable-next-line class-methods-use-this + setupEntryClick() { + return $('.tree-holder').on('click', 'tr[data-link]', function () { visitUrl(this.dataset.link, convertPermissionToBoolean(this.dataset.externalLink)); }); - }; - - BuildArtifacts.prototype.setupTooltips = function() { + } + // eslint-disable-next-line class-methods-use-this + setupTooltips() { $('.js-artifact-tree-tooltip').tooltip({ placement: 'bottom', // Stop the tooltip from hiding when we stop hovering the element directly @@ -41,7 +41,5 @@ window.BuildArtifacts = (function() { .on('mouseleave', (e) => { $(e.currentTarget).find('.js-artifact-tree-tooltip').tooltip('hide'); }); - }; - - return BuildArtifacts; -})(); + } +} diff --git a/app/assets/javascripts/build_variables.js b/app/assets/javascripts/build_variables.js index c955a9ac2ea..35edf3e0017 100644 --- a/app/assets/javascripts/build_variables.js +++ b/app/assets/javascripts/build_variables.js @@ -1,8 +1,10 @@ -/* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren */ +/* eslint-disable func-names*/ -$(function() { - $('.reveal-variables').off('click').on('click', function() { - $('.js-build-variables').toggle(); - $(this).hide(); - }); -}); +export default function handleRevealVariables() { + $('.js-reveal-variables') + .off('click') + .on('click', function () { + $('.js-build-variables').toggle(); + $(this).hide(); + }); +} diff --git a/app/assets/javascripts/ci_lint_editor.js b/app/assets/javascripts/ci_lint_editor.js index dd4a08a2f31..b9469e5b7cb 100644 --- a/app/assets/javascripts/ci_lint_editor.js +++ b/app/assets/javascripts/ci_lint_editor.js @@ -1,7 +1,4 @@ - -window.gl = window.gl || {}; - -class CILintEditor { +export default class CILintEditor { constructor() { this.editor = window.ace.edit('ci-editor'); this.textarea = document.querySelector('#content'); @@ -13,5 +10,3 @@ class CILintEditor { }); } } - -gl.CILintEditor = CILintEditor; diff --git a/app/assets/javascripts/create_merge_request_dropdown.js b/app/assets/javascripts/create_merge_request_dropdown.js index ff2f2c81971..bf40eb3ee11 100644 --- a/app/assets/javascripts/create_merge_request_dropdown.js +++ b/app/assets/javascripts/create_merge_request_dropdown.js @@ -1,5 +1,5 @@ /* eslint-disable no-new */ -/* global Flash */ +import Flash from './flash'; import DropLab from './droplab/drop_lab'; import ISetter from './droplab/plugins/input_setter'; diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js index cdf5e3c0290..49bb6c52180 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js @@ -1,7 +1,6 @@ -/* global Flash */ - import Vue from 'vue'; import Cookies from 'js-cookie'; +import Flash from '../flash'; import Translate from '../vue_shared/translate'; import banner from './components/banner.vue'; import stageCodeComponent from './components/stage_code_component.vue'; diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue index a663e30dfd0..54e13b79a4f 100644 --- a/app/assets/javascripts/deploy_keys/components/app.vue +++ b/app/assets/javascripts/deploy_keys/components/app.vue @@ -1,5 +1,5 @@ <script> - /* global Flash */ + import Flash from '../../flash'; import eventHub from '../eventhub'; import DeployKeysService from '../service'; import DeployKeysStore from '../store'; diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js b/app/assets/javascripts/diff_notes/components/resolve_btn.js index efb6ced9f46..20ddcbfb8bd 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_btn.js +++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js @@ -1,9 +1,9 @@ /* eslint-disable comma-dangle, object-shorthand, func-names, quote-props, no-else-return, camelcase, max-len */ /* global CommentsStore */ /* global ResolveService */ -/* global Flash */ import Vue from 'vue'; +import Flash from '../../flash'; const ResolveBtn = Vue.extend({ props: { diff --git a/app/assets/javascripts/diff_notes/services/resolve.js b/app/assets/javascripts/diff_notes/services/resolve.js index 2f063f6fe1f..6eae54f830b 100644 --- a/app/assets/javascripts/diff_notes/services/resolve.js +++ b/app/assets/javascripts/diff_notes/services/resolve.js @@ -1,7 +1,7 @@ -/* global Flash */ /* global CommentsStore */ import Vue from 'vue'; +import Flash from '../../flash'; import '../../vue_shared/vue_resource_interceptor'; window.gl = window.gl || {}; diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index ad4bdeba01e..d43eae79730 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -12,7 +12,8 @@ /* global NotificationsDropdown */ /* global GroupAvatar */ /* global LineHighlighter */ -/* global BuildArtifacts */ +import BuildArtifacts from './build_artifacts'; +import CILintEditor from './ci_lint_editor'; /* global GroupsSelect */ /* global Search */ /* global Admin */ @@ -91,8 +92,8 @@ import U2FAuthenticate from './u2f/authenticate'; } Dispatcher.prototype.initPageScripts = function() { - var page, path, shortcut_handler, fileBlobPermalinkUrlElement, fileBlobPermalinkUrl; - page = $('body').attr('data-page'); + var path, shortcut_handler, fileBlobPermalinkUrlElement, fileBlobPermalinkUrl; + const page = $('body').attr('data-page'); if (!page) { return false; } @@ -506,7 +507,7 @@ import U2FAuthenticate from './u2f/authenticate'; break; case 'ci:lints:create': case 'ci:lints:show': - new gl.CILintEditor(); + new CILintEditor(); break; case 'users:show': new UserCallout(); diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index 1cba65d17cd..bd45da8c422 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -237,9 +237,12 @@ window.DropzoneInput = (function() { }; const insertToTextArea = function(filename, url) { - return $(child).val(function(index, val) { + const $child = $(child); + $child.val(function(index, val) { return val.replace(`{{${filename}}}`, url); }); + + $child.trigger('change'); }; const appendToTextArea = function(url) { diff --git a/app/assets/javascripts/environments/components/environment.vue b/app/assets/javascripts/environments/components/environment.vue index ce5f6219a3e..c039ae85cfb 100644 --- a/app/assets/javascripts/environments/components/environment.vue +++ b/app/assets/javascripts/environments/components/environment.vue @@ -1,6 +1,6 @@ <script> -/* global Flash */ import Visibility from 'visibilityjs'; +import Flash from '../../flash'; import EnvironmentsService from '../services/environments_service'; import environmentTable from './environments_table.vue'; import EnvironmentsStore from '../stores/environments_store'; diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue index 01e70c0bbb7..b155560df9d 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_view.vue +++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue @@ -1,6 +1,6 @@ <script> -/* global Flash */ import Visibility from 'visibilityjs'; +import Flash from '../../flash'; import EnvironmentsService from '../services/environments_service'; import environmentTable from '../components/environments_table.vue'; import EnvironmentsStore from '../stores/environments_store'; diff --git a/app/assets/javascripts/filtered_search/dropdown_emoji.js b/app/assets/javascripts/filtered_search/dropdown_emoji.js index ada14d2053c..a6cc079d720 100644 --- a/app/assets/javascripts/filtered_search/dropdown_emoji.js +++ b/app/assets/javascripts/filtered_search/dropdown_emoji.js @@ -1,7 +1,6 @@ -/* global Flash */ - -import Ajax from '~/droplab/plugins/ajax'; -import Filter from '~/droplab/plugins/filter'; +import Flash from '../flash'; +import Ajax from '../droplab/plugins/ajax'; +import Filter from '../droplab/plugins/filter'; import './filtered_search_dropdown'; class DropdownEmoji extends gl.FilteredSearchDropdown { diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js b/app/assets/javascripts/filtered_search/dropdown_non_user.js index b32d589481d..788fb1dc614 100644 --- a/app/assets/javascripts/filtered_search/dropdown_non_user.js +++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js @@ -1,7 +1,6 @@ -/* global Flash */ - -import Ajax from '~/droplab/plugins/ajax'; -import Filter from '~/droplab/plugins/filter'; +import Flash from '../flash'; +import Ajax from '../droplab/plugins/ajax'; +import Filter from '../droplab/plugins/filter'; import './filtered_search_dropdown'; class DropdownNonUser extends gl.FilteredSearchDropdown { diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js b/app/assets/javascripts/filtered_search/dropdown_user.js index ce8817b1b2e..a9e2b65def0 100644 --- a/app/assets/javascripts/filtered_search/dropdown_user.js +++ b/app/assets/javascripts/filtered_search/dropdown_user.js @@ -1,6 +1,5 @@ -/* global Flash */ - -import AjaxFilter from '~/droplab/plugins/ajax_filter'; +import Flash from '../flash'; +import AjaxFilter from '../droplab/plugins/ajax_filter'; import './filtered_search_dropdown'; import { addClassIfElementExists } from '../lib/utils/dom_utils'; diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index a44dc279a6f..7b233842d5a 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -1,3 +1,4 @@ +import Flash from '../flash'; import FilteredSearchContainer from './container'; import RecentSearchesRoot from './recent_searches_root'; import RecentSearchesStore from './stores/recent_searches_store'; @@ -36,7 +37,7 @@ class FilteredSearchManager { .catch((error) => { if (error.name === 'RecentSearchesServiceError') return undefined; // eslint-disable-next-line no-new - new window.Flash('An error occurred while parsing recent searches'); + new Flash('An error occurred while parsing recent searches'); // Gracefully fail to empty array return []; }) diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js index 28e8240169d..dd24fc44d2a 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js +++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js @@ -1,5 +1,5 @@ import AjaxCache from '../lib/utils/ajax_cache'; -import '../flash'; /* global Flash */ +import Flash from '../flash'; import FilteredSearchContainer from './container'; import UsersCache from '../lib/utils/users_cache'; diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js index ccff8f0ace7..bc5cd818e1c 100644 --- a/app/assets/javascripts/flash.js +++ b/app/assets/javascripts/flash.js @@ -1,71 +1,94 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, no-param-reassign, quotes, quote-props, prefer-template, comma-dangle, max-len */ +import _ from 'underscore'; -window.Flash = (function() { - var hideFlash; +const hideFlash = (flashEl, fadeTransition = true) => { + if (fadeTransition) { + Object.assign(flashEl.style, { + transition: 'opacity .3s', + opacity: '0', + }); + } - hideFlash = function() { - return $(this).fadeOut(); - }; + flashEl.addEventListener('transitionend', () => { + flashEl.remove(); + }, { + once: true, + passive: true, + }); - /** - * Flash banner supports different types of Flash configurations - * along with ability to provide actionConfig which can be used to show - * additional action or link on banner next to message - * - * @param {String} message Flash message - * @param {String} type Type of Flash, it can be `notice` or `alert` (default) - * @param {Object} parent Reference to Parent element under which Flash needs to appear - * @param {Object} actionConfig Map of config to show action on banner - * @param {String} href URL to which action link should point (default '#') - * @param {String} title Title of action - * @param {Function} clickHandler Method to call when action is clicked on - */ - function Flash(message, type, parent, actionConfig) { - var flash, textDiv, actionLink; - if (type == null) { - type = 'alert'; - } - if (parent == null) { - parent = null; - } - if (parent) { - this.flashContainer = parent.find('.flash-container'); - } else { - this.flashContainer = $('.flash-container-page'); - } - this.flashContainer.html(''); - flash = $('<div/>', { - "class": "flash-" + type - }); - flash.on('click', hideFlash); - textDiv = $('<div/>', { - "class": 'flash-text', - text: message - }); - textDiv.appendTo(flash); + if (!fadeTransition) flashEl.dispatchEvent(new Event('transitionend')); +}; - if (actionConfig) { - const actionLinkConfig = { - class: 'flash-action', - href: actionConfig.href || '#', - text: actionConfig.title - }; +const createAction = config => ` + <a + href="${config.href || '#'}" + class="flash-action" + ${config.href ? '' : 'role="button"'} + > + ${_.escape(config.title)} + </a> +`; - if (!actionConfig.href) { - actionLinkConfig.role = 'button'; - } +const createFlashEl = (message, type, isInContentWrapper = false) => ` + <div + class="flash-${type}" + > + <div + class="flash-text ${isInContentWrapper ? 'container-fluid container-limited' : ''}" + > + ${_.escape(message)} + </div> + </div> +`; - actionLink = $('<a/>', actionLinkConfig); +/* + * Flash banner supports different types of Flash configurations + * along with ability to provide actionConfig which can be used to show + * additional action or link on banner next to message + * + * @param {String} message Flash message text + * @param {String} type Type of Flash, it can be `notice` or `alert` (default) + * @param {Object} parent Reference to parent element under which Flash needs to appear + * @param {Object} actonConfig Map of config to show action on banner + * @param {String} href URL to which action config should point to (default: '#') + * @param {String} title Title of action + * @param {Function} clickHandler Method to call when action is clicked on + * @param {Boolean} fadeTransition Boolean to determine whether to fade the alert out + */ +const createFlash = function createFlash( + message, + type = 'alert', + parent = document, + actionConfig = null, + fadeTransition = true, +) { + const flashContainer = parent.querySelector('.flash-container'); - actionLink.appendTo(flash); - this.flashContainer.on('click', '.flash-action', actionConfig.clickHandler); - } - if (this.flashContainer.parent().hasClass('content-wrapper')) { - textDiv.addClass('container-fluid container-limited'); + if (!flashContainer) return null; + + const isInContentWrapper = flashContainer.parentNode.classList.contains('content-wrapper'); + + flashContainer.innerHTML = createFlashEl(message, type, isInContentWrapper); + + const flashEl = flashContainer.querySelector(`.flash-${type}`); + flashEl.addEventListener('click', () => hideFlash(flashEl, fadeTransition)); + + if (actionConfig) { + flashEl.innerHTML += createAction(actionConfig); + + if (actionConfig.clickHandler) { + flashEl.querySelector('.flash-action').addEventListener('click', e => actionConfig.clickHandler(e)); } - flash.appendTo(this.flashContainer); - this.flashContainer.show(); } - return Flash; -})(); + flashContainer.style.display = 'block'; + + return flashContainer; +}; + +export { + createFlash as default, + createFlashEl, + createAction, + hideFlash, +}; +window.Flash = createFlash; diff --git a/app/assets/javascripts/groups/index.js b/app/assets/javascripts/groups/index.js index 9ad8e5c6052..600bae24b52 100644 --- a/app/assets/javascripts/groups/index.js +++ b/app/assets/javascripts/groups/index.js @@ -1,6 +1,5 @@ -/* global Flash */ - import Vue from 'vue'; +import Flash from '../flash'; import GroupFilterableList from './groups_filterable_list'; import GroupsComponent from './components/groups.vue'; import GroupFolder from './components/group_folder.vue'; diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js index dc170c60456..ea2e2205077 100644 --- a/app/assets/javascripts/header.js +++ b/app/assets/javascripts/header.js @@ -1,7 +1,16 @@ -/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var */ +import { highCountTrim } from '~/lib/utils/text_utility'; -$(document).on('todo:toggle', function(e, count) { - var $todoPendingCount = $('.todos-count'); - $todoPendingCount.text(gl.text.highCountTrim(count)); - $todoPendingCount.toggleClass('hidden', count === 0); +/** + * Updates todo counter when todos are toggled. + * When count is 0, we hide the badge. + * + * @param {jQuery.Event} e + * @param {String} count + */ +$(document).on('todo:toggle', (e, count) => { + const parsedCount = parseInt(count, 10); + const $todoPendingCount = $('.todos-count'); + + $todoPendingCount.text(highCountTrim(parsedCount)); + $todoPendingCount.toggleClass('hidden', parsedCount === 0); }); diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js index cf1e6a14725..32415a8791f 100644 --- a/app/assets/javascripts/integrations/integration_settings_form.js +++ b/app/assets/javascripts/integrations/integration_settings_form.js @@ -1,4 +1,4 @@ -/* global Flash */ +import Flash from '../flash'; export default class IntegrationSettingsForm { constructor(formSelector) { @@ -102,7 +102,7 @@ export default class IntegrationSettingsForm { }) .done((res) => { if (res.error) { - new Flash(`${res.message} ${res.service_response}`, null, null, { + new Flash(`${res.message} ${res.service_response}`, 'alert', document, { title: 'Save anyway', clickHandler: (e) => { e.preventDefault(); diff --git a/app/assets/javascripts/issuable_bulk_update_actions.js b/app/assets/javascripts/issuable_bulk_update_actions.js index c39ffdb2e0f..eb15949603f 100644 --- a/app/assets/javascripts/issuable_bulk_update_actions.js +++ b/app/assets/javascripts/issuable_bulk_update_actions.js @@ -1,7 +1,7 @@ /* eslint-disable comma-dangle, quotes, consistent-return, func-names, array-callback-return, space-before-function-paren, prefer-arrow-callback, max-len, no-unused-expressions, no-sequences, no-underscore-dangle, no-unused-vars, no-param-reassign */ /* global IssuableIndex */ -/* global Flash */ import _ from 'underscore'; +import Flash from './flash'; export default { init({ container, form, issues, prefixId } = {}) { diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js index c0bd64814ca..3fc29f9a661 100644 --- a/app/assets/javascripts/issue.js +++ b/app/assets/javascripts/issue.js @@ -1,9 +1,7 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */ -/* global Flash */ - import 'vendor/jquery.waitforimages'; import '~/lib/utils/text_utility'; -import './flash'; +import Flash from './flash'; import TaskList from './task_list'; import CreateMergeRequestDropdown from './create_merge_request_dropdown'; import IssuablesHelper from './helpers/issuables_helper'; diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue index 06f6ec241f4..eecb56cb185 100644 --- a/app/assets/javascripts/issue_show/components/app.vue +++ b/app/assets/javascripts/issue_show/components/app.vue @@ -1,5 +1,4 @@ <script> -/* global Flash */ import Visibility from 'visibilityjs'; import Poll from '../../lib/utils/poll'; import eventHub from '../event_hub'; @@ -153,7 +152,7 @@ export default { }) .catch(() => { eventHub.$emit('close.form'); - return new Flash('Error updating issue'); + window.Flash('Error updating issue'); }); }, deleteIssuable() { @@ -167,7 +166,7 @@ export default { }) .catch(() => { eventHub.$emit('close.form'); - return new Flash('Error deleting issue'); + window.Flash('Error deleting issue'); }); }, }, diff --git a/app/assets/javascripts/issue_show/components/fields/description.vue b/app/assets/javascripts/issue_show/components/fields/description.vue index dc902eefc5f..0aa1b2c2e31 100644 --- a/app/assets/javascripts/issue_show/components/fields/description.vue +++ b/app/assets/javascripts/issue_show/components/fields/description.vue @@ -1,5 +1,4 @@ <script> - /* global Flash */ import updateMixin from '../../mixins/update'; import markdownField from '../../../vue_shared/components/markdown/field.vue'; diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/job.js index 3d27a3544eb..c6b5844dff6 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/job.js @@ -1,15 +1,12 @@ -/* eslint-disable func-names, wrap-iife, no-use-before-define, -consistent-return, prefer-rest-params */ import _ from 'underscore'; import bp from './breakpoints'; import { bytesToKiB } from './lib/utils/number_utils'; import { setCiStatusFavicon } from './lib/utils/common_utils'; -window.Build = (function () { - Build.timeout = null; - Build.state = null; - - function Build(options) { +export default class Job { + constructor(options) { + this.timeout = null; + this.state = null; this.options = options || $('.js-build-options').data(); this.pageUrl = this.options.pageUrl; @@ -19,9 +16,7 @@ window.Build = (function () { this.$document = $(document); this.logBytes = 0; this.hasBeenScrolled = false; - this.updateDropdown = this.updateDropdown.bind(this); - this.getBuildTrace = this.getBuildTrace.bind(this); this.$buildTrace = $('#build-trace'); this.$buildRefreshAnimation = $('.js-build-refresh'); @@ -33,7 +28,7 @@ window.Build = (function () { this.$scrollTopBtn = $('.js-scroll-up'); this.$scrollBottomBtn = $('.js-scroll-down'); - clearTimeout(Build.timeout); + clearTimeout(this.timeout); this.initSidebar(); this.populateJobs(this.buildStage); @@ -85,7 +80,7 @@ window.Build = (function () { this.getBuildTrace(); } - Build.prototype.initAffixTopArea = function () { + initAffixTopArea() { /** If the browser does not support position sticky, it returns the position as static. If the browser does support sticky, then we allow the browser to handle it, if not @@ -100,13 +95,14 @@ window.Build = (function () { top: offsetTop, }, }); - }; + } - Build.prototype.canScroll = function () { + // eslint-disable-next-line class-methods-use-this + canScroll() { return $(document).height() > $(window).height(); - }; + } - Build.prototype.toggleScroll = function () { + toggleScroll() { const currentPosition = $(document).scrollTop(); const scrollHeight = $(document).height(); @@ -119,7 +115,7 @@ window.Build = (function () { this.toggleDisableButton(this.$scrollTopBtn, false); this.toggleDisableButton(this.$scrollBottomBtn, false); } else if (currentPosition === 0) { - // User is at Top of Build Log + // User is at Top of Log this.toggleDisableButton(this.$scrollTopBtn, true); this.toggleDisableButton(this.$scrollBottomBtn, false); @@ -133,38 +129,40 @@ window.Build = (function () { this.toggleDisableButton(this.$scrollTopBtn, true); this.toggleDisableButton(this.$scrollBottomBtn, true); } - }; + } - Build.prototype.scrollDown = function () { + // eslint-disable-next-line class-methods-use-this + scrollDown() { $(document).scrollTop($(document).height()); - }; + } - Build.prototype.scrollToBottom = function () { + scrollToBottom() { this.scrollDown(); this.hasBeenScrolled = true; this.toggleScroll(); - }; + } - Build.prototype.scrollToTop = function () { + scrollToTop() { $(document).scrollTop(0); this.hasBeenScrolled = true; this.toggleScroll(); - }; + } - Build.prototype.toggleDisableButton = function ($button, disable) { + // eslint-disable-next-line class-methods-use-this + toggleDisableButton($button, disable) { if (disable && $button.prop('disabled')) return; $button.prop('disabled', disable); - }; + } - Build.prototype.toggleScrollAnimation = function (toggle) { + toggleScrollAnimation(toggle) { this.$scrollBottomBtn.toggleClass('animate', toggle); - }; + } - Build.prototype.initSidebar = function () { + initSidebar() { this.$sidebar = $('.js-build-sidebar'); - }; + } - Build.prototype.getBuildTrace = function () { + getBuildTrace() { return $.ajax({ url: `${this.pageUrl}/trace.json`, data: { state: this.state }, @@ -204,7 +202,7 @@ window.Build = (function () { this.toggleScrollAnimation(false); } - Build.timeout = setTimeout(() => { + this.timeout = setTimeout(() => { this.getBuildTrace(); }, 4000); } else { @@ -225,14 +223,14 @@ window.Build = (function () { } }) .then(() => this.toggleScroll()); - }; - - Build.prototype.shouldHideSidebarForViewport = function () { + } + // eslint-disable-next-line class-methods-use-this + shouldHideSidebarForViewport() { const bootstrapBreakpoint = bp.getBreakpointSize(); return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm'; - }; + } - Build.prototype.toggleSidebar = function (shouldHide) { + toggleSidebar(shouldHide) { const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined; const $toggleButton = $('.js-sidebar-build-toggle-header'); @@ -249,17 +247,17 @@ window.Build = (function () { } else { $toggleButton.removeClass('hidden'); } - }; + } - Build.prototype.sidebarOnResize = function () { + sidebarOnResize() { this.toggleSidebar(this.shouldHideSidebarForViewport()); - }; + } - Build.prototype.sidebarOnClick = function () { + sidebarOnClick() { if (this.shouldHideSidebarForViewport()) this.toggleSidebar(); - }; - - Build.prototype.updateArtifactRemoveDate = function () { + } + // eslint-disable-next-line class-methods-use-this, consistent-return + updateArtifactRemoveDate() { const $date = $('.js-artifacts-remove'); if ($date.length) { const date = $date.text(); @@ -267,23 +265,21 @@ window.Build = (function () { gl.utils.timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '), ); } - }; - - Build.prototype.populateJobs = function (stage) { + } + // eslint-disable-next-line class-methods-use-this + populateJobs(stage) { $('.build-job').hide(); $(`.build-job[data-stage="${stage}"]`).show(); - }; - - Build.prototype.updateStageDropdownText = function (stage) { + } + // eslint-disable-next-line class-methods-use-this + updateStageDropdownText(stage) { $('.stage-selection').text(stage); - }; + } - Build.prototype.updateDropdown = function (e) { + updateDropdown(e) { e.preventDefault(); const stage = e.currentTarget.text; this.updateStageDropdownText(stage); this.populateJobs(stage); - }; - - return Build; -})(); + } +} diff --git a/app/assets/javascripts/jobs/job_details_bundle.js b/app/assets/javascripts/jobs/job_details_bundle.js index f92e669414a..baaf5641200 100644 --- a/app/assets/javascripts/jobs/job_details_bundle.js +++ b/app/assets/javascripts/jobs/job_details_bundle.js @@ -1,5 +1,3 @@ -/* global Flash */ - import Vue from 'vue'; import JobMediator from './job_details_mediator'; import jobHeader from './components/header.vue'; diff --git a/app/assets/javascripts/jobs/job_details_mediator.js b/app/assets/javascripts/jobs/job_details_mediator.js index cc014b815c4..3e2658f9fc1 100644 --- a/app/assets/javascripts/jobs/job_details_mediator.js +++ b/app/assets/javascripts/jobs/job_details_mediator.js @@ -1,11 +1,12 @@ -/* global Flash */ /* global Build */ import Visibility from 'visibilityjs'; +import Flash from '../flash'; import Poll from '../lib/utils/poll'; import JobStore from './stores/job_store'; import JobService from './services/job_service'; -import '../build'; +import Job from '../job'; +import handleRevealVariables from '../build_variables'; export default class JobMediator { constructor(options = {}) { @@ -20,7 +21,8 @@ export default class JobMediator { } initBuildClass() { - this.build = new Build(); + this.build = new Job(); + handleRevealVariables(); } fetchJob() { diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js index d8814802d9e..a8f613c6cf2 100644 --- a/app/assets/javascripts/label_manager.js +++ b/app/assets/javascripts/label_manager.js @@ -1,7 +1,8 @@ /* eslint-disable comma-dangle, class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, consistent-return, func-names, space-before-function-paren, max-len */ -/* global Flash */ /* global Sortable */ +import Flash from './flash'; + ((global) => { class LabelManager { constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) { diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index 021f936a4fa..f776829f69c 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len, vars-on-top */ +/* eslint-disable import/prefer-default-export, func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len, vars-on-top */ import 'vendor/latinise'; @@ -13,9 +13,17 @@ if ((base = w.gl).text == null) { gl.text.addDelimiter = function(text) { return text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : text; }; -gl.text.highCountTrim = function(count) { + +/** + * Returns '99+' for numbers bigger than 99. + * + * @param {Number} count + * @return {Number|String} + */ +export function highCountTrim(count) { return count > 99 ? '99+' : count; -}; +} + gl.text.randomString = function() { return Math.random().toString(36).substring(7); }; diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 7c703f7be9d..50aa445e9e7 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -1,5 +1,4 @@ /* eslint-disable func-names, space-before-function-paren, no-var, quotes, consistent-return, prefer-arrow-callback, comma-dangle, object-shorthand, no-new, max-len, no-multi-spaces, import/newline-after-import, import/first */ -/* global Flash */ /* global ConfirmDangerModal */ /* global Aside */ @@ -50,16 +49,11 @@ import './behaviors/'; // everything else import './activities'; import './admin'; -import './api'; import './aside'; import './autosave'; import loadAwardsHandler from './awards_handler'; import bp from './breakpoints'; import './broadcast_message'; -import './build'; -import './build_artifacts'; -import './build_variables'; -import './ci_lint_editor'; import './commits'; import './compare'; import './compare_autocomplete'; @@ -70,7 +64,7 @@ import './diff'; import './dropzone_input'; import './due_date_select'; import './files_comment_button'; -import './flash'; +import Flash from './flash'; import './gl_dropdown'; import './gl_field_error'; import './gl_field_errors'; @@ -163,7 +157,6 @@ $(function () { var $document = $(document); var $window = $(window); var $sidebarGutterToggle = $('.js-sidebar-toggle'); - var $flash = $('.flash-container'); var bootstrapBreakpoint = bp.getBreakpointSize(); var fitSidebarForSize; @@ -248,13 +241,6 @@ $(function () { // Form submitter }); gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true); - // Flash - if ($flash.length > 0) { - $flash.click(function () { - return $(this).fadeOut(); - }); - $flash.show(); - } // Disable form buttons while a form is submitting $body.on('ajax:complete, ajax:beforeSend, submit', 'form', function (e) { var buttons; diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js index 645045fea88..93f8f6ee926 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js @@ -1,8 +1,8 @@ /* eslint-disable comma-dangle, quote-props, no-useless-computed-key, object-shorthand, no-new, no-param-reassign, max-len */ /* global ace */ -/* global Flash */ import Vue from 'vue'; +import Flash from '../../flash'; ((global) => { global.mergeConflicts = global.mergeConflicts || {}; diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js index d74cf5328ad..17591829b76 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js +++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js @@ -1,7 +1,7 @@ /* eslint-disable new-cap, comma-dangle, no-new */ -/* global Flash */ import Vue from 'vue'; +import Flash from '../flash'; import initIssuableSidebar from '../init_issuable_sidebar'; import './merge_conflict_store'; import './merge_conflict_service'; diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index c042b22d1fd..df042c7baff 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -1,9 +1,8 @@ /* eslint-disable no-new, class-methods-use-this */ -/* global Flash */ /* global notes */ import Cookies from 'js-cookie'; -import './flash'; +import Flash from './flash'; import BlobForkSuggestion from './blob/blob_fork_suggestion'; import initChangesDropdown from './init_changes_dropdown'; import bp from './breakpoints'; diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js index 3e07ec4d0aa..8f3f1986763 100644 --- a/app/assets/javascripts/milestone.js +++ b/app/assets/javascripts/milestone.js @@ -1,7 +1,8 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-use-before-define, camelcase, quotes, object-shorthand, no-shadow, no-unused-vars, comma-dangle, no-var, prefer-template, no-underscore-dangle, consistent-return, one-var, one-var-declaration-per-line, default-case, prefer-arrow-callback, max-len */ -/* global Flash */ /* global Sortable */ +import Flash from './flash'; + (function() { this.Milestone = (function() { function Milestone() { diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js b/app/assets/javascripts/mini_pipeline_graph_dropdown.js index 64c1447f427..ca3d271663b 100644 --- a/app/assets/javascripts/mini_pipeline_graph_dropdown.js +++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js @@ -1,5 +1,5 @@ /* eslint-disable no-new */ -/* global Flash */ +import Flash from './flash'; /** * In each pipelines table we have a mini pipeline graph for each pipeline. diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index 442ed86d50c..cbe24c0915b 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -1,6 +1,6 @@ <script> - /* global Flash */ import _ from 'underscore'; + import Flash from '../../flash'; import MonitoringService from '../services/monitoring_service'; import GraphGroup from './graph_group.vue'; import Graph from './graph.vue'; diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 24de21f2ce2..cf7322ba1da 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -5,7 +5,6 @@ default-case, prefer-template, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape, class-methods-use-this */ -/* global Flash */ /* global Autosave */ /* global ResolveService */ /* global mrRefreshWidgetUrl */ @@ -18,6 +17,7 @@ import Dropzone from 'dropzone'; import 'vendor/jquery.caret'; // required by jquery.atwho import 'vendor/jquery.atwho'; import AjaxCache from '~/lib/utils/ajax_cache'; +import Flash from './flash'; import CommentTypeToggle from './comment_type_toggle'; import loadAwardsHandler from './awards_handler'; import './autosave'; @@ -354,7 +354,7 @@ export default class Notes { Object.keys(noteEntity.commands_changes).length > 0) { $notesList.find('.system-note.being-posted').remove(); } - this.addFlash(noteEntity.errors.commands_only, 'notice', this.parentTimeline); + this.addFlash(noteEntity.errors.commands_only, 'notice', this.parentTimeline.get(0)); this.refresh(); } return; @@ -593,7 +593,7 @@ export default class Notes { } else if ($form.hasClass('js-discussion-note-form')) { formParentTimeline = $form.closest('.discussion-notes').find('.notes'); } - return this.addFlash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', formParentTimeline); + return this.addFlash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', formParentTimeline.get(0)); } updateNoteError($parentTimeline) { @@ -1213,13 +1213,13 @@ export default class Notes { } addFlash(...flashParams) { - this.flashInstance = new Flash(...flashParams); + this.flashContainer = new Flash(...flashParams); } clearFlash() { - if (this.flashInstance && this.flashInstance.flashContainer) { - this.flashInstance.flashContainer.hide(); - this.flashInstance = null; + if (this.flashContainer) { + this.flashContainer.style.display = 'none'; + this.flashContainer = null; } } diff --git a/app/assets/javascripts/notes/components/issue_comment_form.vue b/app/assets/javascripts/notes/components/issue_comment_form.vue index ab8516296a8..2ce52e4538a 100644 --- a/app/assets/javascripts/notes/components/issue_comment_form.vue +++ b/app/assets/javascripts/notes/components/issue_comment_form.vue @@ -1,8 +1,9 @@ <script> - /* global Flash, Autosave */ + /* global Autosave */ import { mapActions, mapGetters } from 'vuex'; import _ from 'underscore'; import autosize from 'vendor/autosize'; + import Flash from '../../flash'; import '../../autosave'; import TaskList from '../../task_list'; import * as constants from '../constants'; @@ -145,7 +146,7 @@ Flash( 'Something went wrong while adding your comment. Please try again.', 'alert', - $(this.$refs.commentForm), + this.$refs.commentForm, ); } } else { @@ -160,7 +161,7 @@ this.isSubmitting = false; this.discard(false); const msg = 'Your comment could not be submitted! Please check your network connection and try again.'; - Flash(msg, 'alert', $(this.$el)); + Flash(msg, 'alert', this.$el); this.note = noteData.data.note.note; // Restore textarea content. this.removePlaceholderNotes(); }); diff --git a/app/assets/javascripts/notes/components/issue_discussion.vue b/app/assets/javascripts/notes/components/issue_discussion.vue index b131ef4b182..baf43190d9e 100644 --- a/app/assets/javascripts/notes/components/issue_discussion.vue +++ b/app/assets/javascripts/notes/components/issue_discussion.vue @@ -1,6 +1,6 @@ <script> - /* global Flash */ import { mapActions, mapGetters } from 'vuex'; + import Flash from '../../flash'; import { SYSTEM_NOTE } from '../constants'; import issueNote from './issue_note.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; @@ -133,7 +133,7 @@ this.isReplying = true; this.$nextTick(() => { const msg = 'Your comment could not be submitted! Please check your network connection and try again.'; - Flash(msg, 'alert', $(this.$el)); + Flash(msg, 'alert', this.$el); this.$refs.noteForm.note = noteText; callback(err); }); diff --git a/app/assets/javascripts/notes/components/issue_note.vue b/app/assets/javascripts/notes/components/issue_note.vue index 1f43b8a16ad..0ddbd672bed 100644 --- a/app/assets/javascripts/notes/components/issue_note.vue +++ b/app/assets/javascripts/notes/components/issue_note.vue @@ -1,7 +1,6 @@ <script> - /* global Flash */ - import { mapGetters, mapActions } from 'vuex'; + import Flash from '../../flash'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import issueNoteHeader from './issue_note_header.vue'; import issueNoteActions from './issue_note_actions.vue'; @@ -101,7 +100,7 @@ this.isEditing = true; this.$nextTick(() => { const msg = 'Something went wrong while editing your comment. Please try again.'; - Flash(msg, 'alert', $(this.$el)); + Flash(msg, 'alert', this.$el); this.recoverNoteContent(noteText); callback(); }); diff --git a/app/assets/javascripts/notes/components/issue_note_awards_list.vue b/app/assets/javascripts/notes/components/issue_note_awards_list.vue index d42e61e3899..c3a340139e7 100644 --- a/app/assets/javascripts/notes/components/issue_note_awards_list.vue +++ b/app/assets/javascripts/notes/components/issue_note_awards_list.vue @@ -1,10 +1,9 @@ <script> - /* global Flash */ - import { mapActions, mapGetters } from 'vuex'; import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg'; import emojiSmile from 'icons/_emoji_smile.svg'; import emojiSmiley from 'icons/_emoji_smiley.svg'; + import Flash from '../../flash'; import { glEmojiTag } from '../../emoji'; import tooltip from '../../vue_shared/directives/tooltip'; diff --git a/app/assets/javascripts/notes/components/issue_notes_app.vue b/app/assets/javascripts/notes/components/issue_notes_app.vue index b6fc5e5036f..aecd1f957e5 100644 --- a/app/assets/javascripts/notes/components/issue_notes_app.vue +++ b/app/assets/javascripts/notes/components/issue_notes_app.vue @@ -1,6 +1,6 @@ <script> - /* global Flash */ import { mapGetters, mapActions } from 'vuex'; + import Flash from '../../flash'; import store from '../stores/'; import * as constants from '../constants'; import issueNote from './issue_note.vue'; diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 1a791039909..6f04aecc9b7 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -1,5 +1,5 @@ -/* global Flash */ import Visibility from 'visibilityjs'; +import Flash from '../../flash'; import Poll from '../../lib/utils/poll'; import * as types from './mutation_types'; import * as utils from './utils'; @@ -99,7 +99,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => { eTagPoll.makeRequest(); $('.js-gfm-input').trigger('clear-commands-cache.atwho'); - Flash('Commands applied', 'notice', $(noteData.flashContainer)); + Flash('Commands applied', 'notice', noteData.flashContainer); } if (commandsChanges) { @@ -114,8 +114,8 @@ export const saveNote = ({ commit, dispatch }, noteData) => { .catch(() => { Flash( 'Something went wrong while adding your award. Please try again.', - null, - $(noteData.flashContainer), + 'alert', + noteData.flashContainer, ); }); } @@ -126,7 +126,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => { } if (errors && errors.commands_only) { - Flash(errors.commands_only, 'notice', $(noteData.flashContainer)); + Flash(errors.commands_only, 'notice', noteData.flashContainer); } commit(types.REMOVE_PLACEHOLDER_NOTES); diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js index 838356133cd..f90ac2d9f71 100644 --- a/app/assets/javascripts/notifications_dropdown.js +++ b/app/assets/javascripts/notifications_dropdown.js @@ -1,5 +1,5 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, consistent-return, prefer-arrow-callback, no-else-return, max-len */ -/* global Flash */ +import Flash from './flash'; (function() { this.NotificationsDropdown = (function() { diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue index c4c63a52358..f3c0aca17ba 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue @@ -1,6 +1,4 @@ <script> - /* global Flash */ - import '~/flash'; import playIconSvg from 'icons/_icon_play.svg'; import eventHub from '../event_hub'; import loadingIcon from '../../vue_shared/components/loading_icon.vue'; diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue index a4a27247406..1a7a5c2a415 100644 --- a/app/assets/javascripts/pipelines/components/stage.vue +++ b/app/assets/javascripts/pipelines/components/stage.vue @@ -13,7 +13,7 @@ * 4. Commit widget */ -/* global Flash */ +import Flash from '../../flash'; import { borderlessStatusIconEntityMap } from '../../vue_shared/ci_status_icons'; import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import tooltip from '../../vue_shared/directives/tooltip'; diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js index e97f5632dc8..50bdf80c3e3 100644 --- a/app/assets/javascripts/pipelines/mixins/pipelines.js +++ b/app/assets/javascripts/pipelines/mixins/pipelines.js @@ -1,6 +1,5 @@ -/* global Flash */ -import '~/flash'; import Visibility from 'visibilityjs'; +import Flash from '../../flash'; import Poll from '../../lib/utils/poll'; import emptyState from '../components/empty_state.vue'; import errorState from '../components/error_state.vue'; diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js index bfc416da50b..206023d4ddb 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js +++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js @@ -1,6 +1,5 @@ -/* global Flash */ - import Vue from 'vue'; +import Flash from '../flash'; import PipelinesMediator from './pipeline_details_mediatior'; import pipelineGraph from './components/graph/graph_component.vue'; import pipelineHeader from './components/header_component.vue'; diff --git a/app/assets/javascripts/pipelines/pipeline_details_mediatior.js b/app/assets/javascripts/pipelines/pipeline_details_mediatior.js index 385e7430a7d..823ccd849f4 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_mediatior.js +++ b/app/assets/javascripts/pipelines/pipeline_details_mediatior.js @@ -1,6 +1,5 @@ -/* global Flash */ - import Visibility from 'visibilityjs'; +import Flash from '../flash'; import Poll from '../lib/utils/poll'; import PipelineStore from './stores/pipeline_store'; import PipelineService from './services/pipeline_service'; diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js index 3deb242bc1f..0dc02f012e4 100644 --- a/app/assets/javascripts/profile/profile.js +++ b/app/assets/javascripts/profile/profile.js @@ -1,5 +1,5 @@ /* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len */ -/* global Flash */ +import Flash from '../flash'; import { getPagePath } from '../lib/utils/common_utils'; ((global) => { diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit.js b/app/assets/javascripts/protected_branches/protected_branch_edit.js index 3b920942a3f..632625da8e7 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_edit.js +++ b/app/assets/javascripts/protected_branches/protected_branch_edit.js @@ -1,6 +1,5 @@ /* eslint-disable no-new */ -/* global Flash */ - +import Flash from '../flash'; import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown'; export default class ProtectedBranchEdit { @@ -57,7 +56,7 @@ export default class ProtectedBranchEdit { }, }, error() { - new Flash('Failed to update branch!', null, $('.js-protected-branches-list')); + new Flash('Failed to update branch!', 'alert', document.querySelector('.js-protected-branches-list')); }, }).always(() => { this.$allowedToMergeDropdown.enable(); diff --git a/app/assets/javascripts/protected_tags/protected_tag_edit.js b/app/assets/javascripts/protected_tags/protected_tag_edit.js index 09a387c0f9e..dad0ad25b65 100644 --- a/app/assets/javascripts/protected_tags/protected_tag_edit.js +++ b/app/assets/javascripts/protected_tags/protected_tag_edit.js @@ -1,6 +1,5 @@ /* eslint-disable no-new */ -/* global Flash */ - +import Flash from '../flash'; import ProtectedTagAccessDropdown from './protected_tag_access_dropdown'; export default class ProtectedTagEdit { @@ -43,7 +42,7 @@ export default class ProtectedTagEdit { }, }, error() { - new Flash('Failed to update tag!', null, $('.js-protected-tags-list')); + new Flash('Failed to update tag!', 'alert', document.querySelector('.js-protected-tags-list')); }, }).always(() => { this.$allowedToCreateDropdownButton.enable(); diff --git a/app/assets/javascripts/repo/components/repo_commit_section.vue b/app/assets/javascripts/repo/components/repo_commit_section.vue index 119e38c583d..6d8cc964eb2 100644 --- a/app/assets/javascripts/repo/components/repo_commit_section.vue +++ b/app/assets/javascripts/repo/components/repo_commit_section.vue @@ -1,5 +1,5 @@ <script> -/* global Flash */ +import Flash from '../../flash'; import Store from '../stores/repo_store'; import RepoMixin from '../mixins/repo_mixin'; import Service from '../services/repo_service'; diff --git a/app/assets/javascripts/repo/helpers/repo_helper.js b/app/assets/javascripts/repo/helpers/repo_helper.js index 7483f8bc305..46204598e1d 100644 --- a/app/assets/javascripts/repo/helpers/repo_helper.js +++ b/app/assets/javascripts/repo/helpers/repo_helper.js @@ -1,7 +1,6 @@ -/* global Flash */ import Service from '../services/repo_service'; import Store from '../stores/repo_store'; -import '../../flash'; +import Flash from '../../flash'; const RepoHelper = { monacoInstance: null, diff --git a/app/assets/javascripts/repo/services/repo_service.js b/app/assets/javascripts/repo/services/repo_service.js index af83497fa39..830685f7e6e 100644 --- a/app/assets/javascripts/repo/services/repo_service.js +++ b/app/assets/javascripts/repo/services/repo_service.js @@ -1,4 +1,3 @@ -/* global Flash */ import axios from 'axios'; import Store from '../stores/repo_store'; import Api from '../../api'; diff --git a/app/assets/javascripts/repo/stores/repo_store.js b/app/assets/javascripts/repo/stores/repo_store.js index 93b39cff27e..c633f538c1b 100644 --- a/app/assets/javascripts/repo/stores/repo_store.js +++ b/app/assets/javascripts/repo/stores/repo_store.js @@ -1,4 +1,3 @@ -/* global Flash */ import Helper from '../helpers/repo_helper'; import Service from '../services/repo_service'; diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js index 05caf177aec..07fee53d814 100644 --- a/app/assets/javascripts/search.js +++ b/app/assets/javascripts/search.js @@ -1,5 +1,5 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, object-shorthand, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-else-return, max-len */ -/* global Flash */ +import Flash from './flash'; import Api from './api'; (function() { diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js index f83c3b037ed..74c17bc14a2 100644 --- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js +++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js @@ -1,5 +1,4 @@ -/* global Flash */ - +import Flash from '../../../flash'; import AssigneeTitle from './assignee_title'; import Assignees from './assignees'; diff --git a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue index f2b1099a678..22a9a34dda3 100644 --- a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue +++ b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue @@ -1,5 +1,5 @@ <script> -/* global Flash */ +import Flash from '../../../flash'; import editForm from './edit_form.vue'; export default { diff --git a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js index 3c9de02407e..977dd83a7ea 100644 --- a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js +++ b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js @@ -1,5 +1,3 @@ -/* global Flash */ - function isValidProjectId(id) { return id > 0; } @@ -38,7 +36,7 @@ class SidebarMoveIssue { data: (searchTerm, callback) => { this.mediator.fetchAutocompleteProjects(searchTerm) .then(callback) - .catch(() => new Flash('An error occurred while fetching projects autocomplete.')); + .catch(() => new window.Flash('An error occurred while fetching projects autocomplete.')); }, renderRow: project => ` <li> @@ -73,7 +71,7 @@ class SidebarMoveIssue { this.mediator.moveIssue() .catch(() => { - Flash('An error occurred while moving the issue.'); + window.Flash('An error occurred while moving the issue.'); this.$confirmButton .enable() .removeClass('is-loading'); diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js index 2fe6e5b31f0..ede3a0de144 100644 --- a/app/assets/javascripts/sidebar/sidebar_mediator.js +++ b/app/assets/javascripts/sidebar/sidebar_mediator.js @@ -1,5 +1,4 @@ -/* global Flash */ - +import Flash from '../flash'; import Service from './services/sidebar_service'; import Store from './stores/sidebar_store'; diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js index 3a06b477d7c..77db075d1ef 100644 --- a/app/assets/javascripts/star.js +++ b/app/assets/javascripts/star.js @@ -1,6 +1,5 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, max-len */ -/* global Flash */ - +import Flash from './flash'; import { __, s__ } from './locale'; export default class Star { diff --git a/app/assets/javascripts/task_list.js b/app/assets/javascripts/task_list.js index c39f569da5e..dcbec40c79e 100644 --- a/app/assets/javascripts/task_list.js +++ b/app/assets/javascripts/task_list.js @@ -1,6 +1,5 @@ -/* global Flash */ - import 'deckar01-task_list'; +import Flash from './flash'; export default class TaskList { constructor(options = {}) { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js index e98d147733c..e86a0f7e749 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js @@ -1,6 +1,5 @@ -/* global Flash */ - import '~/lib/utils/datetime_utility'; +import Flash from '../../flash'; import MemoryUsage from './mr_widget_memory_usage'; import StatusIcon from './mr_widget_status_icon'; import MRWidgetService from '../services/mr_widget_service'; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js index bdfd4d9667c..05c4a28be88 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js @@ -1,4 +1,4 @@ -/* global Flash */ +import Flash from '../../../flash'; import statusIcon from '../mr_widget_status_icon'; import MRWidgetAuthor from '../../components/mr_widget_author'; import eventHub from '../../event_hub'; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js index 74fc52796a0..2dfd87ed904 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js @@ -1,5 +1,4 @@ -/* global Flash */ - +import Flash from '../../../flash'; import mrWidgetAuthorTime from '../../components/mr_widget_author_time'; import tooltip from '../../../vue_shared/directives/tooltip'; import loadingIcon from '../../../vue_shared/components/loading_icon.vue'; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js index 61734163b6e..b8a96b23012 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js @@ -1,7 +1,7 @@ -/* global Flash */ import successSvg from 'icons/_icon_status_success.svg'; import warningSvg from 'icons/_icon_status_warning.svg'; import simplePoll from '~/lib/utils/simple_poll'; +import Flash from '../../../flash'; import statusIcon from '../mr_widget_status_icon'; import eventHub from '../../event_hub'; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js index 54be1fbe675..4f83350e07c 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js @@ -1,4 +1,3 @@ -/* global Flash */ import statusIcon from '../mr_widget_status_icon'; import tooltip from '../../../vue_shared/directives/tooltip'; import eventHub from '../../event_hub'; @@ -27,12 +26,12 @@ export default { .then(res => res.json()) .then((res) => { eventHub.$emit('UpdateWidgetData', res); - new Flash('The merge request can now be merged.', 'notice'); // eslint-disable-line + new window.Flash('The merge request can now be merged.', 'notice'); // eslint-disable-line $('.merge-request .detail-page-description .title').text(this.mr.title); }) .catch(() => { this.isMakingRequest = false; - new Flash('Something went wrong. Please try again.'); // eslint-disable-line + new window.Flash('Something went wrong. Please try again.'); // eslint-disable-line }); }, }, diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js index 044b664484b..4f497b204a3 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js @@ -1,5 +1,4 @@ -/* global Flash */ - +import Flash from '../flash'; import { WidgetHeader, WidgetMergeHelp, diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index 759d30c9c7c..af4187fab46 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -1,5 +1,5 @@ <script> - /* global Flash */ + import Flash from '../../../flash'; import markdownHeader from './header.vue'; import markdownToolbar from './toolbar.vue'; diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 82bceddf1f0..676c1d1988b 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -7,7 +7,12 @@ module GroupsHelper can?(current_user, :change_share_with_group_lock, group) end - def group_icon(group) + def group_icon(group, options = {}) + img_path = group_icon_url(group, options) + image_tag img_path, options + end + + def group_icon_url(group, options = {}) if group.is_a?(String) group = Group.find_by_full_path(group) end @@ -89,7 +94,7 @@ module GroupsHelper link_to(group_path(group), class: "group-path #{'breadcrumb-item-text' unless for_dropdown} js-breadcrumb-item-text #{'hidable' if hidable}") do output = if (group.try(:avatar_url) || show_avatar) && !Rails.env.test? - image_tag(group_icon(group), class: "avatar-tile", width: 15, height: 15) + group_icon(group, class: "avatar-tile", width: 15, height: 15) else "" end diff --git a/app/helpers/lazy_image_tag_helper.rb b/app/helpers/lazy_image_tag_helper.rb index 2c5619ac41b..603b9438e35 100644 --- a/app/helpers/lazy_image_tag_helper.rb +++ b/app/helpers/lazy_image_tag_helper.rb @@ -10,6 +10,7 @@ module LazyImageTagHelper unless options.delete(:lazy) == false options[:data] ||= {} options[:data][:src] = path_to_image(source) + options[:class] ||= "" options[:class] << " lazy" diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb index 8fbfed11bdf..2ec70203710 100644 --- a/app/models/concerns/avatarable.rb +++ b/app/models/concerns/avatarable.rb @@ -11,7 +11,7 @@ module Avatarable # If asset_host is set then it is expected that assets are handled by a standalone host. # That means we do not want to get GitLab's relative_url_root option anymore. - host = asset_host.present? ? asset_host : gitlab_host + host = (asset_host.present? && (!respond_to?(:public?) || public?)) ? asset_host : gitlab_host [host, avatar.url].join end diff --git a/app/serializers/group_entity.rb b/app/serializers/group_entity.rb index 7c872a3e986..6d8466da902 100644 --- a/app/serializers/group_entity.rb +++ b/app/serializers/group_entity.rb @@ -45,6 +45,6 @@ class GroupEntity < Grape::Entity end expose :avatar_url do |group| - group_icon(group) + group_icon_url(group) end end diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 7b32e215c7f..a52dce6cb4b 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -593,7 +593,7 @@ module SystemNoteService def discussion_lock(issuable, author) action = issuable.discussion_locked? ? 'locked' : 'unlocked' - body = "#{action} this issue" + body = "#{action} this #{issuable.class.to_s.titleize.downcase}" create_note(NoteSummary.new(issuable, issuable.project, author, body, action: action)) end diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml index e3a77dfdf10..47cc2d4d27e 100644 --- a/app/views/admin/groups/_group.html.haml +++ b/app/views/admin/groups/_group.html.haml @@ -20,7 +20,7 @@ = visibility_level_icon(group.visibility_level, fw: false) .avatar-container.s40 - = image_tag group_icon(group), class: "avatar s40 hidden-xs" + = group_icon(group, class: "avatar s40 hidden-xs") .title = link_to [:admin, group], class: 'group-name' do = group.full_name diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 3e02f7b1e16..2545cecc721 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -16,7 +16,7 @@ %ul.well-list %li .avatar-container.s60 - = image_tag group_icon(@group), class: "avatar s60" + = group_icon(@group, class: "avatar s60") %li %span.light Name: %strong= @group.name diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml index 181c7bee702..a0760c2073b 100644 --- a/app/views/groups/_home_panel.html.haml +++ b/app/views/groups/_home_panel.html.haml @@ -1,7 +1,7 @@ .group-home-panel.text-center %div{ class: container_class } .avatar-container.s70.group-avatar - = image_tag group_icon(@group), class: "avatar s70 avatar-tile" + = group_icon(@group, class: "avatar s70 avatar-tile") %h1.group-title = @group.name %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) } diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 15606dd30fd..16038ef2f79 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -10,7 +10,7 @@ .form-group .col-sm-offset-2.col-sm-10 .avatar-container.s160 - = image_tag group_icon(@group), alt: '', class: 'avatar group-avatar s160' + = group_icon(@group, alt: '', class: 'avatar group-avatar s160') %p.light - if @group.avatar? You can change your group avatar here diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index 8cba495f7e4..0bf318b0b66 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -6,7 +6,7 @@ .context-header = link_to group_path(@group), title: @group.name do .avatar-container.s40.group-avatar - = image_tag group_icon(@group), class: "avatar s40 avatar-tile" + = group_icon(@group, class: "avatar s40 avatar-tile") .sidebar-context-title = @group.name %ul.sidebar-top-level-items diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 8ae4fd94146..893e536e289 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -97,7 +97,7 @@ %button.btn.js-settings-toggle = expanded ? 'Collapse' : 'Expand' %p - Perform advanced options such as housekeeping, exporting, archiving, renaming, transferring, or removing your project. + Perform advanced options such as housekeeping, archiving, renaming, transferring, or removing your project. .settings-content.no-animate{ class: ('expanded' if expanded) } .sub-section %h4 Housekeeping diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml index 43e23bb2200..d5c6d329ce4 100644 --- a/app/views/projects/jobs/_sidebar.html.haml +++ b/app/views/projects/jobs/_sidebar.html.haml @@ -48,7 +48,7 @@ - if @build.trigger_variables.any? %p - %button.btn.group.btn-group-justified.reveal-variables Reveal Variables + %button.btn.group.btn-group-justified.js-reveal-variables Reveal Variables %dl.js-build-variables.trigger-build-variables.hide - @build.trigger_variables.each do |trigger_variable| diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml index b361ec86ced..63f62eb476e 100644 --- a/app/views/shared/groups/_group.html.haml +++ b/app/views/shared/groups/_group.html.haml @@ -28,7 +28,7 @@ .avatar-container.s40 = link_to group do - = image_tag group_icon(group), class: "avatar s40 hidden-xs" + = group_icon(group, class: "avatar s40 hidden-xs") .title = link_to group_name, group, class: 'group-name' diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index bcdad3c153a..5868c52566d 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -4,7 +4,7 @@ - dom_id = "group_member_#{group_link.id}" %li.member.group_member{ id: dom_id } %span.list-item-name - = image_tag group_icon(group), class: "avatar s40", alt: '' + = group_icon(group, class: "avatar s40", alt: '') %strong = link_to group.full_name, group_path(group) .cgray diff --git a/app/views/users/_groups.html.haml b/app/views/users/_groups.html.haml index eff6c80d144..55799e10a46 100644 --- a/app/views/users/_groups.html.haml +++ b/app/views/users/_groups.html.haml @@ -2,4 +2,4 @@ - groups.each do |group| = link_to group, class: 'profile-groups-avatars inline', title: group.name do .avatar-container.s40 - = image_tag group_icon(group), class: 'avatar group-avatar s40' + = group_icon(group, class: 'avatar group-avatar s40') diff --git a/app/workers/concerns/project_start_import.rb b/app/workers/concerns/project_start_import.rb new file mode 100644 index 00000000000..0704ebbb0fd --- /dev/null +++ b/app/workers/concerns/project_start_import.rb @@ -0,0 +1,9 @@ +module ProjectStartImport + def start(project) + if project.import_started? && project.import_jid == self.jid + return true + end + + project.import_start + end +end diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb index cde5b45ad41..264706e3e23 100644 --- a/app/workers/repository_fork_worker.rb +++ b/app/workers/repository_fork_worker.rb @@ -4,6 +4,7 @@ class RepositoryForkWorker include Sidekiq::Worker include Gitlab::ShellAdapter include DedicatedSidekiqQueue + include ProjectStartImport sidekiq_options status_expiration: StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION @@ -37,7 +38,7 @@ class RepositoryForkWorker private def start_fork(project) - return true if project.import_start + return true if start(project) Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while forking.") false diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index 00a021abbdc..d7c0043d3b6 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -4,6 +4,7 @@ class RepositoryImportWorker include Sidekiq::Worker include DedicatedSidekiqQueue include ExceptionBacktrace + include ProjectStartImport sidekiq_options status_expiration: StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION @@ -34,7 +35,7 @@ class RepositoryImportWorker private def start_import(project) - return true if project.import_start + return true if start(project) Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while importing.") false diff --git a/changelogs/unreleased/34841-todos.yml b/changelogs/unreleased/34841-todos.yml new file mode 100644 index 00000000000..37180eefbfc --- /dev/null +++ b/changelogs/unreleased/34841-todos.yml @@ -0,0 +1,5 @@ +--- +title: Fix bad type checking to prevent 0 count badge to be shown +merge_request: +author: +type: fixed diff --git a/db/migrate/20160518200441_add_artifacts_expire_date_to_ci_builds.rb b/db/migrate/20160518200441_add_artifacts_expire_date_to_ci_builds.rb index 915167b038d..8e9ab3f8acc 100644 --- a/db/migrate/20160518200441_add_artifacts_expire_date_to_ci_builds.rb +++ b/db/migrate/20160518200441_add_artifacts_expire_date_to_ci_builds.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Datetime class AddArtifactsExpireDateToCiBuilds < ActiveRecord::Migration def change add_column :ci_builds, :artifacts_expire_at, :timestamp diff --git a/db/migrate/20160716115711_add_queued_at_to_ci_builds.rb b/db/migrate/20160716115711_add_queued_at_to_ci_builds.rb index 756910a1fa0..fd7a48d881e 100644 --- a/db/migrate/20160716115711_add_queued_at_to_ci_builds.rb +++ b/db/migrate/20160716115711_add_queued_at_to_ci_builds.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Datetime class AddQueuedAtToCiBuilds < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/migrate/20170503021915_add_last_edited_at_and_last_edited_by_id_to_issues.rb b/db/migrate/20170503021915_add_last_edited_at_and_last_edited_by_id_to_issues.rb index 6ac10723c82..a5d1eca82bb 100644 --- a/db/migrate/20170503021915_add_last_edited_at_and_last_edited_by_id_to_issues.rb +++ b/db/migrate/20170503021915_add_last_edited_at_and_last_edited_by_id_to_issues.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Datetime # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. diff --git a/db/migrate/20170503022548_add_last_edited_at_and_last_edited_by_id_to_merge_requests.rb b/db/migrate/20170503022548_add_last_edited_at_and_last_edited_by_id_to_merge_requests.rb index 7a1acdcbf69..47ba6bde856 100644 --- a/db/migrate/20170503022548_add_last_edited_at_and_last_edited_by_id_to_merge_requests.rb +++ b/db/migrate/20170503022548_add_last_edited_at_and_last_edited_by_id_to_merge_requests.rb @@ -1,3 +1,4 @@ +# rubocop:disable Migration/Datetime # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md index 77ae6d2a0ea..10f4c5a0902 100644 --- a/doc/development/fe_guide/style_guide_js.md +++ b/doc/development/fe_guide/style_guide_js.md @@ -88,16 +88,31 @@ followed by any global declarations, then a blank newline prior to any imports o 1. Use ES module syntax to import modules ```javascript // bad - require('foo'); + const SomeClass = require('some_class'); // good - import Foo from 'foo'; + import SomeClass from 'some_class'; // bad - module.exports = Foo; + module.exports = SomeClass; // good - export default Foo; + export default SomeClass; + ``` + + Import statements are following usual naming guidelines, for example object literals use camel case: + + ```javascript + // some_object file + export default { + key: 'value', + }; + + // bad + import ObjectLiteral from 'some_object'; + + // good + import objectLiteral from 'some_object'; ``` 1. Relative paths: when importing a module in the same directory, a child @@ -285,6 +300,13 @@ A forEach will cause side effects, it will be mutating the array being iterated. 1. **Extensions**: Use `.vue` extension for Vue components. 1. **Reference Naming**: Use camelCase for their instances: ```javascript + // bad + import CardBoard from 'cardBoard' + + components: { + CardBoard: + }; + // good import cardBoard from 'cardBoard' diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index f200c694562..58d5b0da1c4 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -103,11 +103,16 @@ module Gitlab end def send_git_diff(repository, diff_refs) - params = { - 'RepoPath' => repository.path_to_repo, - 'ShaFrom' => diff_refs.base_sha, - 'ShaTo' => diff_refs.head_sha - } + params = if Gitlab::GitalyClient.feature_enabled?(:workhorse_send_git_diff) + { + 'GitalyServer' => gitaly_server_hash(repository), + 'RawDiffRequest' => Gitaly::RawDiffRequest.new( + gitaly_diff_or_patch_hash(repository, diff_refs) + ).to_json + } + else + workhorse_diff_or_patch_hash(repository, diff_refs) + end [ SEND_DATA_HEADER, @@ -116,11 +121,16 @@ module Gitlab end def send_git_patch(repository, diff_refs) - params = { - 'RepoPath' => repository.path_to_repo, - 'ShaFrom' => diff_refs.base_sha, - 'ShaTo' => diff_refs.head_sha - } + params = if Gitlab::GitalyClient.feature_enabled?(:workhorse_send_git_patch) + { + 'GitalyServer' => gitaly_server_hash(repository), + 'RawPatchRequest' => Gitaly::RawPatchRequest.new( + gitaly_diff_or_patch_hash(repository, diff_refs) + ).to_json + } + else + workhorse_diff_or_patch_hash(repository, diff_refs) + end [ SEND_DATA_HEADER, @@ -216,6 +226,22 @@ module Gitlab token: Gitlab::GitalyClient.token(repository.project.repository_storage) } end + + def workhorse_diff_or_patch_hash(repository, diff_refs) + { + 'RepoPath' => repository.path_to_repo, + 'ShaFrom' => diff_refs.base_sha, + 'ShaTo' => diff_refs.head_sha + } + end + + def gitaly_diff_or_patch_hash(repository, diff_refs) + { + repository: repository.gitaly_repository, + left_commit_id: diff_refs.base_sha, + right_commit_id: diff_refs.head_sha + } + end end end end diff --git a/rubocop/cop/migration/datetime.rb b/rubocop/cop/migration/datetime.rb index 651935dd53e..9cba3c35b26 100644 --- a/rubocop/cop/migration/datetime.rb +++ b/rubocop/cop/migration/datetime.rb @@ -7,14 +7,18 @@ module RuboCop class Datetime < RuboCop::Cop::Cop include MigrationHelpers - MSG = 'Do not use the `datetime` data type, use `datetime_with_timezone` instead'.freeze + MSG = 'Do not use the `%s` data type, use `datetime_with_timezone` instead'.freeze # Check methods in table creation. def on_def(node) return unless in_migration?(node) node.each_descendant(:send) do |send_node| - add_offense(send_node, :selector) if method_name(send_node) == :datetime + method_name = node.children[1] + + if method_name == :datetime || method_name == :timestamp + add_offense(send_node, :selector, format(MSG, method_name)) + end end end @@ -23,12 +27,14 @@ module RuboCop return unless in_migration?(node) node.each_descendant do |descendant| - add_offense(node, :expression) if descendant.type == :sym && descendant.children.last == :datetime - end - end + next unless descendant.type == :sym - def method_name(node) - node.children[1] + last_argument = descendant.children.last + + if last_argument == :datetime || last_argument == :timestamp + add_offense(node, :expression, format(MSG, last_argument)) + end + end end end end diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 71702db860c..576870ea0f3 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -300,13 +300,13 @@ feature 'Jobs' do shared_examples 'expected variables behavior' do it 'shows variable key and value after click', :js do - expect(page).to have_css('.reveal-variables') + expect(page).to have_css('.js-reveal-variables') expect(page).not_to have_css('.js-build-variable') expect(page).not_to have_css('.js-build-value') click_button 'Reveal Variables' - expect(page).not_to have_css('.reveal-variables') + expect(page).not_to have_css('.js-reveal-variables') expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1') expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1') end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 10bc5f2ecd2..87ae1fa5660 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -57,15 +57,17 @@ describe ApplicationHelper do end describe 'project_icon' do + let(:asset_host) { 'http://assets' } + it 'returns an url for the avatar' do - project = create(:project, avatar: File.open(uploaded_image_temp_path)) + project = create(:project, :public, avatar: File.open(uploaded_image_temp_path)) avatar_url = "/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif" expect(helper.project_icon(project.full_path).to_s) .to eq "<img data-src=\"#{avatar_url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />" - allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host) - avatar_url = "#{gitlab_host}/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif" + allow(ActionController::Base).to receive(:asset_host).and_return(asset_host) + avatar_url = "#{asset_host}/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif" expect(helper.project_icon(project.full_path).to_s) .to eq "<img data-src=\"#{avatar_url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />" diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 76e5964ccf7..97f0ed4904e 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe GroupsHelper do include ApplicationHelper + let(:asset_host) { 'http://assets' } + describe 'group_icon' do avatar_file_path = File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') @@ -10,14 +12,53 @@ describe GroupsHelper do group = create(:group) group.avatar = fixture_file_upload(avatar_file_path) group.save! - expect(group_icon(group.path).to_s) + + avatar_url = "/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif" + + expect(helper.group_icon(group).to_s) + .to eq "<img data-src=\"#{avatar_url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />" + + allow(ActionController::Base).to receive(:asset_host).and_return(asset_host) + avatar_url = "#{asset_host}/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif" + + expect(helper.group_icon(group).to_s) + .to eq "<img data-src=\"#{avatar_url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />" + end + end + + describe 'group_icon_url' do + avatar_file_path = File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') + + it 'returns an url for the avatar' do + group = create(:group) + group.avatar = fixture_file_upload(avatar_file_path) + group.save! + expect(group_icon_url(group.path).to_s) + .to match("/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif") + end + + it 'returns an CDN url for the avatar' do + allow(ActionController::Base).to receive(:asset_host).and_return(asset_host) + group = create(:group) + group.avatar = fixture_file_upload(avatar_file_path) + group.save! + expect(group_icon_url(group.path).to_s) + .to match("#{asset_host}/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif") + end + + it 'returns an based url for the avatar if private' do + allow(ActionController::Base).to receive(:asset_host).and_return(asset_host) + group = create(:group, :private) + group.avatar = fixture_file_upload(avatar_file_path) + group.save! + expect(group_icon_url(group.path).to_s) .to match("/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif") end it 'gives default avatar_icon when no avatar is present' do group = create(:group) group.save! - expect(group_icon(group.path)).to match_asset_path('group_avatar.png') + expect(group_icon_url(group.path)).to match_asset_path('group_avatar.png') end end diff --git a/spec/javascripts/flash_spec.js b/spec/javascripts/flash_spec.js new file mode 100644 index 00000000000..060ffaa339b --- /dev/null +++ b/spec/javascripts/flash_spec.js @@ -0,0 +1,269 @@ +import flash, { + createFlashEl, + createAction, + hideFlash, +} from '~/flash'; + +describe('Flash', () => { + describe('createFlashEl', () => { + let el; + + beforeEach(() => { + el = document.createElement('div'); + }); + + afterEach(() => { + el.innerHTML = ''; + }); + + it('creates flash element with type', () => { + el.innerHTML = createFlashEl('testing', 'alert'); + + expect( + el.querySelector('.flash-alert'), + ).not.toBeNull(); + }); + + it('escapes text', () => { + el.innerHTML = createFlashEl('<script>alert("a");</script>', 'alert'); + + expect( + el.querySelector('.flash-text').textContent.trim(), + ).toBe('<script>alert("a");</script>'); + }); + + it('adds container classes when inside content wrapper', () => { + el.innerHTML = createFlashEl('testing', 'alert', true); + + expect( + el.querySelector('.flash-text').classList.contains('container-fluid'), + ).toBeTruthy(); + expect( + el.querySelector('.flash-text').classList.contains('container-limited'), + ).toBeTruthy(); + }); + }); + + describe('hideFlash', () => { + let el; + + beforeEach(() => { + el = document.createElement('div'); + el.className = 'js-testing'; + }); + + it('sets transition style', () => { + hideFlash(el); + + expect( + el.style.transition, + ).toBe('opacity 0.3s'); + }); + + it('sets opacity style', () => { + hideFlash(el); + + expect( + el.style.opacity, + ).toBe('0'); + }); + + it('does not set styles when fadeTransition is false', () => { + hideFlash(el, false); + + expect( + el.style.opacity, + ).toBe(''); + expect( + el.style.transition, + ).toBe(''); + }); + + it('removes element after transitionend', () => { + document.body.appendChild(el); + + hideFlash(el); + el.dispatchEvent(new Event('transitionend')); + + expect( + document.querySelector('.js-testing'), + ).toBeNull(); + }); + + it('calls event listener callback once', () => { + spyOn(el, 'remove').and.callThrough(); + document.body.appendChild(el); + + hideFlash(el); + + el.dispatchEvent(new Event('transitionend')); + el.dispatchEvent(new Event('transitionend')); + + expect( + el.remove.calls.count(), + ).toBe(1); + }); + }); + + describe('createAction', () => { + let el; + + beforeEach(() => { + el = document.createElement('div'); + }); + + it('creates link with href', () => { + el.innerHTML = createAction({ + href: 'testing', + title: 'test', + }); + + expect( + el.querySelector('.flash-action').href, + ).toContain('testing'); + }); + + it('uses hash as href when no href is present', () => { + el.innerHTML = createAction({ + title: 'test', + }); + + expect( + el.querySelector('.flash-action').href, + ).toContain('#'); + }); + + it('adds role when no href is present', () => { + el.innerHTML = createAction({ + title: 'test', + }); + + expect( + el.querySelector('.flash-action').getAttribute('role'), + ).toBe('button'); + }); + + it('escapes the title text', () => { + el.innerHTML = createAction({ + title: '<script>alert("a")</script>', + }); + + expect( + el.querySelector('.flash-action').textContent.trim(), + ).toBe('<script>alert("a")</script>'); + }); + }); + + describe('createFlash', () => { + describe('no flash-container', () => { + it('does not add to the DOM', () => { + const flashEl = flash('testing'); + + expect( + flashEl, + ).toBeNull(); + expect( + document.querySelector('.flash-alert'), + ).toBeNull(); + }); + }); + + describe('with flash-container', () => { + beforeEach(() => { + document.body.innerHTML += ` + <div class="content-wrapper js-content-wrapper"> + <div class="flash-container"></div> + </div> + `; + }); + + afterEach(() => { + document.querySelector('.js-content-wrapper').remove(); + }); + + it('adds flash element into container', () => { + flash('test'); + + expect( + document.querySelector('.flash-alert'), + ).not.toBeNull(); + }); + + it('adds flash into specified parent', () => { + flash( + 'test', + 'alert', + document.querySelector('.content-wrapper'), + ); + + expect( + document.querySelector('.content-wrapper .flash-alert'), + ).not.toBeNull(); + }); + + it('adds container classes when inside content-wrapper', () => { + flash('test'); + + expect( + document.querySelector('.flash-text').className, + ).toBe('flash-text container-fluid container-limited'); + }); + + it('does not add container when outside of content-wrapper', () => { + document.querySelector('.content-wrapper').className = 'js-content-wrapper'; + flash('test'); + + expect( + document.querySelector('.flash-text').className.trim(), + ).toBe('flash-text'); + }); + + it('removes element after clicking', () => { + flash('test', 'alert', document, null, false); + + document.querySelector('.flash-alert').click(); + + expect( + document.querySelector('.flash-alert'), + ).toBeNull(); + }); + + describe('with actionConfig', () => { + it('adds action link', () => { + flash( + 'test', + 'alert', + document, + { + title: 'test', + }, + ); + + expect( + document.querySelector('.flash-action'), + ).not.toBeNull(); + }); + + it('calls actionConfig clickHandler on click', () => { + const actionConfig = { + title: 'test', + clickHandler: jasmine.createSpy('actionConfig'), + }; + + flash( + 'test', + 'alert', + document, + actionConfig, + ); + + document.querySelector('.flash-action').click(); + + expect( + actionConfig.clickHandler, + ).toHaveBeenCalled(); + }); + }); + }); + }); +}); diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js index 0e01934d3a3..4751eb868a4 100644 --- a/spec/javascripts/header_spec.js +++ b/spec/javascripts/header_spec.js @@ -1,53 +1,48 @@ -/* eslint-disable space-before-function-paren, no-var */ - import '~/header'; -import '~/lib/utils/text_utility'; -(function() { - describe('Header', function() { - var todosPendingCount = '.todos-count'; - var fixtureTemplate = 'issues/open-issue.html.raw'; +describe('Header', function () { + const todosPendingCount = '.todos-count'; + const fixtureTemplate = 'issues/open-issue.html.raw'; - function isTodosCountHidden() { - return $(todosPendingCount).hasClass('hidden'); - } + function isTodosCountHidden() { + return $(todosPendingCount).hasClass('hidden'); + } - function triggerToggle(newCount) { - $(document).trigger('todo:toggle', newCount); - } + function triggerToggle(newCount) { + $(document).trigger('todo:toggle', newCount); + } - preloadFixtures(fixtureTemplate); - beforeEach(function() { - loadFixtures(fixtureTemplate); - }); + preloadFixtures(fixtureTemplate); + beforeEach(() => { + loadFixtures(fixtureTemplate); + }); - it('should update todos-count after receiving the todo:toggle event', function() { - triggerToggle(5); - expect($(todosPendingCount).text()).toEqual('5'); - }); + it('should update todos-count after receiving the todo:toggle event', () => { + triggerToggle('5'); + expect($(todosPendingCount).text()).toEqual('5'); + }); - it('should hide todos-count when it is 0', function() { - triggerToggle(0); - expect(isTodosCountHidden()).toEqual(true); + it('should hide todos-count when it is 0', () => { + triggerToggle('0'); + expect(isTodosCountHidden()).toEqual(true); + }); + + it('should show todos-count when it is more than 0', () => { + triggerToggle('10'); + expect(isTodosCountHidden()).toEqual(false); + }); + + describe('when todos-count is 1000', () => { + beforeEach(() => { + triggerToggle('1000'); }); - it('should show todos-count when it is more than 0', function() { - triggerToggle(10); + it('should show todos-count', () => { expect(isTodosCountHidden()).toEqual(false); }); - describe('when todos-count is 1000', function() { - beforeEach(function() { - triggerToggle(1000); - }); - - it('should show todos-count', function() { - expect(isTodosCountHidden()).toEqual(false); - }); - - it('should show 99+ for todos-count', function() { - expect($(todosPendingCount).text()).toEqual('99+'); - }); + it('should show 99+ for todos-count', () => { + expect($(todosPendingCount).text()).toEqual('99+'); }); }); -}).call(window); +}); diff --git a/spec/javascripts/integrations/integration_settings_form_spec.js b/spec/javascripts/integrations/integration_settings_form_spec.js index 3daeb91b1e2..9033eb9ce02 100644 --- a/spec/javascripts/integrations/integration_settings_form_spec.js +++ b/spec/javascripts/integrations/integration_settings_form_spec.js @@ -138,9 +138,9 @@ describe('IntegrationSettingsForm', () => { deferred.resolve({ error: true, message: errorMessage, service_response: 'some error' }); const $flashContainer = $('.flash-container'); - expect($flashContainer.find('.flash-text').text()).toEqual('Test failed. some error'); + expect($flashContainer.find('.flash-text').text().trim()).toEqual('Test failed. some error'); expect($flashContainer.find('.flash-action')).toBeDefined(); - expect($flashContainer.find('.flash-action').text()).toEqual('Save anyway'); + expect($flashContainer.find('.flash-action').text().trim()).toEqual('Save anyway'); }); it('should submit form if ajax request responds without any error in test', () => { @@ -168,7 +168,7 @@ describe('IntegrationSettingsForm', () => { expect($flashAction).toBeDefined(); spyOn(integrationSettingsForm.$form, 'submit'); - $flashAction.trigger('click'); + $flashAction.get(0).click(); expect(integrationSettingsForm.$form.submit).toHaveBeenCalled(); }); @@ -181,7 +181,7 @@ describe('IntegrationSettingsForm', () => { deferred.reject(); - expect($('.flash-container .flash-text').text()).toEqual(errorMessage); + expect($('.flash-container .flash-text').text().trim()).toEqual(errorMessage); }); it('should always call `toggleSubmitBtnState` with `false` once request is completed', () => { diff --git a/spec/javascripts/build_spec.js b/spec/javascripts/job_spec.js index d5b0f23e7b7..5e67911d338 100644 --- a/spec/javascripts/build_spec.js +++ b/spec/javascripts/job_spec.js @@ -1,13 +1,11 @@ -/* eslint-disable no-new */ -/* global Build */ import { bytesToKiB } from '~/lib/utils/number_utils'; import '~/lib/utils/datetime_utility'; import '~/lib/utils/url_utility'; -import '~/build'; +import Job from '~/job'; import '~/breakpoints'; -describe('Build', () => { - const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1`; +describe('Job', () => { + const JOB_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1`; preloadFixtures('builds/build-with-artifacts.html.raw'); @@ -26,14 +24,14 @@ describe('Build', () => { describe('setup', () => { beforeEach(function () { - this.build = new Build(); + this.job = new Job(); }); it('copies build options', function () { - expect(this.build.pageUrl).toBe(BUILD_URL); - expect(this.build.buildStatus).toBe('success'); - expect(this.build.buildStage).toBe('test'); - expect(this.build.state).toBe(''); + expect(this.job.pageUrl).toBe(JOB_URL); + expect(this.job.buildStatus).toBe('success'); + expect(this.job.buildStage).toBe('test'); + expect(this.job.state).toBe(''); }); it('only shows the jobs matching the current stage', () => { @@ -87,15 +85,15 @@ describe('Build', () => { complete: true, }); - this.build = new Build(); + this.job = new Job(); expect($('#build-trace .js-build-output').text()).toMatch(/Update/); - expect(this.build.state).toBe('newstate'); + expect(this.job.state).toBe('newstate'); jasmine.clock().tick(4001); expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/); - expect(this.build.state).toBe('finalstate'); + expect(this.job.state).toBe('finalstate'); }); it('replaces the entire build trace', () => { @@ -122,7 +120,7 @@ describe('Build', () => { append: false, }); - this.build = new Build(); + this.job = new Job(); expect($('#build-trace .js-build-output').text()).toMatch(/Update/); @@ -148,7 +146,7 @@ describe('Build', () => { total: 100, }); - this.build = new Build(); + this.job = new Job(); expect(document.querySelector('.js-truncated-info').classList).not.toContain('hidden'); }); @@ -167,7 +165,7 @@ describe('Build', () => { total: 100, }); - this.build = new Build(); + this.job = new Job(); expect( document.querySelector('.js-truncated-info-size').textContent.trim(), @@ -193,7 +191,7 @@ describe('Build', () => { deferred2.resolve(); - this.build = new Build(); + this.job = new Job(); expect( document.querySelector('.js-truncated-info-size').textContent.trim(), @@ -227,7 +225,7 @@ describe('Build', () => { total: 100, }); - this.build = new Build(); + this.job = new Job(); expect( document.querySelector('.js-raw-link').textContent.trim(), @@ -249,7 +247,7 @@ describe('Build', () => { total: 100, }); - this.build = new Build(); + this.job = new Job(); expect(document.querySelector('.js-truncated-info').classList).toContain('hidden'); }); @@ -270,7 +268,7 @@ describe('Build', () => { total: 100, }); - this.build = new Build(); + this.job = new Job(); }); it('should render trace controls', () => { @@ -293,11 +291,12 @@ describe('Build', () => { describe('getBuildTrace', () => { it('should request build trace with state parameter', (done) => { spyOn(jQuery, 'ajax').and.callThrough(); - new Build(); + // eslint-disable-next-line no-new + new Job(); setTimeout(() => { expect(jQuery.ajax).toHaveBeenCalledWith( - { url: `${BUILD_URL}/trace.json`, data: { state: '' } }, + { url: `${JOB_URL}/trace.json`, data: { state: '' } }, ); done(); }, 0); diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js index f1a975ba962..829b3ef5735 100644 --- a/spec/javascripts/lib/utils/text_utility_spec.js +++ b/spec/javascripts/lib/utils/text_utility_spec.js @@ -1,4 +1,4 @@ -import '~/lib/utils/text_utility'; +import { highCountTrim } from '~/lib/utils/text_utility'; describe('text_utility', () => { describe('gl.text.getTextWidth', () => { @@ -35,14 +35,14 @@ describe('text_utility', () => { }); }); - describe('gl.text.highCountTrim', () => { + describe('highCountTrim', () => { it('returns 99+ for count >= 100', () => { - expect(gl.text.highCountTrim(105)).toBe('99+'); - expect(gl.text.highCountTrim(100)).toBe('99+'); + expect(highCountTrim(105)).toBe('99+'); + expect(highCountTrim(100)).toBe('99+'); }); it('returns exact number for count < 100', () => { - expect(gl.text.highCountTrim(45)).toBe(45); + expect(highCountTrim(45)).toBe(45); }); }); diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index 3e791a31604..65d2e8fd9fb 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -815,7 +815,7 @@ import '~/notes'; }); it('shows a flash message', () => { - this.notes.addFlash('Error message', FLASH_TYPE_ALERT, this.notes.parentTimeline); + this.notes.addFlash('Error message', FLASH_TYPE_ALERT, this.notes.parentTimeline.get(0)); expect($('.flash-alert').is(':visible')).toBeTruthy(); }); @@ -828,7 +828,7 @@ import '~/notes'; }); it('hides visible flash message', () => { - this.notes.addFlash('Error message 1', FLASH_TYPE_ALERT, this.notes.parentTimeline); + this.notes.addFlash('Error message 1', FLASH_TYPE_ALERT, this.notes.parentTimeline.get(0)); this.notes.clearFlash(); diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 9230d58012f..80bf7986ee0 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -66,12 +66,34 @@ describe Gitlab::Workhorse do let(:diff_refs) { double(base_sha: "base", head_sha: "head") } subject { described_class.send_git_patch(repository, diff_refs) } - it 'sets the header correctly' do - key, command, params = decode_workhorse_header(subject) + context 'when Gitaly workhorse_send_git_patch feature is enabled' do + it 'sets the header correctly' do + key, command, params = decode_workhorse_header(subject) + + expect(key).to eq("Gitlab-Workhorse-Send-Data") + expect(command).to eq("git-format-patch") + expect(params).to eq({ + 'GitalyServer' => { + address: Gitlab::GitalyClient.address(project.repository_storage), + token: Gitlab::GitalyClient.token(project.repository_storage) + }, + 'RawPatchRequest' => Gitaly::RawPatchRequest.new( + repository: repository.gitaly_repository, + left_commit_id: 'base', + right_commit_id: 'head' + ).to_json + }.deep_stringify_keys) + end + end + + context 'when Gitaly workhorse_send_git_patch feature is disabled', :skip_gitaly_mock do + it 'sets the header correctly' do + key, command, params = decode_workhorse_header(subject) - expect(key).to eq("Gitlab-Workhorse-Send-Data") - expect(command).to eq("git-format-patch") - expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head") + expect(key).to eq("Gitlab-Workhorse-Send-Data") + expect(command).to eq("git-format-patch") + expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head") + end end end @@ -115,14 +137,36 @@ describe Gitlab::Workhorse do describe '.send_git_diff' do let(:diff_refs) { double(base_sha: "base", head_sha: "head") } - subject { described_class.send_git_patch(repository, diff_refs) } + subject { described_class.send_git_diff(repository, diff_refs) } - it 'sets the header correctly' do - key, command, params = decode_workhorse_header(subject) + context 'when Gitaly workhorse_send_git_diff feature is enabled' do + it 'sets the header correctly' do + key, command, params = decode_workhorse_header(subject) - expect(key).to eq("Gitlab-Workhorse-Send-Data") - expect(command).to eq("git-format-patch") - expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head") + expect(key).to eq("Gitlab-Workhorse-Send-Data") + expect(command).to eq("git-diff") + expect(params).to eq({ + 'GitalyServer' => { + address: Gitlab::GitalyClient.address(project.repository_storage), + token: Gitlab::GitalyClient.token(project.repository_storage) + }, + 'RawDiffRequest' => Gitaly::RawDiffRequest.new( + repository: repository.gitaly_repository, + left_commit_id: 'base', + right_commit_id: 'head' + ).to_json + }.deep_stringify_keys) + end + end + + context 'when Gitaly workhorse_send_git_diff feature is disabled', :skip_gitaly_mock do + it 'sets the header correctly' do + key, command, params = decode_workhorse_header(subject) + + expect(key).to eq("Gitlab-Workhorse-Send-Data") + expect(command).to eq("git-diff") + expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head") + end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index a26c71e5155..cf26dbfea49 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -874,7 +874,7 @@ describe Project do let(:project) { create(:project) } context 'when avatar file is uploaded' do - let(:project) { create(:project, :with_avatar) } + let(:project) { create(:project, :public, :with_avatar) } let(:avatar_path) { "/uploads/-/system/project/avatar/#{project.id}/dk.png" } let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" } diff --git a/spec/rubocop/cop/migration/datetime_spec.rb b/spec/rubocop/cop/migration/datetime_spec.rb index 388b086ce6a..b1dfcf1b048 100644 --- a/spec/rubocop/cop/migration/datetime_spec.rb +++ b/spec/rubocop/cop/migration/datetime_spec.rb @@ -9,6 +9,7 @@ describe RuboCop::Cop::Migration::Datetime do include CopHelper subject(:cop) { described_class.new } + let(:migration_with_datetime) do %q( class Users < ActiveRecord::Migration @@ -22,6 +23,19 @@ describe RuboCop::Cop::Migration::Datetime do ) end + let(:migration_with_timestamp) do + %q( + class Users < ActiveRecord::Migration + DOWNTIME = false + + def change + add_column(:users, :username, :text) + add_column(:users, :last_sign_in, :timestamp) + end + end + ) + end + let(:migration_without_datetime) do %q( class Users < ActiveRecord::Migration @@ -58,6 +72,17 @@ describe RuboCop::Cop::Migration::Datetime do aggregate_failures do expect(cop.offenses.size).to eq(1) expect(cop.offenses.map(&:line)).to eq([7]) + expect(cop.offenses.first.message).to include('datetime') + end + end + + it 'registers an offense when the ":timestamp" data type is used' do + inspect_source(cop, migration_with_timestamp) + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([7]) + expect(cop.offenses.first.message).to include('timestamp') end end @@ -81,6 +106,7 @@ describe RuboCop::Cop::Migration::Datetime do context 'outside of migration' do it 'registers no offense' do inspect_source(cop, migration_with_datetime) + inspect_source(cop, migration_with_timestamp) inspect_source(cop, migration_without_datetime) inspect_source(cop, migration_with_datetime_with_timezone) diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 7257c359a7e..98409be4236 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -127,10 +127,10 @@ describe MergeRequests::UpdateService, :mailer do end it 'creates system note about discussion lock' do - note = find_note('locked this issue') + note = find_note('locked this merge request') expect(note).not_to be_nil - expect(note.note).to eq 'locked this issue' + expect(note.note).to eq 'locked this merge request' end context 'when not including source branch removal options' do diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index b1241cd8d0b..cd473c1f388 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -1145,4 +1145,42 @@ describe SystemNoteService do it { expect(subject.note).to eq "marked #{duplicate_issue.to_reference(project)} as a duplicate of this issue" } end end + + describe '.discussion_lock' do + subject { described_class.discussion_lock(noteable, author) } + + context 'discussion unlocked' do + it_behaves_like 'a system note' do + let(:action) { 'unlocked' } + end + + it 'creates the note text correctly' do + [:issue, :merge_request].each do |type| + issuable = create(type) + + expect(described_class.discussion_lock(issuable, author).note) + .to eq("unlocked this #{type.to_s.titleize.downcase}") + end + end + end + + context 'discussion locked' do + before do + noteable.update_attribute(:discussion_locked, true) + end + + it_behaves_like 'a system note' do + let(:action) { 'locked' } + end + + it 'creates the note text correctly' do + [:issue, :merge_request].each do |type| + issuable = create(type, discussion_locked: true) + + expect(described_class.discussion_lock(issuable, author).note) + .to eq("locked this #{type.to_s.titleize.downcase}") + end + end + end + end end diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb index d9e9409840f..e881ec37ae5 100644 --- a/spec/workers/repository_fork_worker_spec.rb +++ b/spec/workers/repository_fork_worker_spec.rb @@ -12,6 +12,28 @@ describe RepositoryForkWorker do end describe "#perform" do + describe 'when a worker was reset without cleanup' do + let(:jid) { '12345678' } + let(:started_project) { create(:project, :repository, :import_started) } + + it 'creates a new repository from a fork' do + allow(subject).to receive(:jid).and_return(jid) + + expect(shell).to receive(:fork_repository).with( + '/test/path', + project.full_path, + project.repository_storage_path, + fork_project.namespace.full_path + ).and_return(true) + + subject.perform( + project.id, + '/test/path', + project.full_path, + fork_project.namespace.full_path) + end + end + it "creates a new repository from a fork" do expect(shell).to receive(:fork_repository).with( '/test/path', diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb index 100dfc32bbe..5cff5108477 100644 --- a/spec/workers/repository_import_worker_spec.rb +++ b/spec/workers/repository_import_worker_spec.rb @@ -6,6 +6,23 @@ describe RepositoryImportWorker do subject { described_class.new } describe '#perform' do + context 'when worker was reset without cleanup' do + let(:jid) { '12345678' } + let(:started_project) { create(:project, :import_started, import_jid: jid) } + + it 'imports the project successfully' do + allow(subject).to receive(:jid).and_return(jid) + + expect_any_instance_of(Projects::ImportService).to receive(:execute) + .and_return({ status: :ok }) + + expect_any_instance_of(Repository).to receive(:expire_emptiness_caches) + expect_any_instance_of(Project).to receive(:import_finish) + + subject.perform(project.id) + end + end + context 'when the import was successful' do it 'imports a project' do expect_any_instance_of(Projects::ImportService).to receive(:execute) |