diff options
author | Filipa Lacerda <filipa@gitlab.com> | 2016-12-22 10:15:49 +0000 |
---|---|---|
committer | Filipa Lacerda <filipa@gitlab.com> | 2016-12-22 10:15:49 +0000 |
commit | 7fc64dd18d9b2b6e3a2a01dab0007f7dd25c37ed (patch) | |
tree | 428602d5265cd981a2e33ace8aed6fc9594dd37c /app/assets/javascripts | |
parent | fd3ab00cf90ddf081c61fb701721ca9180378bba (diff) | |
parent | 6d9c1d3efce00da95832feaaf36227bcbffecadf (diff) | |
download | gitlab-ce-pipeline-ui-updates.tar.gz |
Merge branch 'master' into pipeline-ui-updatespipeline-ui-updates
* master: (259 commits)
Exclude non existent repository storages.
fixed minor animation glitch in mini pipeline graph animation
Update Bitbucket callback URL documentation
Update build step for KaTeX.
Add KaTeX fonts to assets paths and precompile
Replace url('...') to url(font-path('...'))
Rname katex.css to katex.scss
Revert conflicting EE changes
Added Autodeploy script for OpenShift
Whitelist next project names: notes, services
Put back progress bar CSS
Remove unneeded bundle refs.
Adds entry to changelog
Reduce MR widget title by one pixel
Use same font size for all items in issue title
Adds background color for disabled state to merge when succeeds dropdown
Filter protocol-relative URLs in ExternalLinkFilter. Fixes issue #22742.
Move javascript for widget check to ci_bundle.
Introduce "Set up autodeploy" button to help configure GitLab CI for deployment
Whitelist next project names: help, ci, admin, search
...
Diffstat (limited to 'app/assets/javascripts')
18 files changed, 412 insertions, 88 deletions
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 824febe3fd3..5e449170cd3 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -4,6 +4,7 @@ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var AUTO_SCROLL_OFFSET = 75; this.Build = (function() { Build.interval = null; @@ -19,6 +20,17 @@ this.buildStage = options.buildStage; this.updateDropdown = bind(this.updateDropdown, this); this.$document = $(document); + this.$body = $('body'); + this.$buildTrace = $('#build-trace'); + this.$autoScrollContainer = $('.autoscroll-container'); + this.$autoScrollStatus = $('#autoscroll-status'); + this.$autoScrollStatusText = this.$autoScrollStatus.find('.status-text'); + this.$upBuildTrace = $('#up-build-trace'); + this.$downBuildTrace = $('#down-build-trace'); + this.$scrollTopBtn = $('#scroll-top'); + this.$scrollBottomBtn = $('#scroll-bottom'); + this.$buildRefreshAnimation = $('.js-build-refresh'); + clearInterval(Build.interval); // Init breakpoint checker this.bp = Breakpoints.get(); @@ -32,6 +44,7 @@ this.$document.off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.sidebarOnClick.bind(this)); this.$document.off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown); + this.$document.on('scroll', this.initScrollMonitor.bind(this)); $(window).off('resize.build').on('resize.build', this.sidebarOnResize.bind(this)); $('a', this.$buildScroll).off('click.stepTrace').on('click.stepTrace', this.stepTrace); this.updateArtifactRemoveDate(); @@ -40,18 +53,6 @@ this.initScrollButtonAffix(); } if (this.buildStatus === "running" || this.buildStatus === "pending") { - // Bind autoscroll button to follow build output - $('#autoscroll-button').on('click', function() { - var state; - state = $(this).data("state"); - if ("enabled" === state) { - $(this).data("state", "disabled"); - return $(this).text("Enable autoscroll"); - } else { - $(this).data("state", "enabled"); - return $(this).text("Disable autoscroll"); - } - }); Build.interval = setInterval((function(_this) { // Check for new build output if user still watching build page // Only valid for runnig build when output changes during time @@ -91,9 +92,10 @@ success: function(buildData) { $('.js-build-output').html(buildData.trace_html); if (removeRefreshStatuses.indexOf(buildData.status) >= 0) { - return $('.js-build-refresh').remove(); + this.initScrollMonitor(); + return this.$buildRefreshAnimation.remove(); } - } + }.bind(this) }); }; @@ -122,22 +124,95 @@ }; Build.prototype.checkAutoscroll = function() { - if ("enabled" === $("#autoscroll-button").data("state")) { - return $("html,body").scrollTop($("#build-trace").height()); + if (this.$autoScrollStatus.data("state") === "enabled") { + return $("html,body").scrollTop(this.$buildTrace.height()); + } + + // Handle a situation where user started new build + // but never scrolled a page + if (!this.$scrollTopBtn.is(':visible') && + !this.$scrollBottomBtn.is(':visible') && + !gl.utils.isInViewport(this.$downBuildTrace.get(0))) { + this.$scrollBottomBtn.show(); } }; Build.prototype.initScrollButtonAffix = function() { - var $body, $buildTrace; - $body = $('body'); - $buildTrace = $('#build-trace'); - return this.$buildScroll.affix({ - offset: { - bottom: function() { - return $body.outerHeight() - ($buildTrace.outerHeight() + $buildTrace.offset().top); - } + // Hide everything initially + this.$scrollTopBtn.hide(); + this.$scrollBottomBtn.hide(); + this.$autoScrollContainer.hide(); + } + + // Page scroll listener to detect if user has scrolling page + // and handle following cases + // 1) User is at Top of Build Log; + // - Hide Top Arrow button + // - Show Bottom Arrow button + // - Disable Autoscroll and hide indicator (when build is running) + // 2) User is at Bottom of Build Log; + // - Show Top Arrow button + // - Hide Bottom Arrow button + // - Enable Autoscroll and show indicator (when build is running) + // 3) User is somewhere in middle of Build Log; + // - Show Top Arrow button + // - Show Bottom Arrow button + // - Disable Autoscroll and hide indicator (when build is running) + Build.prototype.initScrollMonitor = function() { + if (!gl.utils.isInViewport(this.$upBuildTrace.get(0)) && !gl.utils.isInViewport(this.$downBuildTrace.get(0))) { + // User is somewhere in middle of Build Log + + this.$scrollTopBtn.show(); + + if (this.buildStatus === 'success' || this.buildStatus === 'failed') { // Check if Build is completed + this.$scrollBottomBtn.show(); + } else if (this.$buildRefreshAnimation.is(':visible') && !gl.utils.isInViewport(this.$buildRefreshAnimation.get(0))) { + this.$scrollBottomBtn.show(); + } else { + this.$scrollBottomBtn.hide(); } - }); + + // Hide Autoscroll Status Indicator + if (this.$scrollBottomBtn.is(':visible')) { + this.$autoScrollContainer.hide(); + this.$autoScrollStatusText.removeClass('animate'); + } else { + this.$autoScrollContainer.css({ top: this.$body.outerHeight() - AUTO_SCROLL_OFFSET }).show(); + this.$autoScrollStatusText.addClass('animate'); + } + } else if (gl.utils.isInViewport(this.$upBuildTrace.get(0)) && !gl.utils.isInViewport(this.$downBuildTrace.get(0))) { + // User is at Top of Build Log + + this.$scrollTopBtn.hide(); + this.$scrollBottomBtn.show(); + + this.$autoScrollContainer.hide(); + this.$autoScrollStatusText.removeClass('animate'); + } else if ((!gl.utils.isInViewport(this.$upBuildTrace.get(0)) && gl.utils.isInViewport(this.$downBuildTrace.get(0))) || + (this.$buildRefreshAnimation.is(':visible') && gl.utils.isInViewport(this.$buildRefreshAnimation.get(0)))) { + // User is at Bottom of Build Log + + this.$scrollTopBtn.show(); + this.$scrollBottomBtn.hide(); + + // Show and Reposition Autoscroll Status Indicator + this.$autoScrollContainer.css({ top: this.$body.outerHeight() - AUTO_SCROLL_OFFSET }).show(); + this.$autoScrollStatusText.addClass('animate'); + } else if (gl.utils.isInViewport(this.$upBuildTrace.get(0)) && gl.utils.isInViewport(this.$downBuildTrace.get(0))) { + // Build Log height is small + + this.$scrollTopBtn.hide(); + this.$scrollBottomBtn.hide(); + + // Hide Autoscroll Status Indicator + this.$autoScrollContainer.hide(); + this.$autoScrollStatusText.removeClass('animate'); + } + + if (this.buildStatus === "running" || this.buildStatus === "pending") { + // Check if Refresh Animation is in Viewport and enable Autoscroll, disable otherwise. + this.$autoScrollStatus.data("state", gl.utils.isInViewport(this.$buildRefreshAnimation.get(0)) ? 'enabled' : 'disabled'); + } }; Build.prototype.shouldHideSidebarForViewport = function() { diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index 1e259a16f06..5245c5aa494 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -138,9 +138,13 @@ new MergedButtons(); break; case 'projects:merge_requests:commits': - case 'projects:merge_requests:builds': new MergedButtons(); break; + case 'projects:merge_requests:pipelines': + new gl.MiniPipelineGraph({ + container: '.js-pipeline-table', + }); + break; case "projects:merge_requests:diffs": new gl.Diff(); new ZenMode(); @@ -158,8 +162,10 @@ new ZenMode(); shortcut_handler = new ShortcutsNavigation(); break; - case 'projects:commit:builds': - new gl.Pipelines(); + case 'projects:commit:pipelines': + new gl.MiniPipelineGraph({ + container: '.js-pipeline-table', + }); break; case 'projects:commits:show': case 'projects:activity': @@ -172,6 +178,11 @@ new TreeView(); } break; + case 'projects:pipelines:index': + new gl.MiniPipelineGraph({ + container: '.js-pipeline-table', + }); + break; case 'projects:pipelines:builds': case 'projects:pipelines:show': const { controllerAction } = document.querySelector('.js-pipeline-container').dataset; diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index 88c3d257cea..facd653fd72 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -18,7 +18,7 @@ * The environments array is a recursive tree structure and we need to filter * both root level environments and children environments. * - * In order to acomplish that, both `filterState` and `filterEnvironmnetsByState` + * In order to acomplish that, both `filterState` and `filterEnvironmentsByState` * functions work together. * The first one works as the filter that verifies if the given environment matches * the given state. @@ -34,9 +34,9 @@ * @param {Array} array * @return {Array} */ - const filterEnvironmnetsByState = (fn, arr) => arr.map((item) => { + const filterEnvironmentsByState = (fn, arr) => arr.map((item) => { if (item.children) { - const filteredChildren = filterEnvironmnetsByState(fn, item.children).filter(Boolean); + const filteredChildren = filterEnvironmentsByState(fn, item.children).filter(Boolean); if (filteredChildren.length) { item.children = filteredChildren; return item; @@ -76,12 +76,13 @@ helpPagePath: environmentsData.helpPagePath, commitIconSvg: environmentsData.commitIconSvg, playIconSvg: environmentsData.playIconSvg, + terminalIconSvg: environmentsData.terminalIconSvg, }; }, computed: { filteredEnvironments() { - return filterEnvironmnetsByState(filterState(this.visibility), this.state.environments); + return filterEnvironmentsByState(filterState(this.visibility), this.state.environments); }, scope() { @@ -102,7 +103,7 @@ }, /** - * Fetches all the environmnets and stores them. + * Fetches all the environments and stores them. * Toggles loading property. */ created() { @@ -230,6 +231,7 @@ :can-create-deployment="canCreateDeploymentParsed" :can-read-environment="canReadEnvironmentParsed" :play-icon-svg="playIconSvg" + :terminal-icon-svg="terminalIconSvg" :commit-icon-svg="commitIconSvg"></tr> <tr v-if="model.isOpen && model.children && model.children.length > 0" @@ -240,6 +242,7 @@ :can-create-deployment="canCreateDeploymentParsed" :can-read-environment="canReadEnvironmentParsed" :play-icon-svg="playIconSvg" + :terminal-icon-svg="terminalIconSvg" :commit-icon-svg="commitIconSvg"> </tr> diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index 4674d5202e6..b26a40aa268 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -8,6 +8,7 @@ /*= require ./environment_external_url */ /*= require ./environment_stop */ /*= require ./environment_rollback */ +/*= require ./environment_terminal_button */ (() => { /** @@ -33,6 +34,7 @@ 'external-url-component': window.gl.environmentsList.ExternalUrlComponent, 'stop-component': window.gl.environmentsList.StopComponent, 'rollback-component': window.gl.environmentsList.RollbackComponent, + 'terminal-button-component': window.gl.environmentsList.TerminalButtonComponent, }, props: { @@ -68,6 +70,12 @@ type: String, required: false, }, + + terminalIconSvg: { + type: String, + required: false, + }, + }, data() { @@ -506,6 +514,14 @@ </stop-component> </div> + <div v-if="model.terminal_path" + class="inline js-terminal-button-container"> + <terminal-button-component + :terminal-icon-svg="terminalIconSvg" + :terminal-path="model.terminal_path"> + </terminal-button-component> + </div> + <div v-if="canRetry && canCreateDeployment" class="inline js-rollback-component-container"> <rollback-component diff --git a/app/assets/javascripts/environments/components/environment_terminal_button.js.es6 b/app/assets/javascripts/environments/components/environment_terminal_button.js.es6 new file mode 100644 index 00000000000..25e6ac7f3c9 --- /dev/null +++ b/app/assets/javascripts/environments/components/environment_terminal_button.js.es6 @@ -0,0 +1,27 @@ +/*= require vue */ +/* global Vue */ + +(() => { + window.gl = window.gl || {}; + window.gl.environmentsList = window.gl.environmentsList || {}; + + window.gl.environmentsList.TerminalButtonComponent = Vue.component('terminal-button-component', { + props: { + terminalPath: { + type: String, + default: '', + }, + terminalIconSvg: { + type: String, + default: '', + }, + }, + + template: ` + <a class="btn terminal-button" + :href="terminalPath"> + <span class="js-terminal-icon-container" v-html="terminalIconSvg"></span> + </a> + `, + }); +})(); diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6 index 17d03c87bf5..cbd8ac4eddd 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.es6 +++ b/app/assets/javascripts/gfm_auto_complete.js.es6 @@ -112,7 +112,6 @@ return value.path != null ? this.Emoji.template : this.Loading.template; }.bind(this), insertTpl: ':${name}:', - startWithSpace: false, skipSpecialCharacterTest: true, data: this.defaultLoadingData, callbacks: { @@ -129,7 +128,6 @@ }.bind(this), insertTpl: '${atwho-at}${username}', searchKey: 'search', - startWithSpace: false, alwaysHighlightFirst: true, skipSpecialCharacterTest: true, data: this.defaultLoadingData, @@ -172,7 +170,6 @@ }.bind(this), data: this.defaultLoadingData, insertTpl: '${atwho-at}${id}', - startWithSpace: false, callbacks: { sorter: this.DefaultOptions.sorter, filter: this.DefaultOptions.filter, @@ -200,7 +197,6 @@ displayTpl: function(value) { return value.title != null ? this.Milestones.template : this.Loading.template; }.bind(this), - startWithSpace: false, data: this.defaultLoadingData, callbacks: { matcher: this.DefaultOptions.matcher, @@ -225,7 +221,6 @@ at: '!', alias: 'mergerequests', searchKey: 'search', - startWithSpace: false, displayTpl: function(value) { return value.title != null ? this.Issues.template : this.Loading.template; }.bind(this), @@ -259,7 +254,6 @@ return this.isLoading(value) ? this.Loading.template : this.Labels.template; }.bind(this), insertTpl: '${atwho-at}${title}', - startWithSpace: false, callbacks: { matcher: this.DefaultOptions.matcher, sorter: this.DefaultOptions.sorter, @@ -379,14 +373,7 @@ togglePreventSelection(isPrevented = !!this.setting.tabSelectsMatch) { this.setting.tabSelectsMatch = !isPrevented; this.setting.spaceSelectsMatch = !isPrevented; - const eventListenerAction = `${isPrevented ? 'add' : 'remove'}EventListener`; - this.$inputor[0][eventListenerAction]('keydown', gl.GfmAutoComplete.preventSpaceTabEnter); }, - preventSpaceTabEnter(e) { - const key = e.which || e.keyCode; - const preventables = [9, 13, 32]; - if (preventables.indexOf(key) > -1) e.preventDefault(); - } }; }).call(this); diff --git a/app/assets/javascripts/issuable.js.es6 b/app/assets/javascripts/issuable.js.es6 index 1c10a7445bb..9c3c96c20ed 100644 --- a/app/assets/javascripts/issuable.js.es6 +++ b/app/assets/javascripts/issuable.js.es6 @@ -1,13 +1,13 @@ -/* eslint-disable func-names, no-var, camelcase, no-unused-vars, object-shorthand, space-before-function-paren, no-return-assign, comma-dangle, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, prefer-const, padded-blocks, wrap-iife, max-len */ +/* eslint-disable no-param-reassign, func-names, no-var, camelcase, no-unused-vars, object-shorthand, space-before-function-paren, no-return-assign, comma-dangle, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, prefer-const, padded-blocks, wrap-iife, max-len */ /* global Issuable */ /* global Turbolinks */ -(function() { +((global) => { var issuable_created; issuable_created = false; - this.Issuable = { + global.Issuable = { init: function() { Issuable.initTemplates(); Issuable.initSearch(); @@ -111,7 +111,11 @@ filterResults: (function(_this) { return function(form) { var formAction, formData, issuesUrl; - formData = form.serialize(); + formData = form.serializeArray(); + formData = formData.filter(function(data) { + return data.value !== ''; + }); + formData = $.param(formData); formAction = form.attr('action'); issuesUrl = formAction; issuesUrl += "" + (formAction.indexOf('?') < 0 ? '?' : '&'); @@ -184,4 +188,4 @@ } }; -}).call(this); +})(window); diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 8fa80502d92..0a0e73e0ccc 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -93,6 +93,19 @@ } }; + // Check if element scrolled into viewport from above or below + // Courtesy http://stackoverflow.com/a/7557433/414749 + w.gl.utils.isInViewport = function(el) { + var rect = el.getBoundingClientRect(); + + return ( + rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= window.innerHeight && + rect.right <= window.innerWidth + ); + }; + gl.utils.getPagePath = function() { return $('body').data('page').split(':')[0]; }; diff --git a/app/assets/javascripts/merge_request_tabs.js.es6 b/app/assets/javascripts/merge_request_tabs.js.es6 index 3ec0f1fd613..42015a02477 100644 --- a/app/assets/javascripts/merge_request_tabs.js.es6 +++ b/app/assets/javascripts/merge_request_tabs.js.es6 @@ -59,16 +59,13 @@ class MergeRequestTabs { - constructor({ action, setUrl, buildsLoaded, stubLocation } = {}) { + constructor({ action, setUrl, stubLocation } = {}) { this.diffsLoaded = false; - this.buildsLoaded = false; this.pipelinesLoaded = false; this.commitsLoaded = false; this.fixedLayoutPref = null; this.setUrl = setUrl !== undefined ? setUrl : true; - this.buildsLoaded = buildsLoaded || false; - this.setCurrentAction = this.setCurrentAction.bind(this); this.tabShown = this.tabShown.bind(this); this.showTab = this.showTab.bind(this); @@ -119,10 +116,6 @@ $.scrollTo('.merge-request-details .merge-request-tabs', { offset: -navBarHeight, }); - } else if (action === 'builds') { - this.loadBuilds($target.attr('href')); - this.expandView(); - this.resetViewContainer(); } else if (action === 'pipelines') { this.loadPipelines($target.attr('href')); this.expandView(); @@ -180,8 +173,8 @@ setCurrentAction(action) { this.currentAction = action === 'show' ? 'notes' : action; - // Remove a trailing '/commits' '/diffs' '/builds' '/pipelines' '/new' '/new/diffs' - let newState = location.pathname.replace(/\/(commits|diffs|builds|pipelines|new|new\/diffs)(\.html)?\/?$/, ''); + // Remove a trailing '/commits' '/diffs' '/pipelines' '/new' '/new/diffs' + let newState = location.pathname.replace(/\/(commits|diffs|pipelines|new|new\/diffs)(\.html)?\/?$/, ''); // Append the new action if we're on a tab other than 'notes' if (this.currentAction !== 'notes') { @@ -255,22 +248,6 @@ }); } - loadBuilds(source) { - if (this.buildsLoaded) { - return; - } - this.ajaxGet({ - url: `${source}.json`, - success: (data) => { - document.querySelector('div#builds').innerHTML = data.html; - gl.utils.localTimeAgo($('.js-timeago', 'div#builds')); - this.buildsLoaded = true; - new gl.Pipelines(); - this.scrollToElement('#builds'); - }, - }); - } - loadPipelines(source) { if (this.pipelinesLoaded) { return; diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index e47047c4cca..0305aeb07d9 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -74,7 +74,7 @@ MergeRequestWidget.prototype.addEventListeners = function() { var allowedPages; - allowedPages = ['show', 'commits', 'builds', 'pipelines', 'changes']; + allowedPages = ['show', 'commits', 'pipelines', 'changes']; $(document).on('page:change.merge_request', (function(_this) { return function() { var page; @@ -173,7 +173,6 @@ message = message.replace('{{title}}', data.title); notify(title, message, _this.opts.gitlab_icon, function() { this.close(); - return Turbolinks.visit(_this.opts.builds_path); }); } } diff --git a/app/assets/javascripts/merge_request_widget/ci_bundle.js.es6 b/app/assets/javascripts/merge_request_widget/ci_bundle.js.es6 new file mode 100644 index 00000000000..2b074994b4a --- /dev/null +++ b/app/assets/javascripts/merge_request_widget/ci_bundle.js.es6 @@ -0,0 +1,42 @@ +/* global merge_request_widget */ + +(() => { + $(() => { + /* TODO: This needs a better home, or should be refactored. It was previously contained + * in a script tag in app/views/projects/merge_requests/widget/open/_accept.html.haml, + * but Vue chokes on script tags and prevents their execution. So it was moved here + * temporarily. + * */ + + if ($('.accept-mr-form').length) { + $('.accept-mr-form').on('ajax:send', () => { + $('.accept-mr-form :input').disable(); + }); + + $('.accept_merge_request').on('click', () => { + $('.js-merge-button').html('<i class="fa fa-spinner fa-spin"></i> Merge in progress'); + }); + + $('.merge_when_build_succeeds').on('click', () => { + $('#merge_when_build_succeeds').val('1'); + }); + + $('.js-merge-dropdown a').on('click', (e) => { + e.preventDefault(); + $(this).closest('form').submit(); + }); + } else if ($('.rebase-in-progress').length) { + merge_request_widget.rebaseInProgress(); + } else if ($('.rebase-mr-form').length) { + $('.rebase-mr-form').on('ajax:send', () => { + $('.rebase-mr-form :input').disable(); + }); + + $('.js-rebase-button').on('click', () => { + $('.js-rebase-button').html("<i class='fa fa-spinner fa-spin'></i> Rebase in progress"); + }); + } else { + merge_request_widget.getMergeStatus(); + } + }); +})(); diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6 b/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6 new file mode 100644 index 00000000000..90b3366f14b --- /dev/null +++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6 @@ -0,0 +1,96 @@ +/* eslint-disable no-new */ +/* global Flash */ + +/** + * In each pipelines table we have a mini pipeline graph for each pipeline. + * + * When we click in a pipeline stage, we need to make an API call to get the + * builds list to render in a dropdown. + * + * The container should be the table element. + * + * The stage icon clicked needs to have the following HTML structure: + * <div> + * <button class="dropdown js-builds-dropdown-button"></button> + * <div class="js-builds-dropdown-container"></div> + * </div> + */ +(() => { + class MiniPipelineGraph { + constructor(opts = {}) { + this.container = opts.container || ''; + this.dropdownListSelector = '.js-builds-dropdown-container'; + this.getBuildsList = this.getBuildsList.bind(this); + + this.bindEvents(); + } + + /** + * Adds and removes the event listener. + */ + bindEvents() { + const dropdownButtonSelector = 'button.js-builds-dropdown-button'; + + $(this.container).off('click', dropdownButtonSelector, this.getBuildsList) + .on('click', dropdownButtonSelector, this.getBuildsList); + } + + /** + * For the clicked stage, renders the given data in the dropdown list. + * + * @param {HTMLElement} stageContainer + * @param {Object} data + */ + renderBuildsList(stageContainer, data) { + const dropdownContainer = stageContainer.parentElement.querySelector( + `${this.dropdownListSelector} .js-builds-dropdown-list`, + ); + + dropdownContainer.innerHTML = data; + } + + /** + * For the clicked stage, gets the list of builds. + * + * @param {Object} e + * @return {Promise} + */ + getBuildsList(e) { + const button = e.currentTarget; + const endpoint = button.dataset.stageEndpoint; + + return $.ajax({ + dataType: 'json', + type: 'GET', + url: endpoint, + beforeSend: () => { + this.renderBuildsList(button, ''); + this.toggleLoading(button); + }, + success: (data) => { + this.toggleLoading(button); + this.renderBuildsList(button, data.html); + }, + error: () => { + this.toggleLoading(button); + new Flash('An error occurred while fetching the builds.', 'alert'); + }, + }); + } + + /** + * Toggles the visibility of the loading icon. + * + * @param {HTMLElement} stageContainer + * @return {type} + */ + toggleLoading(stageContainer) { + stageContainer.parentElement.querySelector( + `${this.dropdownListSelector} .js-builds-dropdown-loading`, + ).classList.toggle('hidden'); + } + } + + window.gl = window.gl || {}; + window.gl.MiniPipelineGraph = MiniPipelineGraph; +})(); diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js index 64b19a54893..20a68780cd5 100644 --- a/app/assets/javascripts/network/branch_graph.js +++ b/app/assets/javascripts/network/branch_graph.js @@ -356,7 +356,7 @@ icon = this.image(gon.relative_url_root + commit.author.icon, x, y, 20, 20); nameText = this.text(x + 25, y + 10, commit.author.name); idText = this.text(x, y + 35, commit.id); - messageText = this.text(x, y + 50, commit.message); + messageText = this.text(x, y + 50, commit.message.replace(/\r?\n/g, " \n ")); textSet = this.set(icon, nameText, idText, messageText).attr({ "text-anchor": "start", font: "12px Monaco, monospace" @@ -368,6 +368,7 @@ idText.attr({ fill: "#AAA" }); + messageText.node.style["white-space"] = "pre"; this.textWrap(messageText, boxWidth - 50); rect = this.rect(x - 10, y - 10, boxWidth, 100, 4).attr({ fill: "#FFF", @@ -404,16 +405,21 @@ s.push("\n"); x = 0; } - x += word.length * letterWidth; - s.push(word + " "); + if (word === "\n") { + s.push("\n"); + x = 0; + } else { + s.push(word + " "); + x += word.length * letterWidth; + } } t.attr({ - text: s.join("") + text: s.join("").trim() }); b = t.getBBox(); - h = Math.abs(b.y2) - Math.abs(b.y) + 1; + h = Math.abs(b.y2) + 1; return t.attr({ - y: b.y + h + y: h }); }; diff --git a/app/assets/javascripts/terminal/terminal.js.es6 b/app/assets/javascripts/terminal/terminal.js.es6 new file mode 100644 index 00000000000..6b9422b1816 --- /dev/null +++ b/app/assets/javascripts/terminal/terminal.js.es6 @@ -0,0 +1,62 @@ +/* global Terminal */ + +(() => { + class GLTerminal { + + constructor(options) { + this.options = options || {}; + + this.options.cursorBlink = options.cursorBlink || true; + this.options.screenKeys = options.screenKeys || true; + this.container = document.querySelector(options.selector); + + this.setSocketUrl(); + this.createTerminal(); + $(window).off('resize.terminal').on('resize.terminal', () => { + this.terminal.fit(); + }); + } + + setSocketUrl() { + const { protocol, hostname, port } = window.location; + const wsProtocol = protocol === 'https:' ? 'wss://' : 'ws://'; + const path = this.container.dataset.projectPath; + + this.socketUrl = `${wsProtocol}${hostname}:${port}${path}`; + } + + createTerminal() { + this.terminal = new Terminal(this.options); + this.socket = new WebSocket(this.socketUrl, ['terminal.gitlab.com']); + this.socket.binaryType = 'arraybuffer'; + + this.terminal.open(this.container); + this.socket.onopen = () => { this.runTerminal(); }; + this.socket.onerror = () => { this.handleSocketFailure(); }; + } + + runTerminal() { + const decoder = new TextDecoder('utf-8'); + const encoder = new TextEncoder('utf-8'); + + this.terminal.on('data', (data) => { + this.socket.send(encoder.encode(data)); + }); + + this.socket.addEventListener('message', (ev) => { + this.terminal.write(decoder.decode(ev.data)); + }); + + this.isTerminalInitialized = true; + this.terminal.fit(); + } + + handleSocketFailure() { + this.terminal.write('\r\nConnection failure'); + } + + } + + window.gl = window.gl || {}; + gl.Terminal = GLTerminal; +})(); diff --git a/app/assets/javascripts/terminal/terminal_bundle.js.es6 b/app/assets/javascripts/terminal/terminal_bundle.js.es6 new file mode 100644 index 00000000000..ded7ee6e9fe --- /dev/null +++ b/app/assets/javascripts/terminal/terminal_bundle.js.es6 @@ -0,0 +1,5 @@ +//= require xterm/xterm.js +//= require xterm/fit.js +//= require ./terminal.js + +$(() => new gl.Terminal({ selector: '#terminal' })); diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js index d2aa3c7a841..e407b856e10 100644 --- a/app/assets/javascripts/u2f/authenticate.js +++ b/app/assets/javascripts/u2f/authenticate.js @@ -89,7 +89,8 @@ U2FAuthenticate.prototype.renderError = function(error) { this.renderTemplate('error', { - error_message: error.message() + error_message: error.message(), + error_code: error.errorCode }); return this.container.find('#js-u2f-try-again').on('click', this.renderSetup); }; diff --git a/app/assets/javascripts/u2f/error.js b/app/assets/javascripts/u2f/error.js index 69f98c9c0ad..bb9942a3aa0 100644 --- a/app/assets/javascripts/u2f/error.js +++ b/app/assets/javascripts/u2f/error.js @@ -9,7 +9,6 @@ this.errorCode = errorCode; this.message = bind(this.message, this); this.httpsDisabled = window.location.protocol !== 'https:'; - console.error("U2F Error Code: " + this.errorCode); } U2FError.prototype.message = function() { diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js index 4f5d68f546b..050c9bfc02e 100644 --- a/app/assets/javascripts/u2f/register.js +++ b/app/assets/javascripts/u2f/register.js @@ -76,7 +76,8 @@ U2FRegister.prototype.renderError = function(error) { this.renderTemplate('error', { - error_message: error.message() + error_message: error.message(), + error_code: error.errorCode }); return this.container.find('#js-u2f-try-again').on('click', this.renderSetup); }; |