diff options
author | Filipa Lacerda <filipa@gitlab.com> | 2017-12-11 19:42:58 +0000 |
---|---|---|
committer | Filipa Lacerda <filipa@gitlab.com> | 2017-12-11 19:42:58 +0000 |
commit | 3c19c971df0490772494e224405f13a0f98d6bf4 (patch) | |
tree | ff78e561cbbfb49d66f135f8dbf638bf07bcff60 /app/assets/javascripts | |
parent | a2479aaa1be00416f952eb79fa444328266c16b6 (diff) | |
parent | 4ccbd556d98e002b1c521fd3dd7748fe1d9c4044 (diff) | |
download | gitlab-ce-3c19c971df0490772494e224405f13a0f98d6bf4.tar.gz |
Merge branch 'master' into 38869-datetime
* master: (112 commits)
small change to make less conflict with EE version
Add cop for use of remove_column
Resolve merge conflicts with dev.gitlab.org/master after security release
add index for doc/administration/operations/
Remove RubySampler#sample_objects for performance as well
Bugfix: User can't change the access level of an access requester
Add spec for removing issues.assignee_id
updated imports
Keep track of storage check timings
Remove a header level in the new 'Automatic CE->EE merge' doc
Improve down step of removing issues.assignee_id column
Fix specs after removing assignee_id field
Remove issues.assignee_id column
Resolve conflicts in app/models/user.rb
Fix image view mode
Do not raise when downstream pipeline is created
Remove the need for destroy and add a comment in the spec
Use build instead of create in importer spec
Simplify normalizing of paths
Remove allocation tracking code from InfluxDB sampler for performance
...
Diffstat (limited to 'app/assets/javascripts')
57 files changed, 568 insertions, 357 deletions
diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js index 34669dd13d6..b0b72c40f25 100644 --- a/app/assets/javascripts/admin.js +++ b/app/assets/javascripts/admin.js @@ -1,4 +1,5 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-arrow-callback, camelcase, quotes, comma-dangle, max-len */ +import { refreshCurrentPage } from './lib/utils/url_utility'; window.Admin = (function() { function Admin() { @@ -40,10 +41,10 @@ window.Admin = (function() { return $('.change-owner-link').show(); }); $('li.project_member').bind('ajax:success', function() { - return gl.utils.refreshCurrentPage(); + return refreshCurrentPage(); }); $('li.group_member').bind('ajax:success', function() { - return gl.utils.refreshCurrentPage(); + return refreshCurrentPage(); }); showBlacklistType = function() { if ($("input[name='blacklist_type']:checked").val() === 'file') { diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js index b70b0a9bbf8..417ac31fc86 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.js +++ b/app/assets/javascripts/behaviors/toggler_behavior.js @@ -5,6 +5,7 @@ // %button.js-toggle-button // %div.js-toggle-content // +import { getLocationHash } from '../lib/utils/url_utility'; $(() => { function toggleContainer(container, toggleState) { @@ -32,7 +33,7 @@ $(() => { // If we're accessing a permalink, ensure it is not inside a // closed js-toggle-container! - const hash = window.gl.utils.getLocationHash(); + const hash = getLocationHash(); const anchor = hash && document.getElementById(hash); const container = anchor && $(anchor).closest('.js-toggle-container'); diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js index 0d590a9dbc4..f7ae6f1cd12 100644 --- a/app/assets/javascripts/blob/blob_file_dropzone.js +++ b/app/assets/javascripts/blob/blob_file_dropzone.js @@ -1,6 +1,6 @@ /* eslint-disable func-names, object-shorthand, prefer-arrow-callback */ import Dropzone from 'dropzone'; -import '../lib/utils/url_utility'; +import { visitUrl } from '../lib/utils/url_utility'; import { HIDDEN_CLASS } from '../lib/utils/constants'; import csrf from '../lib/utils/csrf'; @@ -49,7 +49,7 @@ export default class BlobFileDropzone { }); this.on('success', function (header, response) { $('#modal-upload-blob').modal('hide'); - window.gl.utils.visitUrl(response.filePath); + visitUrl(response.filePath); }); this.on('maxfilesexceeded', function (file) { dropzoneMessage.addClass(HIDDEN_CLASS); diff --git a/app/assets/javascripts/blob/blob_line_permalink_updater.js b/app/assets/javascripts/blob/blob_line_permalink_updater.js index c8f68860fbd..d36d9f0de2d 100644 --- a/app/assets/javascripts/blob/blob_line_permalink_updater.js +++ b/app/assets/javascripts/blob/blob_line_permalink_updater.js @@ -1,7 +1,9 @@ +import { getLocationHash } from '../lib/utils/url_utility'; + const lineNumberRe = /^L[0-9]+/; const updateLineNumbersOnBlobPermalinks = (linksToUpdate) => { - const hash = gl.utils.getLocationHash(); + const hash = getLocationHash(); if (hash && lineNumberRe.test(hash)) { const hashUrlString = `#${hash}`; diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js index 50d0cb5c86d..5662802525e 100644 --- a/app/assets/javascripts/commit/image_file.js +++ b/app/assets/javascripts/commit/image_file.js @@ -121,7 +121,7 @@ export default class ImageFile { return $('.swipe.view', this.file).each((function(_this) { return function(index, view) { var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref; - ref = this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1]; + ref = _this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1]; $swipeFrame = $('.swipe-frame', view); $swipeWrap = $('.swipe-wrap', view); $swipeBar = $('.swipe-bar', view); @@ -158,7 +158,7 @@ export default class ImageFile { return $('.onion-skin.view', this.file).each((function(_this) { return function(index, view) { var $frame, $track, $dragger, $frameAdded, framePadding, ref, dragging = false; - ref = this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1]; + ref = _this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1]; $frame = $('.onion-skin-frame', view); $frameAdded = $('.frame.added', view); $track = $('.drag-track', view); diff --git a/app/assets/javascripts/compare.js b/app/assets/javascripts/compare.js index d8cbf7e99c5..144caf1d278 100644 --- a/app/assets/javascripts/compare.js +++ b/app/assets/javascripts/compare.js @@ -1,8 +1,8 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */ import { localTimeAgo } from './lib/utils/datetime_utility'; -window.Compare = (function() { - function Compare(opts) { +export default class Compare { + constructor(opts) { this.opts = opts; this.source_loading = $(".js-source-loading"); this.target_loading = $(".js-target-loading"); @@ -35,12 +35,12 @@ window.Compare = (function() { this.initialState(); } - Compare.prototype.initialState = function() { + initialState() { this.getSourceHtml(); - return this.getTargetHtml(); - }; + this.getTargetHtml(); + } - Compare.prototype.getTargetProject = function() { + getTargetProject() { return $.ajax({ url: this.opts.targetProjectUrl, data: { @@ -53,22 +53,22 @@ window.Compare = (function() { return $('.js-target-branch-dropdown .dropdown-content').html(html); } }); - }; + } - Compare.prototype.getSourceHtml = function() { - return this.sendAjax(this.opts.sourceBranchUrl, this.source_loading, '.mr_source_commit', { + getSourceHtml() { + return this.constructor.sendAjax(this.opts.sourceBranchUrl, this.source_loading, '.mr_source_commit', { ref: $("input[name='merge_request[source_branch]']").val() }); - }; + } - Compare.prototype.getTargetHtml = function() { - return this.sendAjax(this.opts.targetBranchUrl, this.target_loading, '.mr_target_commit', { + getTargetHtml() { + return this.constructor.sendAjax(this.opts.targetBranchUrl, this.target_loading, '.mr_target_commit', { target_project_id: $("input[name='merge_request[target_project_id]']").val(), ref: $("input[name='merge_request[target_branch]']").val() }); - }; + } - Compare.prototype.sendAjax = function(url, loading, target, data) { + static sendAjax(url, loading, target, data) { var $target; $target = $(target); return $.ajax({ @@ -85,7 +85,5 @@ window.Compare = (function() { localTimeAgo($('.js-timeago', className)); } }); - }; - - return Compare; -})(); + } +} diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js index 72c0d98d47c..e633ef8a29e 100644 --- a/app/assets/javascripts/compare_autocomplete.js +++ b/app/assets/javascripts/compare_autocomplete.js @@ -1,68 +1,60 @@ /* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, object-shorthand, comma-dangle, prefer-arrow-callback, no-else-return, newline-per-chained-call, wrap-iife, max-len */ -window.CompareAutocomplete = (function() { - function CompareAutocomplete() { - this.initDropdown(); - } - - CompareAutocomplete.prototype.initDropdown = function() { - return $('.js-compare-dropdown').each(function() { - var $dropdown, selected; - $dropdown = $(this); - selected = $dropdown.data('selected'); - const $dropdownContainer = $dropdown.closest('.dropdown'); - const $fieldInput = $(`input[name="${$dropdown.data('field-name')}"]`, $dropdownContainer); - const $filterInput = $('input[type="search"]', $dropdownContainer); - $dropdown.glDropdown({ - data: function(term, callback) { - return $.ajax({ - url: $dropdown.data('refs-url'), - data: { - ref: $dropdown.data('ref'), - search: term, - } - }).done(function(refs) { - return callback(refs); - }); - }, - selectable: true, - filterable: true, - filterRemote: true, - fieldName: $dropdown.data('field-name'), - filterInput: 'input[type="search"]', - renderRow: function(ref) { - var link; - if (ref.header != null) { - return $('<li />').addClass('dropdown-header').text(ref.header); - } else { - link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref)); - return $('<li />').append(link); +export default function initCompareAutocomplete() { + $('.js-compare-dropdown').each(function() { + var $dropdown, selected; + $dropdown = $(this); + selected = $dropdown.data('selected'); + const $dropdownContainer = $dropdown.closest('.dropdown'); + const $fieldInput = $(`input[name="${$dropdown.data('field-name')}"]`, $dropdownContainer); + const $filterInput = $('input[type="search"]', $dropdownContainer); + $dropdown.glDropdown({ + data: function(term, callback) { + return $.ajax({ + url: $dropdown.data('refs-url'), + data: { + ref: $dropdown.data('ref'), + search: term, } - }, - id: function(obj, $el) { - return $el.attr('data-ref'); - }, - toggleLabel: function(obj, $el) { - return $el.text().trim(); - } - }); - $filterInput.on('keyup', (e) => { - const keyCode = e.keyCode || e.which; - if (keyCode !== 13) return; - const text = $filterInput.val(); - $fieldInput.val(text); - $('.dropdown-toggle-text', $dropdown).text(text); - $dropdownContainer.removeClass('open'); - }); - - $dropdownContainer.on('click', '.dropdown-content a', (e) => { - $dropdown.prop('title', e.target.text.replace(/_+?/g, '-')); - if ($dropdown.hasClass('has-tooltip')) { - $dropdown.tooltip('fixTitle'); + }).done(function(refs) { + return callback(refs); + }); + }, + selectable: true, + filterable: true, + filterRemote: true, + fieldName: $dropdown.data('field-name'), + filterInput: 'input[type="search"]', + renderRow: function(ref) { + var link; + if (ref.header != null) { + return $('<li />').addClass('dropdown-header').text(ref.header); + } else { + link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref)); + return $('<li />').append(link); } - }); + }, + id: function(obj, $el) { + return $el.attr('data-ref'); + }, + toggleLabel: function(obj, $el) { + return $el.text().trim(); + } + }); + $filterInput.on('keyup', (e) => { + const keyCode = e.keyCode || e.which; + if (keyCode !== 13) return; + const text = $filterInput.val(); + $fieldInput.val(text); + $('.dropdown-toggle-text', $dropdown).text(text); + $dropdownContainer.removeClass('open'); }); - }; - return CompareAutocomplete; -})(); + $dropdownContainer.on('click', '.dropdown-content a', (e) => { + $dropdown.prop('title', e.target.text.replace(/_+?/g, '-')); + if ($dropdown.hasClass('has-tooltip')) { + $dropdown.tooltip('fixTitle'); + } + }); + }); +} diff --git a/app/assets/javascripts/contextual_sidebar.js b/app/assets/javascripts/contextual_sidebar.js index 46b68ebe158..cd20dde2951 100644 --- a/app/assets/javascripts/contextual_sidebar.js +++ b/app/assets/javascripts/contextual_sidebar.js @@ -28,7 +28,7 @@ export default class ContextualSidebar { this.$closeSidebar.on('click', () => this.toggleSidebarNav(false)); this.$overlay.on('click', () => this.toggleSidebarNav(false)); this.$sidebarToggle.on('click', () => { - const value = !this.$sidebar.hasClass('sidebar-icons-only'); + const value = !this.$sidebar.hasClass('sidebar-collapsed-desktop'); this.toggleCollapsedSidebar(value); }); @@ -43,16 +43,16 @@ export default class ContextualSidebar { } toggleSidebarNav(show) { - this.$sidebar.toggleClass('nav-sidebar-expanded', show); + this.$sidebar.toggleClass('sidebar-expanded-mobile', show); this.$overlay.toggleClass('mobile-nav-open', show); - this.$sidebar.removeClass('sidebar-icons-only'); + this.$sidebar.removeClass('sidebar-collapsed-desktop'); } toggleCollapsedSidebar(collapsed) { const breakpoint = bp.getBreakpointSize(); if (this.$sidebar.length) { - this.$sidebar.toggleClass('sidebar-icons-only', collapsed); + this.$sidebar.toggleClass('sidebar-collapsed-desktop', collapsed); this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed); } ContextualSidebar.setCollapsedCookie(collapsed); diff --git a/app/assets/javascripts/deploy_keys/components/action_btn.vue b/app/assets/javascripts/deploy_keys/components/action_btn.vue index 3f993213dd0..f9f2f9bf693 100644 --- a/app/assets/javascripts/deploy_keys/components/action_btn.vue +++ b/app/assets/javascripts/deploy_keys/components/action_btn.vue @@ -32,7 +32,9 @@ doAction() { this.isLoading = true; - eventHub.$emit(`${this.type}.key`, this.deployKey); + eventHub.$emit(`${this.type}.key`, this.deployKey, () => { + this.isLoading = false; + }); }, }, computed: { @@ -50,6 +52,9 @@ :disabled="isLoading" @click="doAction"> {{ text }} - <loading-icon v-if="isLoading" /> + <loading-icon + v-if="isLoading" + :inline="true" + /> </button> </template> diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue index 54e13b79a4f..fe046449054 100644 --- a/app/assets/javascripts/deploy_keys/components/app.vue +++ b/app/assets/javascripts/deploy_keys/components/app.vue @@ -47,12 +47,15 @@ .then(() => this.fetchKeys()) .catch(() => new Flash('Error enabling deploy key')); }, - disableKey(deployKey) { + disableKey(deployKey, callback) { // eslint-disable-next-line no-alert if (confirm('You are going to remove this deploy key. Are you sure?')) { this.service.disableKey(deployKey.id) .then(() => this.fetchKeys()) + .then(callback) .catch(() => new Flash('Error removing deploy key')); + } else { + callback(); } }, }, diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index c8874e48c09..a162424b3cf 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -1,4 +1,4 @@ -import './lib/utils/url_utility'; +import { getLocationHash } from './lib/utils/url_utility'; import FilesCommentButton from './files_comment_button'; import SingleFileDiff from './single_file_diff'; import imageDiffHelper from './image_diff/helpers/index'; @@ -31,7 +31,7 @@ export default class Diff { isBound = true; } - if (gl.utils.getLocationHash()) { + if (getLocationHash()) { this.highlightSelectedLine(); } @@ -73,7 +73,7 @@ export default class Diff { } openAnchoredDiff(cb) { - const locationHash = gl.utils.getLocationHash(); + const locationHash = getLocationHash(); const anchoredDiff = locationHash && locationHash.split('_')[0]; if (!anchoredDiff) return; @@ -128,7 +128,7 @@ export default class Diff { } // eslint-disable-next-line class-methods-use-this highlightSelectedLine() { - const hash = gl.utils.getLocationHash(); + const hash = getLocationHash(); const $diffFiles = $('.diff-file'); $diffFiles.find('.hll').removeClass('hll'); diff --git a/app/assets/javascripts/diff_notes/diff_notes_bundle.js b/app/assets/javascripts/diff_notes/diff_notes_bundle.js index 0863c3406bd..e0422057090 100644 --- a/app/assets/javascripts/diff_notes/diff_notes_bundle.js +++ b/app/assets/javascripts/diff_notes/diff_notes_bundle.js @@ -16,7 +16,8 @@ import './components/diff_note_avatars'; import './components/new_issue_for_discussion'; $(() => { - const projectPath = document.querySelector('.merge-request').dataset.projectPath; + const projectPathHolder = document.querySelector('.merge-request') || document.querySelector('.commit-box'); + const projectPath = projectPathHolder.dataset.projectPath; const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn, new-issue-for-discussion-btn'; window.gl = window.gl || {}; diff --git a/app/assets/javascripts/diff_notes/services/resolve.js b/app/assets/javascripts/diff_notes/services/resolve.js index 6eae54f830b..96fe23640af 100644 --- a/app/assets/javascripts/diff_notes/services/resolve.js +++ b/app/assets/javascripts/diff_notes/services/resolve.js @@ -43,7 +43,7 @@ class ResolveServiceClass { discussion.resolveAllNotes(resolvedBy); } - gl.mrWidget.checkStatus(); + if (gl.mrWidget) gl.mrWidget.checkStatus(); discussion.updateHeadline(data); }) .catch(() => new Flash('An error occurred when trying to resolve a discussion. Please try again.')); diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 24476d3d757..299e43a4e90 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -22,8 +22,8 @@ import NewCommitForm from './new_commit_form'; import Project from './project'; import projectAvatar from './project_avatar'; /* global MergeRequest */ -/* global Compare */ -/* global CompareAutocomplete */ +import Compare from './compare'; +import initCompareAutocomplete from './compare_autocomplete'; /* global ProjectFindFile */ import ProjectNew from './project_new'; import projectImport from './project_import'; @@ -525,13 +525,6 @@ import ProjectVariables from './project_variables'; case 'projects:settings:ci_cd:show': // Initialize expandable settings panels initSettingsPanels(); - - import(/* webpackChunkName: "ci-cd-settings" */ './projects/ci_cd_settings_bundle') - .then(ciCdSettings => ciCdSettings.default()) - .catch((err) => { - Flash(s__('ProjectSettings|Problem setting up the CI/CD settings JavaScript')); - throw err; - }); case 'groups:settings:ci_cd:show': new ProjectVariables(); break; @@ -629,7 +622,7 @@ import ProjectVariables from './project_variables'; projectAvatar(); switch (path[1]) { case 'compare': - new CompareAutocomplete(); + initCompareAutocomplete(); break; case 'edit': shortcut_handler = new ShortcutsNavigation(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index 69c57f923b6..2ba85c7da97 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 { visitUrl } from '../lib/utils/url_utility'; import Flash from '../flash'; import FilteredSearchContainer from './container'; import RecentSearchesRoot from './recent_searches_root'; @@ -566,7 +567,7 @@ class FilteredSearchManager { if (this.updateObject) { this.updateObject(parameterizedUrl); } else { - gl.utils.visitUrl(parameterizedUrl); + visitUrl(parameterizedUrl); } } diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js index 98837c3b2a0..6110d961609 100644 --- a/app/assets/javascripts/fly_out_nav.js +++ b/app/assets/javascripts/fly_out_nav.js @@ -21,7 +21,7 @@ let headerHeight = 50; export const getHeaderHeight = () => headerHeight; -export const isSidebarCollapsed = () => sidebar && sidebar.classList.contains('sidebar-icons-only'); +export const isSidebarCollapsed = () => sidebar && sidebar.classList.contains('sidebar-collapsed-desktop'); export const canShowActiveSubItems = (el) => { if (el.classList.contains('active') && !isSidebarCollapsed()) { diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 7ca783d3af6..cf4a70e321e 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -2,6 +2,7 @@ /* global fuzzaldrinPlus */ import _ from 'underscore'; import fuzzaldrinPlus from 'fuzzaldrin-plus'; +import { visitUrl } from './lib/utils/url_utility'; import { isObject } from './lib/utils/type_utility'; var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote, GitLabDropdownInput; @@ -852,7 +853,7 @@ GitLabDropdown = (function() { if ($el.length) { var href = $el.attr('href'); if (href && href !== '#') { - gl.utils.visitUrl(href); + visitUrl(href); } else { $el.trigger('click'); } diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue index 2c0b6ab4ea8..241e026b84c 100644 --- a/app/assets/javascripts/groups/components/app.vue +++ b/app/assets/javascripts/groups/components/app.vue @@ -5,7 +5,7 @@ import eventHub from '../event_hub'; import { getParameterByName } from '../../lib/utils/common_utils'; import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import { COMMON_STR } from '../constants'; - +import { mergeUrlParams } from '../../lib/utils/url_utility'; import groupsComponent from './groups.vue'; export default { @@ -93,7 +93,7 @@ export default { this.isLoading = false; $.scrollTo(0); - const currentPath = gl.utils.mergeUrlParams({ page }, window.location.href); + const currentPath = mergeUrlParams({ page }, window.location.href); window.history.replaceState({ page: currentPath, }, document.title, currentPath); diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue index c76ce762b54..6421547bbde 100644 --- a/app/assets/javascripts/groups/components/group_item.vue +++ b/app/assets/javascripts/groups/components/group_item.vue @@ -1,4 +1,5 @@ <script> +import { visitUrl } from '../../lib/utils/url_utility'; import tooltip from '../../vue_shared/directives/tooltip'; import identicon from '../../vue_shared/components/identicon.vue'; import eventHub from '../event_hub'; @@ -60,7 +61,7 @@ export default { if (this.hasChildren) { eventHub.$emit('toggleChildren', this.group); } else { - gl.utils.visitUrl(this.group.relativePath); + visitUrl(this.group.relativePath); } } }, diff --git a/app/assets/javascripts/groups/new_group_child.js b/app/assets/javascripts/groups/new_group_child.js index 8e273579aae..a120d501e35 100644 --- a/app/assets/javascripts/groups/new_group_child.js +++ b/app/assets/javascripts/groups/new_group_child.js @@ -1,3 +1,4 @@ +import { visitUrl } from '../lib/utils/url_utility'; import DropLab from '../droplab/drop_lab'; import ISetter from '../droplab/plugins/input_setter'; @@ -54,9 +55,9 @@ export default class NewGroupChild { onClickNewGroupChildButton(e) { if (e.target.dataset.action === NEW_PROJECT) { - gl.utils.visitUrl(this.newGroupPath); + visitUrl(this.newGroupPath); } else if (e.target.dataset.action === NEW_SUBGROUP) { - gl.utils.visitUrl(this.subgroupPath); + visitUrl(this.subgroupPath); } } } diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js index 7de07e9403d..91b5ef1c10a 100644 --- a/app/assets/javascripts/issue.js +++ b/app/assets/javascripts/issue.js @@ -8,18 +8,7 @@ import IssuablesHelper from './helpers/issuables_helper'; export default class Issue { constructor() { - if ($('a.btn-close').length) { - this.taskList = new TaskList({ - dataType: 'issue', - fieldName: 'description', - selector: '.detail-page-description', - onSuccess: (result) => { - document.querySelector('#task_status').innerText = result.task_status; - document.querySelector('#task_status_short').innerText = result.task_status_short; - } - }); - this.initIssueBtnEventListeners(); - } + if ($('a.btn-close').length) this.initIssueBtnEventListeners(); Issue.$btnNewBranch = $('#new-branch'); Issue.createMrDropdownWrap = document.querySelector('.create-mr-dropdown-wrap'); diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue index 5bdc7c99503..fd1a50dd533 100644 --- a/app/assets/javascripts/issue_show/components/app.vue +++ b/app/assets/javascripts/issue_show/components/app.vue @@ -1,5 +1,6 @@ <script> import Visibility from 'visibilityjs'; +import { visitUrl } from '../../lib/utils/url_utility'; import Poll from '../../lib/utils/poll'; import eventHub from '../event_hub'; import Service from '../services/index'; @@ -8,7 +9,7 @@ import titleComponent from './title.vue'; import descriptionComponent from './description.vue'; import editedComponent from './edited.vue'; import formComponent from './form.vue'; -import '../../lib/utils/url_utility'; +import RecaptchaDialogImplementor from '../../vue_shared/mixins/recaptcha_dialog_implementor'; export default { props: { @@ -149,6 +150,11 @@ export default { editedComponent, formComponent, }, + + mixins: [ + RecaptchaDialogImplementor, + ], + methods: { openForm() { if (!this.showForm) { @@ -164,12 +170,14 @@ export default { closeForm() { this.showForm = false; }, + updateIssuable() { this.service.updateIssuable(this.store.formState) .then(res => res.json()) + .then(data => this.checkForSpam(data)) .then((data) => { if (location.pathname !== data.web_url) { - gl.utils.visitUrl(data.web_url); + visitUrl(data.web_url); } return this.service.getData(); @@ -179,11 +187,24 @@ export default { this.store.updateState(data); eventHub.$emit('close.form'); }) - .catch(() => { - eventHub.$emit('close.form'); - window.Flash(`Error updating ${this.issuableType}`); + .catch((error) => { + if (error && error.name === 'SpamError') { + this.openRecaptcha(); + } else { + eventHub.$emit('close.form'); + window.Flash(`Error updating ${this.issuableType}`); + } }); }, + + closeRecaptchaDialog() { + this.store.setFormState({ + updateLoading: false, + }); + + this.closeRecaptcha(); + }, + deleteIssuable() { this.service.deleteIssuable() .then(res => res.json()) @@ -191,7 +212,7 @@ export default { // Stop the poll so we don't get 404's with the issuable not existing this.poll.stop(); - gl.utils.visitUrl(data.web_url); + visitUrl(data.web_url); }) .catch(() => { eventHub.$emit('close.form'); @@ -237,9 +258,9 @@ export default { </script> <template> - <div> +<div> + <div v-if="canUpdate && showForm"> <form-component - v-if="canUpdate && showForm" :form-state="formState" :can-destroy="canDestroy" :issuable-templates="issuableTemplates" @@ -251,30 +272,37 @@ export default { :can-attach-file="canAttachFile" :enable-autocomplete="enableAutocomplete" /> - <div v-else> - <title-component - :issuable-ref="issuableRef" - :can-update="canUpdate" - :title-html="state.titleHtml" - :title-text="state.titleText" - :show-inline-edit-button="showInlineEditButton" - /> - <description-component - v-if="state.descriptionHtml" - :can-update="canUpdate" - :description-html="state.descriptionHtml" - :description-text="state.descriptionText" - :updated-at="state.updatedAt" - :task-status="state.taskStatus" - :issuable-type="issuableType" - :update-url="updateEndpoint" - /> - <edited-component - v-if="hasUpdated" - :updated-at="state.updatedAt" - :updated-by-name="state.updatedByName" - :updated-by-path="state.updatedByPath" - /> - </div> + + <recaptcha-dialog + v-show="showRecaptcha" + :html="recaptchaHTML" + @close="closeRecaptchaDialog" + /> + </div> + <div v-else> + <title-component + :issuable-ref="issuableRef" + :can-update="canUpdate" + :title-html="state.titleHtml" + :title-text="state.titleText" + :show-inline-edit-button="showInlineEditButton" + /> + <description-component + v-if="state.descriptionHtml" + :can-update="canUpdate" + :description-html="state.descriptionHtml" + :description-text="state.descriptionText" + :updated-at="state.updatedAt" + :task-status="state.taskStatus" + :issuable-type="issuableType" + :update-url="updateEndpoint" + /> + <edited-component + v-if="hasUpdated" + :updated-at="state.updatedAt" + :updated-by-name="state.updatedByName" + :updated-by-path="state.updatedByPath" + /> </div> +</div> </template> diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue index b7559ced946..feb73481422 100644 --- a/app/assets/javascripts/issue_show/components/description.vue +++ b/app/assets/javascripts/issue_show/components/description.vue @@ -1,9 +1,14 @@ <script> import animateMixin from '../mixins/animate'; import TaskList from '../../task_list'; + import RecaptchaDialogImplementor from '../../vue_shared/mixins/recaptcha_dialog_implementor'; export default { - mixins: [animateMixin], + mixins: [ + animateMixin, + RecaptchaDialogImplementor, + ], + props: { canUpdate: { type: Boolean, @@ -51,6 +56,7 @@ this.updateTaskStatusText(); }, }, + methods: { renderGFM() { $(this.$refs['gfm-content']).renderGFM(); @@ -61,9 +67,19 @@ dataType: this.issuableType, fieldName: 'description', selector: '.detail-page-description', + onSuccess: this.taskListUpdateSuccess.bind(this), }); } }, + + taskListUpdateSuccess(data) { + try { + this.checkForSpam(data); + } catch (error) { + if (error && error.name === 'SpamError') this.openRecaptcha(); + } + }, + updateTaskStatusText() { const taskRegexMatches = this.taskStatus.match(/(\d+) of ((?!0)\d+)/); const $issuableHeader = $('.issuable-meta'); @@ -109,5 +125,11 @@ :data-update-url="updateUrl" > </textarea> + + <recaptcha-dialog + v-show="showRecaptcha" + :html="recaptchaHTML" + @close="closeRecaptcha" + /> </div> </template> diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js index aca9dec2a96..a21ce41e65e 100644 --- a/app/assets/javascripts/issue_show/index.js +++ b/app/assets/javascripts/issue_show/index.js @@ -5,7 +5,7 @@ import '../vue_shared/vue_resource_interceptor'; document.addEventListener('DOMContentLoaded', () => { const initialDataEl = document.getElementById('js-issuable-app-initial-data'); - const initialData = JSON.parse(initialDataEl.innerHTML.replace(/"/g, '"')); + const props = JSON.parse(initialDataEl.innerHTML.replace(/"/g, '"')); $('.issuable-edit').on('click', (e) => { e.preventDefault(); @@ -18,32 +18,9 @@ document.addEventListener('DOMContentLoaded', () => { components: { issuableApp, }, - data() { - return { - ...initialData, - }; - }, render(createElement) { return createElement('issuable-app', { - props: { - canUpdate: this.canUpdate, - canDestroy: this.canDestroy, - endpoint: this.endpoint, - issuableRef: this.issuableRef, - initialTitleHtml: this.initialTitleHtml, - initialTitleText: this.initialTitleText, - initialDescriptionHtml: this.initialDescriptionHtml, - initialDescriptionText: this.initialDescriptionText, - issuableTemplates: this.issuableTemplates, - markdownPreviewPath: this.markdownPreviewPath, - markdownDocsPath: this.markdownDocsPath, - projectPath: this.projectPath, - projectNamespace: this.projectNamespace, - updatedAt: this.updatedAt, - updatedByName: this.updatedByName, - updatedByPath: this.updatedByPath, - initialTaskStatus: this.initialTaskStatus, - }, + props, }); }, }); diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js index 8c011a36dd6..198a7823381 100644 --- a/app/assets/javascripts/job.js +++ b/app/assets/javascripts/job.js @@ -1,4 +1,5 @@ import _ from 'underscore'; +import { visitUrl } from './lib/utils/url_utility'; import bp from './breakpoints'; import { bytesToKiB } from './lib/utils/number_utils'; import { setCiStatusFavicon } from './lib/utils/common_utils'; @@ -10,7 +11,7 @@ export default class Job { this.state = null; this.options = options || $('.js-build-options').data(); - this.pageUrl = this.options.pageUrl; + this.pagePath = this.options.pagePath; this.buildStatus = this.options.buildStatus; this.state = this.options.logState; this.buildStage = this.options.buildStage; @@ -168,11 +169,11 @@ export default class Job { getBuildTrace() { return $.ajax({ - url: `${this.pageUrl}/trace.json`, + url: `${this.pagePath}/trace.json`, data: { state: this.state }, }) .done((log) => { - setCiStatusFavicon(`${this.pageUrl}/status.json`); + setCiStatusFavicon(`${this.pagePath}/status.json`); if (log.state) { this.state = log.state; @@ -210,7 +211,7 @@ export default class Job { } if (log.status !== this.buildStatus) { - gl.utils.visitUrl(this.pageUrl); + visitUrl(this.pagePath); } }) .fail(() => { diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 33cc807912c..b5328c77b25 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -1,3 +1,4 @@ +import { getLocationHash } from './url_utility'; export const getPagePath = (index = 0) => $('body').attr('data-page').split(':')[index]; @@ -65,7 +66,7 @@ export const disableButtonIfEmptyField = (fieldSelector, buttonSelector, eventNa // automatically adjust scroll position for hash urls taking the height of the navbar into account // https://github.com/twitter/bootstrap/issues/1768 export const handleLocationHash = () => { - let hash = window.gl.utils.getLocationHash(); + let hash = getLocationHash(); if (!hash) return; // This is required to handle non-unicode characters in hash diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index 17236c91490..f1ee9c8f2e5 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -1,93 +1,69 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, one-var, one-var-declaration-per-line, no-void, guard-for-in, no-restricted-syntax, prefer-template, quotes, max-len */ - -var base; -var w = window; -if (w.gl == null) { - w.gl = {}; -} -if ((base = w.gl).utils == null) { - base.utils = {}; -} // Returns an array containing the value(s) of the // of the key passed as an argument -w.gl.utils.getParameterValues = function(sParam) { - var i, sPageURL, sParameterName, sURLVariables, values; - sPageURL = decodeURIComponent(window.location.search.substring(1)); - sURLVariables = sPageURL.split('&'); - sParameterName = void 0; - values = []; - i = 0; - while (i < sURLVariables.length) { - sParameterName = sURLVariables[i].split('='); +export function getParameterValues(sParam) { + const sPageURL = decodeURIComponent(window.location.search.substring(1)); + + return sPageURL.split('&').reduce((acc, urlParam) => { + const sParameterName = urlParam.split('='); + if (sParameterName[0] === sParam) { - values.push(sParameterName[1].replace(/\+/g, ' ')); + acc.push(sParameterName[1].replace(/\+/g, ' ')); } - i += 1; - } - return values; -}; + + return acc; + }, []); +} + // @param {Object} params - url keys and value to merge // @param {String} url -w.gl.utils.mergeUrlParams = function(params, url) { - var lastChar, newUrl, paramName, paramValue, pattern; - newUrl = decodeURIComponent(url); - for (paramName in params) { - paramValue = params[paramName]; - pattern = new RegExp("\\b(" + paramName + "=).*?(&|$)"); - if (paramValue == null) { - newUrl = newUrl.replace(pattern, ''); +export function mergeUrlParams(params, url) { + let newUrl = Object.keys(params).reduce((acc, paramName) => { + const paramValue = params[paramName]; + const pattern = new RegExp(`\\b(${paramName}=).*?(&|$)`); + + if (paramValue === null) { + return acc.replace(pattern, ''); } else if (url.search(pattern) !== -1) { - newUrl = newUrl.replace(pattern, "$1" + paramValue + "$2"); - } else { - newUrl = "" + newUrl + (newUrl.indexOf('?') > 0 ? '&' : '?') + paramName + "=" + paramValue; + return acc.replace(pattern, `$1${paramValue}$2`); } - } + + return `${acc}${acc.indexOf('?') > 0 ? '&' : '?'}${paramName}=${paramValue}`; + }, decodeURIComponent(url)); + // Remove a trailing ampersand - lastChar = newUrl[newUrl.length - 1]; + const lastChar = newUrl[newUrl.length - 1]; + if (lastChar === '&') { newUrl = newUrl.slice(0, -1); } + return newUrl; -}; -// removes parameter query string from url. returns the modified url -w.gl.utils.removeParamQueryString = function(url, param) { - var urlVariables, variables; - url = decodeURIComponent(url); - urlVariables = url.split('&'); - return ((function() { - var j, len, results; - results = []; - for (j = 0, len = urlVariables.length; j < len; j += 1) { - variables = urlVariables[j]; - if (variables.indexOf(param) === -1) { - results.push(variables); - } - } - return results; - })()).join('&'); -}; -w.gl.utils.removeParams = (params) => { +} + +export function removeParamQueryString(url, param) { + const decodedUrl = decodeURIComponent(url); + const urlVariables = decodedUrl.split('&'); + + return urlVariables.filter(variable => variable.indexOf(param) === -1).join('&'); +} + +export function removeParams(params) { const url = document.createElement('a'); url.href = window.location.href; + params.forEach((param) => { - url.search = w.gl.utils.removeParamQueryString(url.search, param); + url.search = removeParamQueryString(url.search, param); }); + return url.href; -}; -w.gl.utils.getLocationHash = function(url) { - var hashIndex; - if (typeof url === 'undefined') { - // Note: We can't use window.location.hash here because it's - // not consistent across browsers - Firefox will pre-decode it - url = window.location.href; - } - hashIndex = url.indexOf('#'); - return hashIndex === -1 ? null : url.substring(hashIndex + 1); -}; +} -w.gl.utils.refreshCurrentPage = () => gl.utils.visitUrl(window.location.href); +export function getLocationHash(url = window.location.href) { + const hashIndex = url.indexOf('#'); + + return hashIndex === -1 ? null : url.substring(hashIndex + 1); +} -// eslint-disable-next-line import/prefer-default-export export function visitUrl(url, external = false) { if (external) { // Simulate `target="blank" rel="noopener noreferrer"` @@ -100,12 +76,10 @@ export function visitUrl(url, external = false) { } } +export function refreshCurrentPage() { + visitUrl(window.location.href); +} + export function redirectTo(url) { return window.location.assign(url); } - -window.gl = window.gl || {}; -window.gl.utils = { - ...(window.gl.utils || {}), - visitUrl, -}; diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index c993cc91de9..d26a9eafd7b 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -28,8 +28,8 @@ import './commit/image_file'; // lib/utils import { handleLocationHash } from './lib/utils/common_utils'; -import { localTimeAgo, renderTimeago, getLocationHash } from './lib/utils/datetime_utility'; -import './lib/utils/url_utility'; +import { localTimeAgo, renderTimeago } from './lib/utils/datetime_utility'; +import { getLocationHash, visitUrl } from './lib/utils/url_utility'; // behaviors import './behaviors/'; @@ -40,9 +40,6 @@ import './admin'; import './aside'; import loadAwardsHandler from './awards_handler'; import bp from './breakpoints'; -import './commits'; -import './compare'; -import './compare_autocomplete'; import './confirm_danger_modal'; import Flash, { removeFlashClickListener } from './flash'; import './gl_dropdown'; @@ -294,7 +291,7 @@ $(function () { const action = `${this.action}${link.search === '' ? '?' : '&'}`; event.preventDefault(); - gl.utils.visitUrl(`${action}${$(this).serialize()}`); + visitUrl(`${action}${$(this).serialize()}`); }); const flashContainer = document.querySelector('.flash-container'); diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 870b0e41a80..e8de17ae634 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -11,6 +11,7 @@ import { handleLocationHash, isMetaClick, } from './lib/utils/common_utils'; +import { getLocationHash } from './lib/utils/url_utility'; import initDiscussionTab from './image_diff/init_discussion_tab'; import Diff from './diff'; import { @@ -320,7 +321,7 @@ import { // Scroll any linked note into view // Similar to `toggler_behavior` in the discussion tab - const hash = gl.utils.getLocationHash(); + const hash = getLocationHash(); const anchor = hash && $container.find(`.note[id="${hash}"]`); if (anchor && anchor.length > 0) { const notesContent = anchor.closest('.notes_content'); diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index cbe24c0915b..8da723ced03 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -21,6 +21,8 @@ hasMetrics: convertPermissionToBoolean(metricsData.hasMetrics), documentationPath: metricsData.documentationPath, settingsPath: metricsData.settingsPath, + tagsPath: metricsData.tagsPath, + projectPath: metricsData.projectPath, metricsEndpoint: metricsData.additionalMetrics, deploymentEndpoint: metricsData.deploymentEndpoint, emptyGettingStartedSvgPath: metricsData.emptyGettingStartedSvgPath, @@ -112,6 +114,8 @@ :hover-data="hoverData" :update-aspect-ratio="updateAspectRatio" :deployment-data="store.deploymentData" + :project-path="projectPath" + :tags-path="tagsPath" /> </graph-group> </div> diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue index f8782fde927..cdae287658b 100644 --- a/app/assets/javascripts/monitoring/components/graph.vue +++ b/app/assets/javascripts/monitoring/components/graph.vue @@ -30,6 +30,14 @@ required: false, default: () => ({}), }, + projectPath: { + type: String, + required: true, + }, + tagsPath: { + type: String, + required: true, + }, }, mixins: [MonitoringMixin], @@ -251,6 +259,14 @@ :line-color="path.lineColor" :area-color="path.areaColor" /> + <rect + class="prometheus-graph-overlay" + :width="(graphWidth - 70)" + :height="(graphHeight - 100)" + transform="translate(-5, 20)" + ref="graphOverlay" + @mousemove="handleMouseOverGraph($event)"> + </rect> <graph-deployment :show-deploy-info="showDeployInfo" :deployment-data="reducedDeploymentData" @@ -267,14 +283,6 @@ :graph-height-offset="graphHeightOffset" :show-flag-content="showFlagContent" /> - <rect - class="prometheus-graph-overlay" - :width="(graphWidth - 70)" - :height="(graphHeight - 100)" - transform="translate(-5, 20)" - ref="graphOverlay" - @mousemove="handleMouseOverGraph($event)"> - </rect> </svg> </svg> </div> diff --git a/app/assets/javascripts/monitoring/components/graph/deployment.vue b/app/assets/javascripts/monitoring/components/graph/deployment.vue index e3b8be0c7fb..026e2fd0c49 100644 --- a/app/assets/javascripts/monitoring/components/graph/deployment.vue +++ b/app/assets/javascripts/monitoring/components/graph/deployment.vue @@ -1,5 +1,6 @@ <script> - import { dateFormat, timeFormat } from '../../utils/date_time_formatters'; + import { dateFormatWithName, timeFormat } from '../../utils/date_time_formatters'; + import Icon from '../../../vue_shared/components/icon.vue'; export default { props: { @@ -25,6 +26,10 @@ }, }, + components: { + Icon, + }, + computed: { calculatedHeight() { return this.graphHeight - this.graphHeightOffset; @@ -33,7 +38,7 @@ methods: { refText(d) { - return d.tag ? d.ref : d.sha.slice(0, 6); + return d.tag ? d.ref : d.sha.slice(0, 8); }, formatTime(deploymentTime) { @@ -41,7 +46,7 @@ }, formatDate(deploymentTime) { - return dateFormat(deploymentTime); + return dateFormatWithName(deploymentTime); }, nameDeploymentClass(deployment) { @@ -54,11 +59,19 @@ positionFlag(deployment) { let xPosition = 3; - if (deployment.xPos > (this.graphWidth - 200)) { - xPosition = -97; + if (deployment.xPos > (this.graphWidth - 225)) { + xPosition = -142; } return xPosition; }, + + svgContainerHeight(tag) { + let svgHeight = 80; + if (!tag) { + svgHeight -= 20; + } + return svgHeight; + }, }, }; </script> @@ -91,35 +104,75 @@ class="js-deploy-info-box" :x="positionFlag(deployment)" y="0" - width="92" - height="60"> + width="134" + :height="svgContainerHeight(deployment.tag)"> <rect class="rect-text-metric deploy-info-rect rect-metric" x="1" y="1" rx="2" - width="90" - height="58"> + width="132" + :height="svgContainerHeight(deployment.tag) - 2"> </rect> - <g - transform="translate(5, 2)"> - <text - class="deploy-info-text text-metric-bold"> - {{refText(deployment)}} - </text> - </g> - <text - class="deploy-info-text" - y="18" - transform="translate(5, 2)"> - {{formatDate(deployment.time)}} - </text> <text class="deploy-info-text text-metric-bold" - y="38" transform="translate(5, 2)"> - {{formatTime(deployment.time)}} + Deployed </text> + <!--The date info--> + <g transform="translate(5, 20)"> + <text class="deploy-info-text"> + {{formatDate(deployment.time)}} + </text> + <text + class="deploy-info-text text-metric-bold" + x="62"> + {{formatTime(deployment.time)}} + </text> + </g> + <line + class="divider-line" + x1="0" + y1="38" + x2="132" + :y2="38" + stroke="#000"> + </line> + <!--Commit information--> + <g transform="translate(5, 40)"> + <icon + name="commit" + :width="12" + :height="12" + :y="3"> + </icon> + <a :xlink:href="deployment.commitUrl"> + <text + class="deploy-info-text deploy-info-text-link" + transform="translate(20, 2)"> + {{refText(deployment)}} + </text> + </a> + </g> + <!--Tag information--> + <g + transform="translate(5, 55)" + v-if="deployment.tag"> + <icon + name="label" + :width="12" + :height="12" + :y="5"> + </icon> + <a :xlink:href="deployment.tagUrl"> + <text + class="deploy-info-text deploy-info-text-link" + transform="translate(20, 2)" + y="2"> + {{deployment.tag}} + </text> + </a> + </g> </svg> </g> <svg diff --git a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js index 31f38aca5d6..cbca14ede02 100644 --- a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js +++ b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js @@ -33,7 +33,9 @@ const mixins = { id: deployment.id, time, sha: deployment.sha, + commitUrl: `${this.projectPath}/commit/${deployment.sha}`, tag: deployment.tag, + tagUrl: `${this.tagsPath}/${deployment.tag}`, ref: deployment.ref.name, xPos, showDeploymentFlag: false, diff --git a/app/assets/javascripts/monitoring/utils/date_time_formatters.js b/app/assets/javascripts/monitoring/utils/date_time_formatters.js index c4c6b1ac1f5..ad07a8465e2 100644 --- a/app/assets/javascripts/monitoring/utils/date_time_formatters.js +++ b/app/assets/javascripts/monitoring/utils/date_time_formatters.js @@ -1,6 +1,7 @@ import d3 from 'd3'; export const dateFormat = d3.time.format('%b %-d, %Y'); +export const dateFormatWithName = d3.time.format('%a, %b %-d'); export const timeFormat = d3.time.format('%-I:%M%p'); export const bisectDate = d3.bisector(d => d.time).left; diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js index 1d496c64e53..aa377327107 100644 --- a/app/assets/javascripts/namespace_select.js +++ b/app/assets/javascripts/namespace_select.js @@ -1,6 +1,6 @@ /* eslint-disable func-names, space-before-function-paren, no-var, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, max-len */ import Api from './api'; -import './lib/utils/url_utility'; +import { mergeUrlParams } from './lib/utils/url_utility'; export default class NamespaceSelect { constructor(opts) { @@ -50,7 +50,7 @@ export default class NamespaceSelect { } }, url(namespace) { - return gl.utils.mergeUrlParams({ [fieldName]: namespace.id }, window.location.href); + return mergeUrlParams({ [fieldName]: namespace.id }, window.location.href); }, }); } diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index f487928cb84..042fe44e1c6 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -16,6 +16,7 @@ import Autosize from 'autosize'; import 'vendor/jquery.caret'; // required by jquery.atwho import 'vendor/jquery.atwho'; import AjaxCache from '~/lib/utils/ajax_cache'; +import { getLocationHash } from './lib/utils/url_utility'; import Flash from './flash'; import CommentTypeToggle from './comment_type_toggle'; import GLForm from './gl_form'; @@ -331,7 +332,7 @@ export default class Notes { } static updateNoteTargetSelector($note) { - const hash = gl.utils.getLocationHash(); + const hash = getLocationHash(); // Needs to be an explicit true/false for the jQuery `toggleClass(force)` const addTargetClass = Boolean(hash && $note.filter(`#${hash}`).length > 0); $note.toggleClass('target', addTargetClass); diff --git a/app/assets/javascripts/notes/components/issue_note.vue b/app/assets/javascripts/notes/components/issue_note.vue index 8c81c5d6df3..3ceb961f58e 100644 --- a/app/assets/javascripts/notes/components/issue_note.vue +++ b/app/assets/javascripts/notes/components/issue_note.vue @@ -1,5 +1,6 @@ <script> import { mapGetters, mapActions } from 'vuex'; + import { escape } from 'underscore'; import Flash from '../../flash'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import noteHeader from './note_header.vue'; @@ -85,7 +86,7 @@ }; this.isRequesting = true; this.oldContent = this.note.note_html; - this.note.note_html = noteText; + this.note.note_html = escape(noteText); this.updateNote(data) .then(() => { diff --git a/app/assets/javascripts/notes/components/issue_notes_app.vue b/app/assets/javascripts/notes/components/issue_notes_app.vue index 4cfcffa2391..e4d01285d39 100644 --- a/app/assets/javascripts/notes/components/issue_notes_app.vue +++ b/app/assets/javascripts/notes/components/issue_notes_app.vue @@ -1,5 +1,6 @@ <script> import { mapGetters, mapActions } from 'vuex'; + import { getLocationHash } from '../../lib/utils/url_utility'; import Flash from '../../flash'; import store from '../stores/'; import * as constants from '../constants'; @@ -95,7 +96,7 @@ this.poll(); }, checkLocationHash() { - const hash = gl.utils.getLocationHash(); + const hash = getLocationHash(); const element = document.getElementById(hash); if (hash && element) { diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js index 8d74c5de5cf..a94163a5f87 100644 --- a/app/assets/javascripts/notes/index.js +++ b/app/assets/javascripts/notes/index.js @@ -8,10 +8,18 @@ document.addEventListener('DOMContentLoaded', () => new Vue({ }, data() { const notesDataset = document.getElementById('js-vue-notes').dataset; + const parsedUserData = JSON.parse(notesDataset.currentUserData); + const currentUserData = parsedUserData ? { + id: parsedUserData.id, + name: parsedUserData.name, + username: parsedUserData.username, + avatar_url: parsedUserData.avatar_path || parsedUserData.avatar_url, + path: parsedUserData.path, + } : {}; return { noteableData: JSON.parse(notesDataset.noteableData), - currentUserData: JSON.parse(notesDataset.currentUserData), + currentUserData, notesData: { lastFetchedAt: notesDataset.lastFetchedAt, discussionsPath: notesDataset.discussionsPath, diff --git a/app/assets/javascripts/pager.js b/app/assets/javascripts/pager.js index e3fc1e2fc2f..6792b984cc5 100644 --- a/app/assets/javascripts/pager.js +++ b/app/assets/javascripts/pager.js @@ -1,5 +1,5 @@ import { getParameterByName } from '~/lib/utils/common_utils'; -import '~/lib/utils/url_utility'; +import { removeParams } from './lib/utils/url_utility'; (() => { const ENDLESS_SCROLL_BOTTOM_PX = 400; @@ -7,7 +7,7 @@ import '~/lib/utils/url_utility'; const Pager = { init(limit = 0, preload = false, disable = false, prepareData = $.noop, callback = $.noop) { - this.url = $('.content_list').data('href') || gl.utils.removeParams(['limit', 'offset']); + this.url = $('.content_list').data('href') || removeParams(['limit', 'offset']); this.limit = limit; this.offset = parseInt(getParameterByName('offset'), 10) || this.limit; this.disable = disable; diff --git a/app/assets/javascripts/performance_bar.js b/app/assets/javascripts/performance_bar.js index 9bbdf7f513c..0562a681c4b 100644 --- a/app/assets/javascripts/performance_bar.js +++ b/app/assets/javascripts/performance_bar.js @@ -1,5 +1,6 @@ import 'vendor/peek'; import 'vendor/peek.performance_bar'; +import { getParameterValues } from './lib/utils/url_utility'; export default class PerformanceBar { constructor(opts) { @@ -39,7 +40,7 @@ export default class PerformanceBar { } handleLineProfileLink(e) { - const lineProfilerParameter = gl.utils.getParameterValues('lineprofiler'); + const lineProfilerParameter = getParameterValues('lineprofiler'); const lineProfilerParameterRegex = new RegExp(`lineprofiler=${lineProfilerParameter[0]}`); const shouldToggleModal = lineProfilerParameter.length > 0 && lineProfilerParameterRegex.test(e.currentTarget.href); diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js index 3131e71d9d6..d4f26b81f30 100644 --- a/app/assets/javascripts/project.js +++ b/app/assets/javascripts/project.js @@ -1,6 +1,7 @@ /* eslint-disable func-names, space-before-function-paren, no-var, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */ import Cookies from 'js-cookie'; +import { visitUrl } from './lib/utils/url_utility'; import projectSelect from './project_select'; export default class Project { @@ -122,7 +123,7 @@ export default class Project { var action = $form.attr('action'); var divider = action.indexOf('?') === -1 ? '?' : '&'; if (shouldVisit) { - gl.utils.visitUrl(`${action}${divider}${$form.serialize()}`); + visitUrl(`${action}${divider}${$form.serialize()}`); } } }, diff --git a/app/assets/javascripts/projects/ci_cd_settings_bundle.js b/app/assets/javascripts/projects/ci_cd_settings_bundle.js deleted file mode 100644 index 90e418f6771..00000000000 --- a/app/assets/javascripts/projects/ci_cd_settings_bundle.js +++ /dev/null @@ -1,19 +0,0 @@ -function updateAutoDevopsRadios(radioWrappers) { - radioWrappers.forEach((radioWrapper) => { - const radio = radioWrapper.querySelector('.js-auto-devops-enable-radio'); - const runPipelineCheckboxWrapper = radioWrapper.querySelector('.js-run-auto-devops-pipeline-checkbox-wrapper'); - const runPipelineCheckbox = radioWrapper.querySelector('.js-run-auto-devops-pipeline-checkbox'); - - if (runPipelineCheckbox) { - runPipelineCheckbox.checked = radio.checked; - runPipelineCheckboxWrapper.classList.toggle('hide', !radio.checked); - } - }); -} - -export default function initCiCdSettings() { - const radioWrappers = document.querySelectorAll('.js-auto-devops-enable-radio-wrapper'); - radioWrappers.forEach(radioWrapper => - radioWrapper.addEventListener('change', () => updateAutoDevopsRadios(radioWrappers)), - ); -} diff --git a/app/assets/javascripts/projects/project_import_gitlab_project.js b/app/assets/javascripts/projects/project_import_gitlab_project.js index c34927499fc..cec6f0dd5a3 100644 --- a/app/assets/javascripts/projects/project_import_gitlab_project.js +++ b/app/assets/javascripts/projects/project_import_gitlab_project.js @@ -1,7 +1,7 @@ -import '../lib/utils/url_utility'; +import { getParameterValues } from '../lib/utils/url_utility'; const bindEvents = () => { - const path = gl.utils.getParameterValues('path')[0]; + const path = getParameterValues('path')[0]; // get the path url and append it in the inputS $('.js-path-name').val(path); diff --git a/app/assets/javascripts/repo/stores/actions.js b/app/assets/javascripts/repo/stores/actions.js index 120ce96f44d..af5dcf054ef 100644 --- a/app/assets/javascripts/repo/stores/actions.js +++ b/app/assets/javascripts/repo/stores/actions.js @@ -1,9 +1,10 @@ import Vue from 'vue'; +import { visitUrl } from '../../lib/utils/url_utility'; import flash from '../../flash'; import service from '../services'; import * as types from './mutation_types'; -export const redirectToUrl = (_, url) => gl.utils.visitUrl(url); +export const redirectToUrl = (_, url) => visitUrl(url); export const setInitialData = ({ commit }, data) => commit(types.SET_INITIAL_DATA, data); diff --git a/app/assets/javascripts/repo/stores/actions/tree.js b/app/assets/javascripts/repo/stores/actions/tree.js index aa830e946a2..7c251e26bed 100644 --- a/app/assets/javascripts/repo/stores/actions/tree.js +++ b/app/assets/javascripts/repo/stores/actions/tree.js @@ -1,3 +1,4 @@ +import { visitUrl } from '../../../lib/utils/url_utility'; import { normalizeHeaders } from '../../../lib/utils/common_utils'; import flash from '../../../flash'; import service from '../../services'; @@ -73,7 +74,7 @@ export const clickedTreeRow = ({ commit, dispatch }, row) => { } else if (row.type === 'submodule') { commit(types.TOGGLE_LOADING, row); - gl.utils.visitUrl(row.url); + visitUrl(row.url); } else if (row.type === 'blob' && row.opened) { dispatch('setFileActive', row); } else { diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js index ebe7a99ffae..130730b1700 100644 --- a/app/assets/javascripts/shortcuts.js +++ b/app/assets/javascripts/shortcuts.js @@ -1,5 +1,6 @@ import Cookies from 'js-cookie'; import Mousetrap from 'mousetrap'; +import { refreshCurrentPage, visitUrl } from './lib/utils/url_utility'; import findAndFollowLink from './shortcuts_dashboard_navigation'; const defaultStopCallback = Mousetrap.stopCallback; @@ -38,7 +39,7 @@ export default class Shortcuts { if (typeof findFileURL !== 'undefined' && findFileURL !== null) { Mousetrap.bind('t', () => { - gl.utils.visitUrl(findFileURL); + visitUrl(findFileURL); }); } @@ -62,7 +63,7 @@ export default class Shortcuts { } else { Cookies.set(performanceBarCookieName, 'true', { path: '/' }); } - gl.utils.refreshCurrentPage(); + refreshCurrentPage(); } static toggleMarkdownPreview(e) { diff --git a/app/assets/javascripts/shortcuts_blob.js b/app/assets/javascripts/shortcuts_blob.js index fbc57bb4304..cf309be4f6f 100644 --- a/app/assets/javascripts/shortcuts_blob.js +++ b/app/assets/javascripts/shortcuts_blob.js @@ -1,5 +1,5 @@ /* global Mousetrap */ - +import { getLocationHash, visitUrl } from './lib/utils/url_utility'; import Shortcuts from './shortcuts'; const defaults = { @@ -18,9 +18,9 @@ export default class ShortcutsBlob extends Shortcuts { moveToFilePermalink() { if (this.options.fileBlobPermalinkUrl) { - const hash = gl.utils.getLocationHash(); + const hash = getLocationHash(); const hashUrlString = hash ? `#${hash}` : ''; - gl.utils.visitUrl(`${this.options.fileBlobPermalinkUrl}${hashUrlString}`); + visitUrl(`${this.options.fileBlobPermalinkUrl}${hashUrlString}`); } } } diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js index d4c07a188b3..ed7ab09be06 100644 --- a/app/assets/javascripts/sidebar/sidebar_mediator.js +++ b/app/assets/javascripts/sidebar/sidebar_mediator.js @@ -1,3 +1,4 @@ +import { visitUrl } from '../lib/utils/url_utility'; import Flash from '../flash'; import Service from './services/sidebar_service'; import Store from './stores/sidebar_store'; @@ -81,7 +82,7 @@ export default class SidebarMediator { .then(response => response.json()) .then((data) => { if (location.pathname !== data.web_url) { - gl.utils.visitUrl(data.web_url); + visitUrl(data.web_url); } }); } diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js index 2fffe09c74e..748caecf153 100644 --- a/app/assets/javascripts/todos.js +++ b/app/assets/javascripts/todos.js @@ -1,5 +1,5 @@ /* eslint-disable class-methods-use-this, no-unneeded-ternary, quote-props */ - +import { visitUrl } from './lib/utils/url_utility'; import UsersSelect from './users_select'; import { isMetaClick } from './lib/utils/common_utils'; @@ -150,7 +150,7 @@ export default class Todos { window.open(todoLink, windowTarget); } else { - gl.utils.visitUrl(todoLink); + visitUrl(todoLink); } } } diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js index 7777ed1c3dc..1a0b2c0415b 100644 --- a/app/assets/javascripts/tree.js +++ b/app/assets/javascripts/tree.js @@ -1,4 +1,5 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, class-methods-use-this */ +import { visitUrl } from './lib/utils/url_utility'; export default class TreeView { constructor() { @@ -14,7 +15,7 @@ export default class TreeView { e.preventDefault(); return window.open(path, '_blank'); } else { - return gl.utils.visitUrl(path); + return visitUrl(path); } } }); @@ -56,7 +57,7 @@ export default class TreeView { } else if (e.which === 13) { path = $('.tree-item.selected .tree-item-file-name a').attr('href'); if (path) { - return gl.utils.visitUrl(path); + return visitUrl(path); } } }); 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 00af57d4027..ee1a45cc754 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,4 +1,5 @@ import { getTimeago } from '~/lib/utils/datetime_utility'; +import { visitUrl } from '../../lib/utils/url_utility'; import Flash from '../../flash'; import MemoryUsage from './mr_widget_memory_usage'; import StatusIcon from './mr_widget_status_icon'; @@ -36,7 +37,7 @@ export default { .then(res => res.json()) .then((res) => { if (res.redirect_url) { - gl.utils.visitUrl(res.redirect_url); + visitUrl(res.redirect_url); } }) .catch(() => { 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 1274db2c4c8..9cb3edead86 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,3 +1,4 @@ +import Project from '~/project'; import SmartInterval from '~/smart_interval'; import Flash from '../flash'; import { @@ -140,6 +141,7 @@ export default { const el = document.createElement('div'); el.innerHTML = res.body; document.body.appendChild(el); + Project.initRefSwitcher(); } }) .catch(() => { diff --git a/app/assets/javascripts/vue_shared/components/icon.vue b/app/assets/javascripts/vue_shared/components/icon.vue index 4216660da8c..365229ea274 100644 --- a/app/assets/javascripts/vue_shared/components/icon.vue +++ b/app/assets/javascripts/vue_shared/components/icon.vue @@ -36,6 +36,30 @@ required: false, default: '', }, + + width: { + type: Number, + required: false, + default: null, + }, + + height: { + type: Number, + required: false, + default: null, + }, + + y: { + type: Number, + required: false, + default: null, + }, + + x: { + type: Number, + required: false, + default: null, + }, }, computed: { @@ -51,7 +75,11 @@ <template> <svg - :class="[iconSizeClass, cssClasses]"> + :class="[iconSizeClass, cssClasses]" + :width="width" + :height="height" + :x="x" + :y="y"> <use v-bind="{'xlink:href':spriteHref}"/> </svg> diff --git a/app/assets/javascripts/vue_shared/components/popup_dialog.vue b/app/assets/javascripts/vue_shared/components/popup_dialog.vue index 47efee64c6e..6d15bbd84ba 100644 --- a/app/assets/javascripts/vue_shared/components/popup_dialog.vue +++ b/app/assets/javascripts/vue_shared/components/popup_dialog.vue @@ -38,7 +38,8 @@ export default { }, primaryButtonLabel: { type: String, - required: true, + required: false, + default: '', }, submitDisabled: { type: Boolean, @@ -113,8 +114,9 @@ export default { {{ closeButtonLabel }} </button> <button + v-if="primaryButtonLabel" type="button" - class="btn pull-right" + class="btn pull-right js-primary-button" :disabled="submitDisabled" :class="btnKindClass" @click="emitSubmit(true)"> diff --git a/app/assets/javascripts/vue_shared/components/recaptcha_dialog.vue b/app/assets/javascripts/vue_shared/components/recaptcha_dialog.vue new file mode 100644 index 00000000000..3ec50f14eb4 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/recaptcha_dialog.vue @@ -0,0 +1,85 @@ +<script> +import PopupDialog from './popup_dialog.vue'; + +export default { + name: 'recaptcha-dialog', + + props: { + html: { + type: String, + required: false, + default: '', + }, + }, + + data() { + return { + script: {}, + scriptSrc: 'https://www.google.com/recaptcha/api.js', + }; + }, + + components: { + PopupDialog, + }, + + methods: { + appendRecaptchaScript() { + this.removeRecaptchaScript(); + + const script = document.createElement('script'); + script.src = this.scriptSrc; + script.classList.add('js-recaptcha-script'); + script.async = true; + script.defer = true; + + this.script = script; + + document.body.appendChild(script); + }, + + removeRecaptchaScript() { + if (this.script instanceof Element) this.script.remove(); + }, + + close() { + this.removeRecaptchaScript(); + this.$emit('close'); + }, + + submit() { + this.$el.querySelector('form').submit(); + }, + }, + + watch: { + html() { + this.appendRecaptchaScript(); + }, + }, + + mounted() { + window.recaptchaDialogCallback = this.submit.bind(this); + }, +}; +</script> + +<template> +<popup-dialog + kind="warning" + class="recaptcha-dialog js-recaptcha-dialog" + :hide-footer="true" + :title="__('Please solve the reCAPTCHA')" + @toggle="close" +> + <div slot="body"> + <p> + {{__('We want to be sure it is you, please confirm you are not a robot.')}} + </p> + <div + ref="recaptcha" + v-html="html" + ></div> + </div> +</popup-dialog> +</template> diff --git a/app/assets/javascripts/vue_shared/mixins/recaptcha_dialog_implementor.js b/app/assets/javascripts/vue_shared/mixins/recaptcha_dialog_implementor.js new file mode 100644 index 00000000000..ef70f9432e3 --- /dev/null +++ b/app/assets/javascripts/vue_shared/mixins/recaptcha_dialog_implementor.js @@ -0,0 +1,36 @@ +import RecaptchaDialog from '../components/recaptcha_dialog.vue'; + +export default { + data() { + return { + showRecaptcha: false, + recaptchaHTML: '', + }; + }, + + components: { + RecaptchaDialog, + }, + + methods: { + openRecaptcha() { + this.showRecaptcha = true; + }, + + closeRecaptcha() { + this.showRecaptcha = false; + }, + + checkForSpam(data) { + if (!data.recaptcha_html) return data; + + this.recaptchaHTML = data.recaptcha_html; + + const spamError = new Error(data.error_message); + spamError.name = 'SpamError'; + spamError.message = 'SpamError'; + + throw spamError; + }, + }, +}; |