diff options
Diffstat (limited to 'app/assets/javascripts')
58 files changed, 556 insertions, 373 deletions
diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js index 07d67d49aa5..69ff2f95799 100644 --- a/app/assets/javascripts/blob/viewer/index.js +++ b/app/assets/javascripts/blob/viewer/index.js @@ -8,7 +8,10 @@ export default class BlobViewer { this.richViewer = document.querySelector('.blob-viewer[data-type="rich"]'); this.$fileHolder = $('.file-holder'); - let initialViewerName = document.querySelector('.blob-viewer:not(.hidden)').getAttribute('data-type'); + const initialViewer = document.querySelector('.blob-viewer:not(.hidden)'); + if (!initialViewer) return; + + let initialViewerName = initialViewer.getAttribute('data-type'); this.initBindings(); diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js index 239eeacf2d7..0d23bdeeb99 100644 --- a/app/assets/javascripts/boards/components/board.js +++ b/app/assets/javascripts/boards/components/board.js @@ -35,7 +35,10 @@ gl.issueBoards.Board = Vue.extend({ filter: { handler() { this.list.page = 1; - this.list.getIssues(true); + this.list.getIssues(true) + .catch(() => { + // TODO: handle request error + }); }, deep: true, }, diff --git a/app/assets/javascripts/boards/components/board_blank_state.js b/app/assets/javascripts/boards/components/board_blank_state.js index 3fc68457961..870e115bd1a 100644 --- a/app/assets/javascripts/boards/components/board_blank_state.js +++ b/app/assets/javascripts/boards/components/board_blank_state.js @@ -70,7 +70,10 @@ export default { list.id = listObj.id; list.label.id = listObj.label.id; - list.getIssues(); + list.getIssues() + .catch(() => { + // TODO: handle request error + }); }); }) .catch(() => { diff --git a/app/assets/javascripts/boards/components/board_list.js b/app/assets/javascripts/boards/components/board_list.js index b13386536bf..7ee2696e720 100644 --- a/app/assets/javascripts/boards/components/board_list.js +++ b/app/assets/javascripts/boards/components/board_list.js @@ -2,6 +2,7 @@ import boardNewIssue from './board_new_issue'; import boardCard from './board_card'; import eventHub from '../eventhub'; +import loadingIcon from '../../vue_shared/components/loading_icon.vue'; const Store = gl.issueBoards.BoardsStore; @@ -44,6 +45,7 @@ export default { components: { boardCard, boardNewIssue, + loadingIcon, }, methods: { listHeight() { @@ -90,7 +92,10 @@ export default { if (this.scrollHeight() <= this.listHeight() && this.list.issuesSize > this.list.issues.length) { this.list.page += 1; - this.list.getIssues(false); + this.list.getIssues(false) + .catch(() => { + // TODO: handle request error + }); } if (this.scrollHeight() > Math.ceil(this.listHeight())) { @@ -153,10 +158,7 @@ export default { class="board-list-loading text-center" aria-label="Loading issues" v-if="loading"> - <i - class="fa fa-spinner fa-spin" - aria-hidden="true"> - </i> + <loading-icon /> </div> <board-new-issue :list="list" @@ -181,12 +183,12 @@ export default { class="board-list-count text-center" v-if="showCount" data-id="-1"> - <i - class="fa fa-spinner fa-spin" - aria-label="Loading more issues" - aria-hidden="true" - v-show="list.loadingMore"> - </i> + + <loading-icon + v-show="list.loadingMore" + label="Loading more issues" + /> + <span v-if="list.issues.length === list.issuesSize"> Showing all issues </span> diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index 317cef9f227..9bcea302da2 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -36,6 +36,9 @@ gl.issueBoards.BoardSidebar = Vue.extend({ }, assigneeId() { return this.issue.assignee ? this.issue.assignee.id : 0; + }, + milestoneTitle() { + return this.issue.milestone ? this.issue.milestone.title : 'No Milestone'; } }, watch: { diff --git a/app/assets/javascripts/boards/components/modal/index.js b/app/assets/javascripts/boards/components/modal/index.js index fdab317dc23..507f16f3f06 100644 --- a/app/assets/javascripts/boards/components/modal/index.js +++ b/app/assets/javascripts/boards/components/modal/index.js @@ -2,6 +2,7 @@ import Vue from 'vue'; import queryData from '../../utils/query_data'; +import loadingIcon from '../../../vue_shared/components/loading_icon.vue'; require('./header'); require('./list'); @@ -108,6 +109,8 @@ gl.issueBoards.IssuesModal = Vue.extend({ if (!this.issuesCount) { this.issuesCount = data.size; } + }).catch(() => { + // TODO: handle request error }); }, }, @@ -135,6 +138,7 @@ gl.issueBoards.IssuesModal = Vue.extend({ 'modal-list': gl.issueBoards.ModalList, 'modal-footer': gl.issueBoards.ModalFooter, 'empty-state': gl.issueBoards.ModalEmptyState, + loadingIcon, }, template: ` <div @@ -159,7 +163,7 @@ gl.issueBoards.IssuesModal = Vue.extend({ class="add-issues-list text-center" v-if="loading || filterLoading"> <div class="add-issues-list-loading"> - <i class="fa fa-spinner fa-spin"></i> + <loading-icon /> </div> </section> <modal-footer></modal-footer> diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index bd2f62bcc1a..90561d0f7a8 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -25,7 +25,9 @@ class List { } if (this.type !== 'blank' && this.id) { - this.getIssues(); + this.getIssues().catch(() => { + // TODO: handle request error + }); } } @@ -52,11 +54,17 @@ class List { gl.issueBoards.BoardsStore.state.lists.splice(index, 1); gl.issueBoards.BoardsStore.updateNewListDropdown(this.id); - gl.boardService.destroyList(this.id); + gl.boardService.destroyList(this.id) + .catch(() => { + // TODO: handle request error + }); } update () { - gl.boardService.updateList(this.id, this.position); + gl.boardService.updateList(this.id, this.position) + .catch(() => { + // TODO: handle request error + }); } nextPage () { @@ -146,11 +154,17 @@ class List { this.issues.splice(oldIndex, 1); this.issues.splice(newIndex, 0, issue); - gl.boardService.moveIssue(issue.id, null, null, moveBeforeIid, moveAfterIid); + gl.boardService.moveIssue(issue.id, null, null, moveBeforeIid, moveAfterIid) + .catch(() => { + // TODO: handle request error + }); } updateIssueLabel(issue, listFrom, moveBeforeIid, moveAfterIid) { - gl.boardService.moveIssue(issue.id, listFrom.id, this.id, moveBeforeIid, moveAfterIid); + gl.boardService.moveIssue(issue.id, listFrom.id, this.id, moveBeforeIid, moveAfterIid) + .catch(() => { + // TODO: handle request error + }); } findIssue (id) { diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js b/app/assets/javascripts/commit/pipelines/pipelines_table.js index ad9c600b499..b8be0d8a301 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.js +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js @@ -6,6 +6,7 @@ import PipelineStore from '../../pipelines/stores/pipelines_store'; import eventHub from '../../pipelines/event_hub'; import EmptyState from '../../pipelines/components/empty_state.vue'; import ErrorState from '../../pipelines/components/error_state.vue'; +import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import '../../lib/utils/common_utils'; import '../../vue_shared/vue_resource_interceptor'; import Poll from '../../lib/utils/poll'; @@ -17,8 +18,6 @@ import Poll from '../../lib/utils/poll'; * We need a store to store the received environemnts. * We need a service to communicate with the server. * - * Necessary SVG in the table are provided as props. This should be refactored - * as soon as we have Webpack and can load them directly into JS files. */ export default Vue.component('pipelines-table', { @@ -27,6 +26,7 @@ export default Vue.component('pipelines-table', { 'pipelines-table-component': PipelinesTableComponent, 'error-state': ErrorState, 'empty-state': EmptyState, + loadingIcon, }, /** @@ -151,13 +151,12 @@ export default Vue.component('pipelines-table', { template: ` <div class="content-list pipelines"> - <div - class="realtime-loading" - v-if="isLoading"> - <i - class="fa fa-spinner fa-spin" - aria-hidden="true" /> - </div> + + <loading-icon + label="Loading pipelines" + size="3" + v-if="isLoading" + /> <empty-state v-if="shouldRenderEmptyState" diff --git a/app/assets/javascripts/deploy_keys/components/action_btn.vue b/app/assets/javascripts/deploy_keys/components/action_btn.vue index 3ff3a9d977e..3f993213dd0 100644 --- a/app/assets/javascripts/deploy_keys/components/action_btn.vue +++ b/app/assets/javascripts/deploy_keys/components/action_btn.vue @@ -1,5 +1,6 @@ <script> import eventHub from '../eventhub'; + import loadingIcon from '../../vue_shared/components/loading_icon.vue'; export default { data() { @@ -22,6 +23,11 @@ default: 'btn-default', }, }, + + components: { + loadingIcon, + }, + methods: { doAction() { this.isLoading = true; @@ -44,11 +50,6 @@ :disabled="isLoading" @click="doAction"> {{ text }} - <i - v-if="isLoading" - class="fa fa-spinner fa-spin" - aria-hidden="true" - aria-label="Loading"> - </i> + <loading-icon v-if="isLoading" /> </button> </template> diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue index 7315a9e11cb..5f6eed0c67c 100644 --- a/app/assets/javascripts/deploy_keys/components/app.vue +++ b/app/assets/javascripts/deploy_keys/components/app.vue @@ -4,6 +4,7 @@ import DeployKeysService from '../service'; import DeployKeysStore from '../store'; import keysPanel from './keys_panel.vue'; + import loadingIcon from '../../vue_shared/components/loading_icon.vue'; export default { data() { @@ -28,6 +29,7 @@ }, components: { keysPanel, + loadingIcon, }, methods: { fetchKeys() { @@ -74,15 +76,11 @@ <template> <div class="col-lg-9 col-lg-offset-3 append-bottom-default deploy-keys"> - <div - class="text-center" - v-if="isLoading && !hasKeys"> - <i - class="fa fa-spinner fa-spin fa-2x" - aria-hidden="true" - aria-label="Loading deploy keys"> - </i> - </div> + <loading-icon + v-if="isLoading && !hasKeys" + size="2" + label="Loading deploy keys" + /> <div v-else-if="hasKeys"> <keys-panel title="Enabled deploy keys for this project" diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index abb871c3af0..43ad127a4db 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -246,6 +246,7 @@ const ShortcutsBlob = require('./shortcuts_blob'); new NotificationsForm(); if ($('#tree-slider').length) { new TreeView(); + new BlobViewer(); } break; case 'projects:pipelines:builds': @@ -300,6 +301,7 @@ const ShortcutsBlob = require('./shortcuts_blob'); case 'projects:tree:show': shortcut_handler = new ShortcutsNavigation(); new TreeView(); + new BlobViewer(); gl.TargetBranchDropDown.bootstrap(); break; case 'projects:find_file:show': diff --git a/app/assets/javascripts/environments/components/environment.vue b/app/assets/javascripts/environments/components/environment.vue index e0088d496eb..d4e13f3c84a 100644 --- a/app/assets/javascripts/environments/components/environment.vue +++ b/app/assets/javascripts/environments/components/environment.vue @@ -1,17 +1,19 @@ <script> /* global Flash */ import EnvironmentsService from '../services/environments_service'; -import EnvironmentTable from './environments_table.vue'; +import environmentTable from './environments_table.vue'; import EnvironmentsStore from '../stores/environments_store'; -import TablePaginationComponent from '../../vue_shared/components/table_pagination'; +import loadingIcon from '../../vue_shared/components/loading_icon.vue'; +import tablePagination from '../../vue_shared/components/table_pagination.vue'; import '../../lib/utils/common_utils'; import eventHub from '../event_hub'; export default { components: { - 'environment-table': EnvironmentTable, - 'table-pagination': TablePaginationComponent, + environmentTable, + tablePagination, + loadingIcon, }, data() { @@ -186,14 +188,11 @@ export default { </div> <div class="content-list environments-container"> - <div - class="environments-list-loading text-center" - v-if="isLoading"> - - <i - class="fa fa-spinner fa-spin" - aria-hidden="true" /> - </div> + <loading-icon + label="Loading environments" + size="3" + v-if="isLoading" + /> <div class="blank-state blank-state-no-icon" diff --git a/app/assets/javascripts/environments/components/environment_actions.vue b/app/assets/javascripts/environments/components/environment_actions.vue index 63bffe8a998..a2448520a5f 100644 --- a/app/assets/javascripts/environments/components/environment_actions.vue +++ b/app/assets/javascripts/environments/components/environment_actions.vue @@ -1,6 +1,7 @@ <script> import playIconSvg from 'icons/_icon_play.svg'; import eventHub from '../event_hub'; +import loadingIcon from '../../vue_shared/components/loading_icon.vue'; export default { props: { @@ -11,6 +12,10 @@ export default { }, }, + components: { + loadingIcon, + }, + data() { return { playIconSvg, @@ -61,10 +66,7 @@ export default { <i class="fa fa-caret-down" aria-hidden="true"/> - <i - v-if="isLoading" - class="fa fa-spinner fa-spin" - aria-hidden="true"/> + <loading-icon v-if="isLoading" /> </span> </button> diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index 0ffe9ea17fa..1f01629aa1b 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -1,5 +1,6 @@ <script> import Timeago from 'timeago.js'; +import _ from 'underscore'; import '../../lib/utils/text_utility'; import ActionsComponent from './environment_actions.vue'; import ExternalUrlComponent from './environment_external_url.vue'; @@ -59,7 +60,7 @@ export default { hasLastDeploymentKey() { if (this.model && this.model.last_deployment && - !this.$options.isObjectEmpty(this.model.last_deployment)) { + !_.isEmpty(this.model.last_deployment)) { return true; } return false; @@ -310,8 +311,8 @@ export default { */ deploymentHasUser() { return this.model && - !this.$options.isObjectEmpty(this.model.last_deployment) && - !this.$options.isObjectEmpty(this.model.last_deployment.user); + !_.isEmpty(this.model.last_deployment) && + !_.isEmpty(this.model.last_deployment.user); }, /** @@ -322,8 +323,8 @@ export default { */ deploymentUser() { if (this.model && - !this.$options.isObjectEmpty(this.model.last_deployment) && - !this.$options.isObjectEmpty(this.model.last_deployment.user)) { + !_.isEmpty(this.model.last_deployment) && + !_.isEmpty(this.model.last_deployment.user)) { return this.model.last_deployment.user; } return {}; @@ -338,8 +339,8 @@ export default { */ shouldRenderBuildName() { return !this.model.isFolder && - !this.$options.isObjectEmpty(this.model.last_deployment) && - !this.$options.isObjectEmpty(this.model.last_deployment.deployable); + !_.isEmpty(this.model.last_deployment) && + !_.isEmpty(this.model.last_deployment.deployable); }, /** @@ -380,7 +381,7 @@ export default { */ shouldRenderDeploymentID() { return !this.model.isFolder && - !this.$options.isObjectEmpty(this.model.last_deployment) && + !_.isEmpty(this.model.last_deployment) && this.model.last_deployment.iid !== undefined; }, @@ -410,21 +411,6 @@ export default { }, }, - /** - * Helper to verify if certain given object are empty. - * Should be replaced by lodash _.isEmpty - https://lodash.com/docs/4.17.2#isEmpty - * @param {Object} object - * @returns {Bollean} - */ - isObjectEmpty(object) { - for (const key in object) { // eslint-disable-line - if (hasOwnProperty.call(object, key)) { - return false; - } - } - return true; - }, - methods: { onClickFolder() { eventHub.$emit('toggleFolder', this.model, this.folderUrl); diff --git a/app/assets/javascripts/environments/components/environment_rollback.vue b/app/assets/javascripts/environments/components/environment_rollback.vue index 44b8730fd09..2ba985bfe3e 100644 --- a/app/assets/javascripts/environments/components/environment_rollback.vue +++ b/app/assets/javascripts/environments/components/environment_rollback.vue @@ -6,6 +6,7 @@ * Makes a post request when the button is clicked. */ import eventHub from '../event_hub'; +import loadingIcon from '../../vue_shared/components/loading_icon.vue'; export default { props: { @@ -20,6 +21,10 @@ export default { }, }, + components: { + loadingIcon, + }, + data() { return { isLoading: false, @@ -49,9 +54,6 @@ export default { Rollback </span> - <i - v-if="isLoading" - class="fa fa-spinner fa-spin" - aria-hidden="true" /> + <loading-icon v-if="isLoading" /> </button> </template> diff --git a/app/assets/javascripts/environments/components/environment_stop.vue b/app/assets/javascripts/environments/components/environment_stop.vue index f483ea7e937..a904453ffa9 100644 --- a/app/assets/javascripts/environments/components/environment_stop.vue +++ b/app/assets/javascripts/environments/components/environment_stop.vue @@ -4,6 +4,7 @@ * Used in environments table. */ import eventHub from '../event_hub'; +import loadingIcon from '../../vue_shared/components/loading_icon.vue'; export default { props: { @@ -19,6 +20,10 @@ export default { }; }, + components: { + loadingIcon, + }, + computed: { title() { return 'Stop'; @@ -51,9 +56,6 @@ export default { <i class="fa fa-stop stop-env-icon" aria-hidden="true" /> - <i - v-if="isLoading" - class="fa fa-spinner fa-spin" - aria-hidden="true" /> + <loading-icon v-if="isLoading" /> </button> </template> diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue index 15eedaf76e1..5148a2ae79b 100644 --- a/app/assets/javascripts/environments/components/environments_table.vue +++ b/app/assets/javascripts/environments/components/environments_table.vue @@ -3,10 +3,12 @@ * Render environments table. */ import EnvironmentTableRowComponent from './environment_item.vue'; +import loadingIcon from '../../vue_shared/components/loading_icon.vue'; export default { components: { 'environment-item': EnvironmentTableRowComponent, + loadingIcon, }, props: { @@ -77,10 +79,8 @@ export default { <template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0"> <tr v-if="isLoadingFolderContent"> - <td colspan="6" class="text-center"> - <i - class="fa fa-spin fa-spinner fa-2x" - aria-hidden="true" /> + <td colspan="6"> + <loading-icon size="2" /> </td> </tr> diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue index f4a0c390c91..bd161c8a379 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_view.vue +++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue @@ -1,16 +1,18 @@ <script> /* global Flash */ import EnvironmentsService from '../services/environments_service'; -import EnvironmentTable from '../components/environments_table.vue'; +import environmentTable from '../components/environments_table.vue'; import EnvironmentsStore from '../stores/environments_store'; -import TablePaginationComponent from '../../vue_shared/components/table_pagination'; +import loadingIcon from '../../vue_shared/components/loading_icon.vue'; +import tablePagination from '../../vue_shared/components/table_pagination.vue'; import '../../lib/utils/common_utils'; import '../../vue_shared/vue_resource_interceptor'; export default { components: { - 'environment-table': EnvironmentTable, - 'table-pagination': TablePaginationComponent, + environmentTable, + tablePagination, + loadingIcon, }, data() { @@ -153,13 +155,12 @@ export default { </div> <div class="environments-container"> - <div - class="environments-list-loading text-center" - v-if="isLoading"> - <i - class="fa fa-spinner fa-spin" - aria-hidden="true"/> - </div> + + <loading-icon + label="Loading environments" + v-if="isLoading" + size="3" + /> <div class="table-holder" diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js index 59d6508fc02..534e651b030 100644 --- a/app/assets/javascripts/files_comment_button.js +++ b/app/assets/javascripts/files_comment_button.js @@ -3,7 +3,6 @@ /* global notes */ let $commentButtonTemplate; -var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; window.FilesCommentButton = (function() { var COMMENT_BUTTON_CLASS, EMPTY_CELL_CLASS, LINE_COLUMN_CLASSES, LINE_CONTENT_CLASS, LINE_HOLDER_CLASS, LINE_NUMBER_CLASS, OLD_LINE_CLASS, TEXT_FILE_SELECTOR, UNFOLDABLE_LINE_CLASS; @@ -27,8 +26,8 @@ window.FilesCommentButton = (function() { TEXT_FILE_SELECTOR = '.text-file'; function FilesCommentButton(filesContainerElement) { - this.render = bind(this.render, this); - this.hideButton = bind(this.hideButton, this); + this.render = this.render.bind(this); + this.hideButton = this.hideButton.bind(this); this.isParallelView = notes.isParallelView(); filesContainerElement.on('mouseover', LINE_COLUMN_CLASSES, this.render) .on('mouseleave', LINE_COLUMN_CLASSES, this.hideButton); diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 0c9eb84f0eb..24c423dd01e 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -1,9 +1,8 @@ /* eslint-disable func-names, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, prefer-rest-params, max-len, vars-on-top, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func, no-mixed-operators */ /* global fuzzaldrinPlus */ +import { isObject } from './lib/utils/type_utility'; -var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote, - bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }, - indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i += 1) { if (i in this && this[i] === item) return i; } return -1; }; +var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote; GitLabDropdownFilter = (function() { var ARROW_KEY_CODES, BLUR_KEYCODES, HAS_VALUE_CLASS; @@ -95,7 +94,7 @@ GitLabDropdownFilter = (function() { // { prop: 'def' } // ] // } - if (gl.utils.isObject(data)) { + if (isObject(data)) { results = {}; for (key in data) { group = data[key]; @@ -213,10 +212,10 @@ GitLabDropdown = (function() { var searchFields, selector, self; this.el = el1; this.options = options; - this.updateLabel = bind(this.updateLabel, this); - this.hidden = bind(this.hidden, this); - this.opened = bind(this.opened, this); - this.shouldPropagate = bind(this.shouldPropagate, this); + this.updateLabel = this.updateLabel.bind(this); + this.hidden = this.hidden.bind(this); + this.opened = this.opened.bind(this); + this.shouldPropagate = this.shouldPropagate.bind(this); self = this; selector = $(this.el).data("target"); this.dropdown = selector != null ? $(selector) : $(this.el).parent(); @@ -398,7 +397,7 @@ GitLabDropdown = (function() { html = [this.noResults()]; } else { // Handle array groups - if (gl.utils.isObject(data)) { + if (isObject(data)) { html = []; for (name in data) { groupData = data[name]; @@ -610,7 +609,12 @@ GitLabDropdown = (function() { var link = document.createElement('a'); link.href = url; - link.innerHTML = text; + + if (this.highlight) { + link.innerHTML = text; + } else { + link.textContent = text; + } if (selected) { link.className = 'is-active'; @@ -627,8 +631,8 @@ GitLabDropdown = (function() { }; GitLabDropdown.prototype.highlightTextMatches = function(text, term) { - var occurrences; - occurrences = fuzzaldrinPlus.match(text, term); + const occurrences = fuzzaldrinPlus.match(text, term); + const indexOf = [].indexOf; return text.split('').map(function(character, i) { if (indexOf.call(occurrences, i) !== -1) { return "<b>" + character + "</b>"; diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js index 521bc77db66..0deb27e522b 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js @@ -2,7 +2,6 @@ import d3 from 'd3'; -const bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; const extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; const hasProp = {}.hasOwnProperty; @@ -95,7 +94,7 @@ export const ContributorsMasterGraph = (function(superClass) { function ContributorsMasterGraph(data1) { this.data = data1; - this.update_content = bind(this.update_content, this); + this.update_content = this.update_content.bind(this); this.width = $('.content').width() - 70; this.height = 200; this.x = null; diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js index 687c2bb6110..544fc91876a 100644 --- a/app/assets/javascripts/issuable_form.js +++ b/app/assets/javascripts/issuable_form.js @@ -7,8 +7,6 @@ /* global Pikaday */ (function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; - this.IssuableForm = (function() { IssuableForm.prototype.issueMoveConfirmMsg = 'Are you sure you want to move this issue to another project?'; @@ -17,10 +15,10 @@ function IssuableForm(form) { var $issuableDueDate, calendar; this.form = form; - this.toggleWip = bind(this.toggleWip, this); - this.renderWipExplanation = bind(this.renderWipExplanation, this); - this.resetAutosave = bind(this.resetAutosave, this); - this.handleSubmit = bind(this.handleSubmit, this); + this.toggleWip = this.toggleWip.bind(this); + this.renderWipExplanation = this.renderWipExplanation.bind(this); + this.resetAutosave = this.resetAutosave.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); gl.GfmAutoComplete.setup(); new UsersSelect(); new ZenMode(); diff --git a/app/assets/javascripts/labels.js b/app/assets/javascripts/labels.js index 17a3fc1b1e4..03dd61b4263 100644 --- a/app/assets/javascripts/labels.js +++ b/app/assets/javascripts/labels.js @@ -1,11 +1,9 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, vars-on-top, no-unused-vars, max-len */ (function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; - this.Labels = (function() { function Labels() { - this.setSuggestedColor = bind(this.setSuggestedColor, this); - this.updateColorPreview = bind(this.updateColorPreview, this); + this.setSuggestedColor = this.setSuggestedColor.bind(this); + this.updateColorPreview = this.updateColorPreview.bind(this); var form; form = $('.label-form'); this.cleanBinding(); diff --git a/app/assets/javascripts/lib/utils/type_utility.js b/app/assets/javascripts/lib/utils/type_utility.js index db62e0be324..be86f336bcd 100644 --- a/app/assets/javascripts/lib/utils/type_utility.js +++ b/app/assets/javascripts/lib/utils/type_utility.js @@ -1,15 +1,2 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, no-return-assign, max-len */ -(function() { - (function(w) { - var base; - if (w.gl == null) { - w.gl = {}; - } - if ((base = w.gl).utils == null) { - base.utils = {}; - } - return w.gl.utils.isObject = function(obj) { - return (obj != null) && (obj.constructor === Object); - }; - })(window); -}).call(window); +// eslint-disable-next-line import/prefer-default-export +export const isObject = obj => obj && obj.constructor === Object; diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js index 3ac6dedf131..517f03d5aba 100644 --- a/app/assets/javascripts/line_highlighter.js +++ b/app/assets/javascripts/line_highlighter.js @@ -31,8 +31,6 @@ require('vendor/jquery.scrollTo'); // </div> // (function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; - this.LineHighlighter = (function() { // CSS class applied to highlighted lines LineHighlighter.prototype.highlightClass = 'hll'; @@ -47,9 +45,9 @@ require('vendor/jquery.scrollTo'); // hash - String URL hash for dependency injection in tests hash = location.hash; } - this.setHash = bind(this.setHash, this); - this.highlightLine = bind(this.highlightLine, this); - this.clickHandler = bind(this.clickHandler, this); + this.setHash = this.setHash.bind(this); + this.highlightLine = this.highlightLine.bind(this); + this.clickHandler = this.clickHandler.bind(this); this.highlightHash = this.highlightHash.bind(this); this._hash = hash; this.bindEvents(); diff --git a/app/assets/javascripts/locale/de/app.js b/app/assets/javascripts/locale/de/app.js index e96090da80e..9411f078ecf 100644 --- a/app/assets/javascripts/locale/de/app.js +++ b/app/assets/javascripts/locale/de/app.js @@ -1 +1 @@ -var locales = locales || {}; locales['de'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-04-12 22:37-0500","Last-Translator":"FULL NAME <EMAIL@ADDRESS>","Language-Team":"German","Language":"de","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Plural-Forms":"nplurals=2; plural=n != 1;","lang":"de","domain":"app","plural_forms":"nplurals=2; plural=n != 1;"},"ByAuthor|by":[""],"Commit":["",""],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":[""],"CycleAnalyticsStage|Code":[""],"CycleAnalyticsStage|Issue":[""],"CycleAnalyticsStage|Plan":[""],"CycleAnalyticsStage|Production":[""],"CycleAnalyticsStage|Review":[""],"CycleAnalyticsStage|Staging":[""],"CycleAnalyticsStage|Test":[""],"Deploy":["",""],"FirstPushedBy|First":[""],"FirstPushedBy|pushed by":[""],"From issue creation until deploy to production":[""],"From merge request merge until deploy to production":[""],"Introducing Cycle Analytics":[""],"Last %d day":["",""],"Limited to showing %d event at most":["",""],"Median":[""],"New Issue":["",""],"Not available":[""],"Not enough data":[""],"OpenedNDaysAgo|Opened":[""],"Pipeline Health":[""],"ProjectLifecycle|Stage":[""],"Read more":[""],"Related Commits":[""],"Related Deployed Jobs":[""],"Related Issues":[""],"Related Jobs":[""],"Related Merge Requests":[""],"Related Merged Requests":[""],"Showing %d event":["",""],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":[""],"The collection of events added to the data gathered for that stage.":[""],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":[""],"The phase of the development lifecycle.":[""],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":[""],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":[""],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":[""],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":[""],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":[""],"The time taken by each data entry gathered by that stage.":[""],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":[""],"Time before an issue gets scheduled":[""],"Time before an issue starts implementation":[""],"Time between merge request creation and merge/close":[""],"Time until first merge request":[""],"Time|hr":["",""],"Time|min":["",""],"Time|s":[""],"Total Time":[""],"Total test time for all commits/merges":[""],"Want to see the data? Please ask an administrator for access.":[""],"We don't have enough data to show this stage.":[""],"You need permission.":[""],"day":["",""]}}};
\ No newline at end of file +var locales = locales || {}; locales['de'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","PO-Revision-Date":"2017-05-09 13:44+0200","Language-Team":"German","Language":"de","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","Plural-Forms":"nplurals=2; plural=n != 1;","Last-Translator":"","X-Generator":"Poedit 2.0.1","lang":"de","domain":"app","plural_forms":"nplurals=2; plural=n != 1;"},"ByAuthor|by":["Von"],"Commit":["Commit","Commits"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["Cycle Analytics liefern einen Überblick darüber, wie viel Zeit in Ihrem Projekt von einer Idee bis zum Produktivdeployment vergeht."],"CycleAnalyticsStage|Code":["Code"],"CycleAnalyticsStage|Issue":["Issue"],"CycleAnalyticsStage|Plan":["Planung"],"CycleAnalyticsStage|Production":["Produktiv"],"CycleAnalyticsStage|Review":["Review"],"CycleAnalyticsStage|Staging":["Staging"],"CycleAnalyticsStage|Test":["Test"],"Deploy":["Deployment","Deployments"],"FirstPushedBy|First":["Erster"],"FirstPushedBy|pushed by":["gepusht von"],"From issue creation until deploy to production":["Vom Anlegen des Issues bis zum Produktivdeployment"],"From merge request merge until deploy to production":["Vom Merge Request bis zum Produktivdeployment"],"Introducing Cycle Analytics":["Was sind Cycle Analytics?"],"Last %d day":["Letzter %d Tag","Letzten %d Tage"],"Limited to showing %d event at most":["Eingeschränkt auf maximal %d Ereignis","Eingeschränkt auf maximal %d Ereignisse"],"Median":["Median"],"New Issue":["Neues Issue","Neue Issues"],"Not available":["Nicht verfügbar"],"Not enough data":["Nicht genügend Daten"],"OpenedNDaysAgo|Opened":["Erstellt"],"Pipeline Health":["Pipeline Kennzahlen"],"ProjectLifecycle|Stage":["Phase"],"Read more":["Mehr"],"Related Commits":["Zugehörige Commits"],"Related Deployed Jobs":["Zugehörige Deploymentjobs"],"Related Issues":["Zugehörige Issues"],"Related Jobs":["Zugehörige Jobs"],"Related Merge Requests":["Zugehörige Merge Requests"],"Related Merged Requests":["Zugehörige abgeschlossene Merge Requests"],"Showing %d event":["Zeige %d Ereignis","Zeige %d Ereignisse"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["Die Code-Phase stellt die Zeit vom ersten Commit bis zum Erstellen eines Merge Requests dar. Sobald Sie Ihren ersten Merge Request anlegen, werden dessen Daten automatisch ergänzt."],"The collection of events added to the data gathered for that stage.":["Ereignisse, die für diese Phase ausgewertet wurden."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["Die Issue-Phase stellt die Zeit vom Anlegen eines Issues bis zum Zuweisen eines Meilensteins oder Hinzufügen zum Issue Board dar. Erstellen Sie einen Issue, damit dessen Daten hier erscheinen."],"The phase of the development lifecycle.":["Die Phase im Entwicklungsprozess."],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["Die Planungsphase stellt die Zeit von der vorherigen Phase bis zum Pushen des ersten Commits dar. Sobald Sie den ersten Commit pushen, werden dessen Daten hier erscheinen."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["Die Produktiv-Phase stellt die Gesamtzeit vom Anlegen eines Issues bis zum Deployment auf dem Produktivsystem dar. Sobald Sie den vollständigen Entwicklungszyklus von einer Idee bis zum Produktivdeployment durchlaufen haben, erscheinen die zugehörigen Daten hier."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["Die Review-Phase stellt die Zeit vom Anlegen eines Merge Requests bis zum Mergen dar. Sobald Sie Ihren ersten Merge Request abschließen, werden dessen Daten hier automatisch angezeigt."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["Die Staging-Phase stellt die Zeit zwischen Mergen eines Merge Requests und dem Produktivdeployment dar. Sobald Sie das erste Produktivdeployment durchgeführt haben, werden dessen Daten hier automatisch angezeigt."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["Die Test-Phase stellt die Zeit dar, die GitLab CI benötigt um die Pipelines von Merge Requests abzuarbeiten. Sobald die erste Pipeline abgeschlossen ist, werden deren Daten hier automatisch angezeigt."],"The time taken by each data entry gathered by that stage.":["Zeit die für das jeweilige Ereignis in der Phase ermittelt wurde."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["Der mittlere aller erfassten Werte. Zum Beispiel ist für 3, 5, 9 der Median 5. Bei 3, 5, 7, 8 ist der Median (5+7)/2 = 6."],"Time before an issue gets scheduled":["Zeit bis ein Issue geplant wird"],"Time before an issue starts implementation":["Zeit bis die Implementierung für ein Issue beginnt"],"Time between merge request creation and merge/close":["Zeit zwischen Anlegen und Mergen/Schließen eines Merge Requests"],"Time until first merge request":["Zeit bis zum ersten Merge Request"],"Time|hr":["h","h"],"Time|min":["min","min"],"Time|s":["s"],"Total Time":["Gesamtzeit"],"Total test time for all commits/merges":["Gesamte Testlaufzeit für alle Commits/Merges"],"Want to see the data? Please ask an administrator for access.":["Um diese Daten einsehen zu können, wenden Sie sich bitte an Ihren Administrator."],"We don't have enough data to show this stage.":["Es liegen nicht genügend Daten vor, um diese Phase anzuzeigen."],"You need permission.":["Sie benötigen Zugriffsrechte."],"day":["Tag","Tage"]}}};
\ No newline at end of file diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index a07aa047293..30636f6afec 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -59,7 +59,6 @@ import './lib/utils/datetime_utility'; import './lib/utils/notify'; import './lib/utils/pretty_time'; import './lib/utils/text_utility'; -import './lib/utils/type_utility'; import './lib/utils/url_utility'; // u2f diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js index ed342b9990f..d1cdcadf87d 100644 --- a/app/assets/javascripts/merge_request.js +++ b/app/assets/javascripts/merge_request.js @@ -6,8 +6,6 @@ require('./task_list'); require('./merge_request_tabs'); (function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; - this.MergeRequest = (function() { function MergeRequest(opts) { // Initialize MergeRequest behavior @@ -16,7 +14,7 @@ require('./merge_request_tabs'); // action - String, current controller action // this.opts = opts != null ? opts : {}; - this.submitNoteForm = bind(this.submitNoteForm, this); + this.submitNoteForm = this.submitNoteForm.bind(this); this.$el = $('.merge-request'); this.$('.show-all-commits').on('click', (function(_this) { return function() { diff --git a/app/assets/javascripts/merge_request_widget.js b/app/assets/javascripts/merge_request_widget.js index 6f6ae9bde92..3f976680b9d 100644 --- a/app/assets/javascripts/merge_request_widget.js +++ b/app/assets/javascripts/merge_request_widget.js @@ -7,8 +7,6 @@ import './smart_interval'; import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; ((global) => { - var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i += 1) { if (i in this && this[i] === item) return i; } return -1; }; - const DEPLOYMENT_TEMPLATE = `<div class="mr-widget-heading" id="<%- id %>"> <div class="ci_widget ci-success"> <%= ci_success_icon %> @@ -258,7 +256,7 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; let stateClass = 'btn-danger'; if (!hasCi) { stateClass = 'btn-create'; - } else if (indexOf.call(allowed_states, state) !== -1) { + } else if (allowed_states.indexOf(state) !== -1) { switch (state) { case "failed": case "canceled": diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 11e68c0a3be..9d481d7c003 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -18,12 +18,11 @@ } $els.each(function(i, dropdown) { - var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, showStarted, useId, showMenuAbove; + var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, defaultNo, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, selectedMilestoneDefault, showAny, showNo, showUpcoming, showStarted, useId, showMenuAbove; $dropdown = $(dropdown); projectId = $dropdown.data('project-id'); milestonesUrl = $dropdown.data('milestones'); issueUpdateURL = $dropdown.data('issueUpdate'); - selectedMilestone = $dropdown.data('selected'); showNo = $dropdown.data('show-no'); showAny = $dropdown.data('show-any'); showMenuAbove = $dropdown.data('showMenuAbove'); @@ -31,6 +30,7 @@ showStarted = $dropdown.data('show-started'); useId = $dropdown.data('use-id'); defaultLabel = $dropdown.data('default-label'); + defaultNo = $dropdown.data('default-no'); issuableId = $dropdown.data('issuable-id'); abilityName = $dropdown.data('ability-name'); $selectbox = $dropdown.closest('.selectbox'); @@ -38,6 +38,9 @@ $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon'); $value = $block.find('.value'); $loading = $block.find('.block-loading').fadeOut(); + selectedMilestoneDefault = (showAny ? '' : null); + selectedMilestoneDefault = (showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault); + selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault; if (issueUpdateURL) { milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>'); milestoneLinkNoneTemplate = '<span class="no-value">None</span>'; @@ -86,8 +89,18 @@ if (showMenuAbove) { $dropdown.data('glDropdown').positionMenuAbove(); } + $(`[data-milestone-id="${selectedMilestone}"] > a`).addClass('is-active'); }); }, + renderRow: function(milestone) { + return ` + <li data-milestone-id="${milestone.name}"> + <a href='#' class='dropdown-menu-milestone-link'> + ${_.escape(milestone.title)} + </a> + </li> + `; + }, filterable: true, search: { fields: ['title'] @@ -120,15 +133,24 @@ // display:block overrides the hide-collapse rule return $value.css('display', ''); }, + opened: function(e) { + const $el = $(e.currentTarget); + if ($dropdown.hasClass('js-issue-board-sidebar')) { + selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault; + } + $('a.is-active', $el).removeClass('is-active'); + $(`[data-milestone-id="${selectedMilestone}"] > a`, $el).addClass('is-active'); + }, vue: $dropdown.hasClass('js-issue-board-sidebar'), clicked: function(options) { const { $el, e } = options; let selected = options.selectedObj; - - var data, isIssueIndex, isMRIndex, page, boardsStore; + var data, isIssueIndex, isMRIndex, isSelecting, page, boardsStore; page = $('body').data('page'); isIssueIndex = page === 'projects:issues:index'; isMRIndex = (page === page && page === 'projects:merge_requests:index'); + isSelecting = (selected.name !== selectedMilestone); + selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault; if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) { e.preventDefault(); return; @@ -142,16 +164,11 @@ boardsStore[$dropdown.data('field-name')] = selected.name; e.preventDefault(); } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { - if (selected.name != null) { - selectedMilestone = selected.name; - } else { - selectedMilestone = ''; - } return Issuable.filterResults($dropdown.closest('form')); } else if ($dropdown.hasClass('js-filter-submit')) { return $dropdown.closest('form').submit(); } else if ($dropdown.hasClass('js-issue-board-sidebar')) { - if (selected.id !== -1) { + if (selected.id !== -1 && isSelecting) { gl.issueBoards.boardStoreIssueSet('milestone', new ListMilestone({ id: selected.id, title: selected.name diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js index 36bc1257cef..426d7f3288e 100644 --- a/app/assets/javascripts/namespace_select.js +++ b/app/assets/javascripts/namespace_select.js @@ -2,11 +2,9 @@ /* global Api */ (function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; - window.NamespaceSelect = (function() { function NamespaceSelect(opts) { - this.onSelectItem = bind(this.onSelectItem, this); + this.onSelectItem = this.onSelectItem.bind(this); var fieldName, showAny; this.dropdown = opts.dropdown; showAny = true; diff --git a/app/assets/javascripts/new_branch_form.js b/app/assets/javascripts/new_branch_form.js index 67046d52a65..9d614cdee3a 100644 --- a/app/assets/javascripts/new_branch_form.js +++ b/app/assets/javascripts/new_branch_form.js @@ -1,11 +1,8 @@ /* eslint-disable func-names, space-before-function-paren, no-var, one-var, prefer-rest-params, max-len, vars-on-top, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, max-len, object-shorthand */ (function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }, - indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i += 1) { if (i in this && this[i] === item) return i; } return -1; }; - this.NewBranchForm = (function() { function NewBranchForm(form, availableRefs) { - this.validate = bind(this.validate, this); + this.validate = this.validate.bind(this); this.branchNameError = form.find('.js-branch-name-error'); this.name = form.find('.js-branch-name'); this.ref = form.find('#ref'); @@ -95,6 +92,8 @@ NewBranchForm.prototype.validate = function() { var errorMessage, errors, formatter, unique, validator; + const indexOf = [].indexOf; + this.branchNameError.empty(); unique = function(values, value) { if (indexOf.call(values, value) === -1) { diff --git a/app/assets/javascripts/new_commit_form.js b/app/assets/javascripts/new_commit_form.js index ad36f08840d..658879607e2 100644 --- a/app/assets/javascripts/new_commit_form.js +++ b/app/assets/javascripts/new_commit_form.js @@ -1,12 +1,10 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-return-assign, max-len */ (function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; - this.NewCommitForm = (function() { function NewCommitForm(form, targetBranchName = 'target_branch') { this.form = form; this.targetBranchName = targetBranchName; - this.renderDestination = bind(this.renderDestination, this); + this.renderDestination = this.renderDestination.bind(this); this.targetBranchDropdown = form.find('button.js-target-branch'); this.originalBranch = form.find('.js-original-branch'); this.createMergeRequest = form.find('.js-create-merge-request'); diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 194c29f4710..f6fe6d9f0fd 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -22,8 +22,6 @@ const normalizeNewlines = function(str) { }; (function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; - this.Notes = (function() { const MAX_VISIBLE_COMMIT_LIST_COUNT = 3; const REGEX_SLASH_COMMANDS = /\/\w+/g; @@ -31,24 +29,24 @@ const normalizeNewlines = function(str) { Notes.interval = null; function Notes(notes_url, note_ids, last_fetched_at, view) { - this.updateTargetButtons = bind(this.updateTargetButtons, this); - this.updateComment = bind(this.updateComment, this); - this.visibilityChange = bind(this.visibilityChange, this); - this.cancelDiscussionForm = bind(this.cancelDiscussionForm, this); - this.addDiffNote = bind(this.addDiffNote, this); - this.setupDiscussionNoteForm = bind(this.setupDiscussionNoteForm, this); - this.replyToDiscussionNote = bind(this.replyToDiscussionNote, this); - this.removeNote = bind(this.removeNote, this); - this.cancelEdit = bind(this.cancelEdit, this); - this.updateNote = bind(this.updateNote, this); - this.addDiscussionNote = bind(this.addDiscussionNote, this); - this.addNoteError = bind(this.addNoteError, this); - this.addNote = bind(this.addNote, this); - this.resetMainTargetForm = bind(this.resetMainTargetForm, this); - this.refresh = bind(this.refresh, this); - this.keydownNoteText = bind(this.keydownNoteText, this); - this.toggleCommitList = bind(this.toggleCommitList, this); - this.postComment = bind(this.postComment, this); + this.updateTargetButtons = this.updateTargetButtons.bind(this); + this.updateComment = this.updateComment.bind(this); + this.visibilityChange = this.visibilityChange.bind(this); + this.cancelDiscussionForm = this.cancelDiscussionForm.bind(this); + this.addDiffNote = this.addDiffNote.bind(this); + this.setupDiscussionNoteForm = this.setupDiscussionNoteForm.bind(this); + this.replyToDiscussionNote = this.replyToDiscussionNote.bind(this); + this.removeNote = this.removeNote.bind(this); + this.cancelEdit = this.cancelEdit.bind(this); + this.updateNote = this.updateNote.bind(this); + this.addDiscussionNote = this.addDiscussionNote.bind(this); + this.addNoteError = this.addNoteError.bind(this); + this.addNote = this.addNote.bind(this); + this.resetMainTargetForm = this.resetMainTargetForm.bind(this); + this.refresh = this.refresh.bind(this); + this.keydownNoteText = this.keydownNoteText.bind(this); + this.toggleCommitList = this.toggleCommitList.bind(this); + this.postComment = this.postComment.bind(this); this.notes_url = notes_url; this.note_ids = note_ids; diff --git a/app/assets/javascripts/notifications_form.js b/app/assets/javascripts/notifications_form.js index 5005af90d48..2ab9c4fed2c 100644 --- a/app/assets/javascripts/notifications_form.js +++ b/app/assets/javascripts/notifications_form.js @@ -1,10 +1,8 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, newline-per-chained-call, comma-dangle, consistent-return, prefer-arrow-callback, max-len */ (function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; - this.NotificationsForm = (function() { function NotificationsForm() { - this.toggleCheckbox = bind(this.toggleCheckbox, this); + this.toggleCheckbox = this.toggleCheckbox.bind(this); this.removeEventListeners(); this.initEventListeners(); } diff --git a/app/assets/javascripts/pipelines/components/async_button.vue b/app/assets/javascripts/pipelines/components/async_button.vue index d1c60b570de..37a6f02d8fd 100644 --- a/app/assets/javascripts/pipelines/components/async_button.vue +++ b/app/assets/javascripts/pipelines/components/async_button.vue @@ -3,6 +3,7 @@ /* global Flash */ import '~/flash'; import eventHub from '../event_hub'; +import loadingIcon from '../../vue_shared/components/loading_icon.vue'; export default { props: { @@ -37,6 +38,10 @@ export default { }, }, + components: { + loadingIcon, + }, + data() { return { isLoading: false, @@ -94,9 +99,6 @@ export default { <i :class="iconClass" aria-hidden="true" /> - <i - class="fa fa-spinner fa-spin" - aria-hidden="true" - v-if="isLoading" /> + <loading-icon v-if="isLoading" /> </button> </template> diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue index a84161ef5e7..14c98847d93 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue @@ -5,11 +5,13 @@ import PipelineService from '../../services/pipeline_service'; import PipelineStore from '../../stores/pipeline_store'; import stageColumnComponent from './stage_column_component.vue'; + import loadingIcon from '../../../vue_shared/components/loading_icon.vue'; import '../../../flash'; export default { components: { stageColumnComponent, + loadingIcon, }, data() { @@ -64,6 +66,24 @@ capitalizeStageName(name) { return name.charAt(0).toUpperCase() + name.slice(1); }, + + isFirstColumn(index) { + return index === 0; + }, + + stageConnectorClass(index, stage) { + let className; + + // If it's the first stage column and only has one job + if (index === 0 && stage.groups.length === 1) { + className = 'no-margin'; + } else if (index > 0) { + // If it is not the first column + className = 'left-margin'; + } + + return className; + }, }, }; </script> @@ -71,21 +91,22 @@ <div class="build-content middle-block js-pipeline-graph"> <div class="pipeline-visualization pipeline-graph"> <div class="text-center"> - <i + <loading-icon v-if="isLoading" - class="loading-icon fa fa-spin fa-spinner fa-3x" - aria-label="Loading" - aria-hidden="true" /> + size="3" + /> </div> <ul v-if="!isLoading" class="stage-column-list"> <stage-column-component - v-for="stage in state.graph" + v-for="(stage, index) in state.graph" :title="capitalizeStageName(stage.name)" :jobs="stage.groups" - :key="stage.name"/> + :key="stage.name" + :stage-connector-class="stageConnectorClass(index, stage)" + :is-first-column="isFirstColumn(index)"/> </ul> </div> </div> diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue index b7da185e280..9b1bbb0906f 100644 --- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue @@ -13,6 +13,18 @@ export default { type: Array, required: true, }, + + isFirstColumn: { + type: Boolean, + required: false, + default: false, + }, + + stageConnectorClass: { + type: String, + required: false, + default: '', + }, }, components: { @@ -28,20 +40,27 @@ export default { jobId(job) { return `ci-badge-${job.name}`; }, + + buildConnnectorClass(index) { + return index === 0 && !this.isFirstColumn ? 'left-connector' : ''; + }, }, }; </script> <template> - <li class="stage-column"> + <li + class="stage-column" + :class="stageConnectorClass"> <div class="stage-name"> {{title}} </div> <div class="builds-container"> <ul> <li - v-for="job in jobs" + v-for="(job, index) in jobs" :key="job.id" class="build" + :class="buildConnnectorClass(index)" :id="jobId(job)"> <div class="curve"></div> diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.js b/app/assets/javascripts/pipelines/components/pipelines_actions.js index ffda18d2e0f..b9e066c5db1 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_actions.js +++ b/app/assets/javascripts/pipelines/components/pipelines_actions.js @@ -3,6 +3,7 @@ import '~/flash'; import playIconSvg from 'icons/_icon_play.svg'; import eventHub from '../event_hub'; +import loadingIconComponent from '../../vue_shared/components/loading_icon.vue'; export default { props: { @@ -17,6 +18,10 @@ export default { }, }, + components: { + loadingIconComponent, + }, + data() { return { playIconSvg, @@ -65,10 +70,7 @@ export default { <i class="fa fa-caret-down" aria-hidden="true" /> - <i - v-if="isLoading" - class="fa fa-spinner fa-spin" - aria-hidden="true" /> + <loading-icon v-if="isLoading" /> </button> <ul class="dropdown-menu dropdown-menu-align-right"> diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue index 310f44b06df..7fc19fce1ff 100644 --- a/app/assets/javascripts/pipelines/components/stage.vue +++ b/app/assets/javascripts/pipelines/components/stage.vue @@ -15,6 +15,7 @@ /* global Flash */ import { borderlessStatusIconEntityMap } from '../../vue_shared/ci_status_icons'; +import loadingIcon from '../../vue_shared/components/loading_icon.vue'; export default { props: { @@ -38,6 +39,10 @@ export default { }; }, + components: { + loadingIcon, + }, + updated() { if (this.dropdownContent.length > 0) { this.stopDropdownClickPropagation(); @@ -153,15 +158,7 @@ export default { :class="dropdownClass" class="js-builds-dropdown-list scrollable-menu"> - <div - class="text-center" - v-if="isLoading"> - <i - class="fa fa-spin fa-spinner" - aria-hidden="true" - aria-label="Loading"> - </i> - </div> + <loading-icon v-if="isLoading"/> <ul v-else diff --git a/app/assets/javascripts/pipelines/pipelines.js b/app/assets/javascripts/pipelines/pipelines.js index 934bd7deb31..050551e5075 100644 --- a/app/assets/javascripts/pipelines/pipelines.js +++ b/app/assets/javascripts/pipelines/pipelines.js @@ -2,11 +2,12 @@ import Visibility from 'visibilityjs'; import PipelinesService from './services/pipelines_service'; import eventHub from './event_hub'; import PipelinesTableComponent from '../vue_shared/components/pipelines_table'; -import TablePaginationComponent from '../vue_shared/components/table_pagination'; +import tablePagination from '../vue_shared/components/table_pagination.vue'; import EmptyState from './components/empty_state.vue'; import ErrorState from './components/error_state.vue'; import NavigationTabs from './components/navigation_tabs'; import NavigationControls from './components/nav_controls'; +import loadingIcon from '../vue_shared/components/loading_icon.vue'; import Poll from '../lib/utils/poll'; export default { @@ -18,12 +19,13 @@ export default { }, components: { - 'gl-pagination': TablePaginationComponent, + tablePagination, 'pipelines-table-component': PipelinesTableComponent, 'empty-state': EmptyState, 'error-state': ErrorState, 'navigation-tabs': NavigationTabs, 'navigation-controls': NavigationControls, + loadingIcon, }, data() { @@ -244,13 +246,11 @@ export default { <div class="content-list pipelines"> - <div - class="realtime-loading" - v-if="isLoading"> - <i - class="fa fa-spinner fa-spin" - aria-hidden="true" /> - </div> + <loading-icon + label="Loading Pipelines" + size="3" + v-if="isLoading" + /> <empty-state v-if="shouldRenderEmptyState" @@ -275,12 +275,13 @@ export default { /> </div> - <gl-pagination + <table-pagination v-if="shouldRenderPagination" :pagenum="pagenum" :change="change" :count="state.count.all" - :pageInfo="state.pageInfo"/> + :pageInfo="state.pageInfo" + /> </div> </div> `, diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js index e01668eabef..11f9754780d 100644 --- a/app/assets/javascripts/project_find_file.js +++ b/app/assets/javascripts/project_find_file.js @@ -2,18 +2,16 @@ /* global fuzzaldrinPlus */ (function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; - this.ProjectFindFile = (function() { var highlighter; function ProjectFindFile(element1, options) { this.element = element1; this.options = options; - this.goToBlob = bind(this.goToBlob, this); - this.goToTree = bind(this.goToTree, this); - this.selectRowDown = bind(this.selectRowDown, this); - this.selectRowUp = bind(this.selectRowUp, this); + this.goToBlob = this.goToBlob.bind(this); + this.goToTree = this.goToTree.bind(this); + this.selectRowDown = this.selectRowDown.bind(this); + this.selectRowUp = this.selectRowUp.bind(this); this.filePaths = {}; this.inputElement = this.element.find(".file-finder-input"); // init event diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js index e9927c1bf51..04b381fe0e0 100644 --- a/app/assets/javascripts/project_new.js +++ b/app/assets/javascripts/project_new.js @@ -1,11 +1,9 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, one-var, no-underscore-dangle, prefer-template, no-else-return, prefer-arrow-callback, max-len */ (function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; - this.ProjectNew = (function() { function ProjectNew() { - this.toggleSettings = bind(this.toggleSettings, this); + this.toggleSettings = this.toggleSettings.bind(this); this.$selects = $('.features select'); this.$repoSelects = this.$selects.filter('.js-repo-select'); diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index a9b3de281e1..b71c3097706 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -3,11 +3,9 @@ import Cookies from 'js-cookie'; (function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; - this.Sidebar = (function() { function Sidebar(currentUser) { - this.toggleTodo = bind(this.toggleTodo, this); + this.toggleTodo = this.toggleTodo.bind(this); this.sidebar = $('aside'); this.removeListeners(); this.addEventListeners(); diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js index 15f5963353a..39e4006ac4e 100644 --- a/app/assets/javascripts/search.js +++ b/app/assets/javascripts/search.js @@ -1,4 +1,5 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, object-shorthand, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-else-return, max-len */ +/* global Flash */ /* global Api */ (function() { @@ -7,6 +8,7 @@ var $groupDropdown, $projectDropdown; $groupDropdown = $('.js-search-group-dropdown'); $projectDropdown = $('.js-search-project-dropdown'); + this.groupId = $groupDropdown.data('group-id'); this.eventListeners(); $groupDropdown.glDropdown({ selectable: true, @@ -46,14 +48,18 @@ search: { fields: ['name'] }, - data: function(term, callback) { - return Api.projects(term, { order_by: 'id' }, function(data) { - data.unshift({ - name_with_namespace: 'Any' - }); - data.splice(1, 0, 'divider'); - return callback(data); - }); + data: (term, callback) => { + this.getProjectsData(term) + .then((data) => { + data.unshift({ + name_with_namespace: 'Any' + }); + data.splice(1, 0, 'divider'); + + return data; + }) + .then(data => callback(data)) + .catch(() => new Flash('Error fetching projects')); }, id: function(obj) { return obj.id; @@ -95,6 +101,18 @@ return $('.js-search-input').val('').trigger('keyup').focus(); }; + Search.prototype.getProjectsData = function(term) { + return new Promise((resolve) => { + if (this.groupId) { + Api.groupProjects(this.groupId, term, resolve); + } else { + Api.projects(term, { + order_by: 'id', + }, resolve); + } + }); + }; + return Search; })(); }).call(window); diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js index 85659d7fa39..8ac71797c14 100644 --- a/app/assets/javascripts/shortcuts.js +++ b/app/assets/javascripts/shortcuts.js @@ -4,11 +4,9 @@ import findAndFollowLink from './shortcuts_dashboard_navigation'; (function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; - this.Shortcuts = (function() { function Shortcuts(skipResetBindings) { - this.onToggleHelp = bind(this.onToggleHelp, this); + this.onToggleHelp = this.onToggleHelp.bind(this); this.enabledHelp = []; if (!skipResetBindings) { Mousetrap.reset(); diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js index 294d087554e..bacb26734c9 100644 --- a/app/assets/javascripts/single_file_diff.js +++ b/app/assets/javascripts/single_file_diff.js @@ -1,8 +1,6 @@ /* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, consistent-return, no-param-reassign, max-len */ (function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; - window.SingleFileDiff = (function() { var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER; @@ -16,7 +14,7 @@ function SingleFileDiff(file) { this.file = file; - this.toggleDiff = bind(this.toggleDiff, this); + this.toggleDiff = this.toggleDiff.bind(this); this.content = $('.diff-content', this.file); this.$toggleIcon = $('.diff-toggle-caret', this.file); this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path'); diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js index 500b78fc5d8..cd5280948fd 100644 --- a/app/assets/javascripts/u2f/authenticate.js +++ b/app/assets/javascripts/u2f/authenticate.js @@ -10,18 +10,16 @@ (function() { const global = window.gl || (window.gl = {}); - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; - global.U2FAuthenticate = (function() { function U2FAuthenticate(container, form, u2fParams, fallbackButton, fallbackUI) { this.container = container; - this.renderNotSupported = bind(this.renderNotSupported, this); - this.renderAuthenticated = bind(this.renderAuthenticated, this); - this.renderError = bind(this.renderError, this); - this.renderInProgress = bind(this.renderInProgress, this); - this.renderTemplate = bind(this.renderTemplate, this); - this.authenticate = bind(this.authenticate, this); - this.start = bind(this.start, this); + this.renderNotSupported = this.renderNotSupported.bind(this); + this.renderAuthenticated = this.renderAuthenticated.bind(this); + this.renderError = this.renderError.bind(this); + this.renderInProgress = this.renderInProgress.bind(this); + this.renderTemplate = this.renderTemplate.bind(this); + this.authenticate = this.authenticate.bind(this); + this.start = this.start.bind(this); this.appId = u2fParams.app_id; this.challenge = u2fParams.challenge; this.form = form; diff --git a/app/assets/javascripts/u2f/error.js b/app/assets/javascripts/u2f/error.js index fd1829efe18..3119b3480c3 100644 --- a/app/assets/javascripts/u2f/error.js +++ b/app/assets/javascripts/u2f/error.js @@ -2,12 +2,10 @@ /* global u2f */ (function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; - this.U2FError = (function() { function U2FError(errorCode, u2fFlowType) { this.errorCode = errorCode; - this.message = bind(this.message, this); + this.message = this.message.bind(this); this.httpsDisabled = window.location.protocol !== 'https:'; this.u2fFlowType = u2fFlowType; } diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js index 17631f2908d..1234d17b8fd 100644 --- a/app/assets/javascripts/u2f/register.js +++ b/app/assets/javascripts/u2f/register.js @@ -8,19 +8,17 @@ // State Flow #1: setup -> in_progress -> registered -> POST to server // State Flow #2: setup -> in_progress -> error -> setup (function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; - this.U2FRegister = (function() { function U2FRegister(container, u2fParams) { this.container = container; - this.renderNotSupported = bind(this.renderNotSupported, this); - this.renderRegistered = bind(this.renderRegistered, this); - this.renderError = bind(this.renderError, this); - this.renderInProgress = bind(this.renderInProgress, this); - this.renderSetup = bind(this.renderSetup, this); - this.renderTemplate = bind(this.renderTemplate, this); - this.register = bind(this.register, this); - this.start = bind(this.start, this); + this.renderNotSupported = this.renderNotSupported.bind(this); + this.renderRegistered = this.renderRegistered.bind(this); + this.renderError = this.renderError.bind(this); + this.renderInProgress = this.renderInProgress.bind(this); + this.renderSetup = this.renderSetup.bind(this); + this.renderTemplate = this.renderTemplate.bind(this); + this.register = this.register.bind(this); + this.start = this.start.bind(this); this.appId = u2fParams.app_id; this.registerRequests = u2fParams.register_requests; this.signRequests = u2fParams.sign_requests; diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js index 32ffa2f0ac0..b11f691e424 100644 --- a/app/assets/javascripts/users/calendar.js +++ b/app/assets/javascripts/users/calendar.js @@ -3,12 +3,10 @@ import d3 from 'd3'; (function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; - this.Calendar = (function() { function Calendar(timestamps, calendar_activities_path) { this.calendar_activities_path = calendar_activities_path; - this.clickDay = bind(this.clickDay, this); + this.clickDay = this.clickDay.bind(this); this.currentSelectedDate = ''; this.daySpace = 1; this.daySize = 15; diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index 38462782007..452578d5b98 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -6,14 +6,13 @@ window.emitSidebarEvent = window.emitSidebarEvent || $.noop; (function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }, - slice = [].slice; + const slice = [].slice; this.UsersSelect = (function() { function UsersSelect(currentUser, els) { var $els; - this.users = bind(this.users, this); - this.user = bind(this.user, this); + this.users = this.users.bind(this); + this.user = this.user.bind(this); this.usersPath = "/autocomplete/users.json"; this.userPath = "/autocomplete/users/:id.json"; if (currentUser != null) { 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 630e80a7408..3c23b8e472b 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 @@ -108,8 +108,6 @@ export default { </div> <mr-widget-memory-usage v-if="deployment.metrics_url" - :mr="mr" - :service="service" :metricsUrl="deployment.metrics_url" /> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js index 395cc9e91fc..486b13e60af 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js @@ -5,8 +5,6 @@ import MRWidgetService from '../services/mr_widget_service'; export default { name: 'MemoryUsage', props: { - mr: { type: Object, required: true }, - service: { type: Object, required: true }, metricsUrl: { type: String, required: true }, }, data() { @@ -14,6 +12,7 @@ export default { // memoryFrom: 0, // memoryTo: 0, memoryMetrics: [], + deploymentTime: 0, hasMetrics: false, loadFailed: false, loadingMetrics: true, @@ -23,8 +22,22 @@ export default { components: { 'mr-memory-graph': MemoryGraph, }, + computed: { + shouldShowLoading() { + return this.loadingMetrics && !this.hasMetrics && !this.loadFailed; + }, + shouldShowMemoryGraph() { + return !this.loadingMetrics && this.hasMetrics && !this.loadFailed; + }, + shouldShowLoadFailure() { + return !this.loadingMetrics && !this.hasMetrics && this.loadFailed; + }, + shouldShowMetricsUnavailable() { + return !this.loadingMetrics && !this.hasMetrics && !this.loadFailed; + }, + }, methods: { - computeGraphData(metrics) { + computeGraphData(metrics, deploymentTime) { this.loadingMetrics = false; const { memory_values } = metrics; // if (memory_previous.length > 0) { @@ -38,70 +51,73 @@ export default { if (memory_values.length > 0) { this.hasMetrics = true; this.memoryMetrics = memory_values[0].values; + this.deploymentTime = deploymentTime; } }, - }, - mounted() { - this.$props.loadingMetrics = true; - gl.utils.backOff((next, stop) => { - MRWidgetService.fetchMetrics(this.$props.metricsUrl) - .then((res) => { - if (res.status === statusCodes.NO_CONTENT) { - this.backOffRequestCounter = this.backOffRequestCounter += 1; - if (this.backOffRequestCounter < 3) { - next(); + loadMetrics() { + gl.utils.backOff((next, stop) => { + MRWidgetService.fetchMetrics(this.metricsUrl) + .then((res) => { + if (res.status === statusCodes.NO_CONTENT) { + this.backOffRequestCounter = this.backOffRequestCounter += 1; + /* eslint-disable no-unused-expressions */ + this.backOffRequestCounter < 3 ? next() : stop(res); } else { stop(res); } - } else { - stop(res); + }) + .catch(stop); + }) + .then((res) => { + if (res.status === statusCodes.NO_CONTENT) { + return res; } - }) - .catch(stop); - }) - .then((res) => { - if (res.status === statusCodes.NO_CONTENT) { - return res; - } - return res.json(); - }) - .then((res) => { - this.computeGraphData(res.metrics); - return res; - }) - .catch(() => { - this.$props.loadFailed = true; - }); + return res.json(); + }) + .then((res) => { + this.computeGraphData(res.metrics, res.deployment_time); + return res; + }) + .catch(() => { + this.loadFailed = true; + this.loadingMetrics = false; + }); + }, + }, + mounted() { + this.loadingMetrics = true; + this.loadMetrics(); }, template: ` - <div class="mr-info-list mr-memory-usage"> + <div class="mr-info-list clearfix mr-memory-usage js-mr-memory-usage"> <div class="legend"></div> <p - v-if="loadingMetrics" - class="usage-info usage-info-loading"> + v-if="shouldShowLoading" + class="usage-info js-usage-info usage-info-loading"> <i class="fa fa-spinner fa-spin usage-info-load-spinner" aria-hidden="true" />Loading deployment statistics. </p> <p - v-if="!hasMetrics && !loadingMetrics" - class="usage-info usage-info-loading"> - Deployment statistics are not available currently. - </p> - <p - v-if="hasMetrics" - class="usage-info"> + v-if="shouldShowMemoryGraph" + class="usage-info js-usage-info"> Deployment memory usage: </p> <p - v-if="loadFailed" - class="usage-info"> + v-if="shouldShowLoadFailure" + class="usage-info js-usage-info usage-info-failed"> Failed to load deployment statistics. </p> + <p + v-if="shouldShowMetricsUnavailable" + class="usage-info js-usage-info usage-info-unavailable"> + Deployment statistics are not available currently. + </p> <mr-memory-graph - v-if="hasMetrics" + v-if="shouldShowMemoryGraph" :metrics="memoryMetrics" + :deploymentTime="deploymentTime" height="25" width="100" /> </div> diff --git a/app/assets/javascripts/vue_shared/ci_action_icons.js b/app/assets/javascripts/vue_shared/ci_action_icons.js index 734b3c6c45e..ee41dc95beb 100644 --- a/app/assets/javascripts/vue_shared/ci_action_icons.js +++ b/app/assets/javascripts/vue_shared/ci_action_icons.js @@ -1,6 +1,7 @@ import cancelSVG from 'icons/_icon_action_cancel.svg'; import retrySVG from 'icons/_icon_action_retry.svg'; import playSVG from 'icons/_icon_action_play.svg'; +import stopSVG from 'icons/_icon_action_stop.svg'; export default function getActionIcon(action) { let icon; @@ -14,6 +15,9 @@ export default function getActionIcon(action) { case 'icon_action_play': icon = playSVG; break; + case 'icon_action_stop': + icon = stopSVG; + break; default: icon = ''; } diff --git a/app/assets/javascripts/vue_shared/components/loading_icon.vue b/app/assets/javascripts/vue_shared/components/loading_icon.vue new file mode 100644 index 00000000000..41b1d0165b0 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/loading_icon.vue @@ -0,0 +1,33 @@ +<script> + export default { + props: { + label: { + type: String, + required: false, + default: 'Loading', + }, + + size: { + type: String, + required: false, + default: '1', + }, + }, + + computed: { + cssClass() { + return `fa-${this.size}x`; + }, + }, + }; +</script> +<template> + <div class="text-center"> + <i + class="fa fa-spin fa-spinner" + :class="cssClass" + aria-hidden="true" + :aria-label="label"> + </i> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/memory_graph.js b/app/assets/javascripts/vue_shared/components/memory_graph.js index 2a605b24339..643b77e04c7 100644 --- a/app/assets/javascripts/vue_shared/components/memory_graph.js +++ b/app/assets/javascripts/vue_shared/components/memory_graph.js @@ -2,6 +2,7 @@ export default { name: 'MemoryGraph', props: { metrics: { type: Array, required: true }, + deploymentTime: { type: Number, required: true }, width: { type: String, required: true }, height: { type: String, required: true }, }, @@ -9,27 +10,105 @@ export default { return { pathD: '', pathViewBox: '', - // dotX: '', - // dotY: '', + dotX: '', + dotY: '', }; }, + computed: { + getFormattedMedian() { + const deployedSince = gl.utils.getTimeago().format(this.deploymentTime * 1000); + return `Deployed ${deployedSince}`; + }, + }, + methods: { + /** + * Returns metric value index in metrics array + * with timestamp closest to matching median + */ + getMedianMetricIndex(median, metrics) { + let matchIndex = 0; + let timestampDiff = 0; + let smallestDiff = 0; + + const metricTimestamps = metrics.map(v => v[0]); + + // Find metric timestamp which is closest to deploymentTime + timestampDiff = Math.abs(metricTimestamps[0] - median); + metricTimestamps.forEach((timestamp, index) => { + if (index === 0) { // Skip first element + return; + } + + smallestDiff = Math.abs(timestamp - median); + if (smallestDiff < timestampDiff) { + matchIndex = index; + timestampDiff = smallestDiff; + } + }); + + return matchIndex; + }, + + /** + * Get Graph Plotting values to render Line and Dot + */ + getGraphPlotValues(median, metrics) { + const renderData = metrics.map(v => v[1]); + const medianMetricIndex = this.getMedianMetricIndex(median, metrics); + let cx = 0; + let cy = 0; + + // Find Maximum and Minimum values from `renderData` array + const maxMemory = Math.max.apply(null, renderData); + const minMemory = Math.min.apply(null, renderData); + + // Find difference between extreme ends + const diff = maxMemory - minMemory; + const lineWidth = renderData.length; + + // Iterate over metrics values and perform following + // 1. Find x & y co-ords for deploymentTime's memory value + // 2. Return line path against maxMemory + const linePath = renderData.map((y, x) => { + if (medianMetricIndex === x) { + cx = x; + cy = maxMemory - y; + } + return `${x} ${maxMemory - y}`; + }); + + return { + pathD: linePath, + pathViewBox: { + lineWidth, + diff, + }, + dotX: cx, + dotY: cy, + }; + }, + + /** + * Render Graph based on provided median and metrics values + */ + renderGraph(median, metrics) { + const { pathD, pathViewBox, dotX, dotY } = this.getGraphPlotValues(median, metrics); + + // Set props and update graph on UI. + this.pathD = `M ${pathD}`; + this.pathViewBox = `0 0 ${pathViewBox.lineWidth} ${pathViewBox.diff}`; + this.dotX = dotX; + this.dotY = dotY; + }, + }, mounted() { - const renderData = this.$props.metrics.map(v => v[1]); - const maxMemory = Math.max.apply(null, renderData); - const minMemory = Math.min.apply(null, renderData); - const diff = maxMemory - minMemory; - // const cx = 0; - // const cy = 0; - const lineWidth = renderData.length; - const linePath = renderData.map((y, x) => `${x} ${maxMemory - y}`); - this.pathD = `M ${linePath}`; - this.pathViewBox = `0 0 ${lineWidth} ${diff}`; + this.renderGraph(this.deploymentTime, this.metrics); }, template: ` <div class="memory-graph-container"> - <svg :width="width" :height="height" xmlns="http://www.w3.org/2000/svg"> + <svg class="has-tooltip" :title="getFormattedMedian" :width="width" :height="height" xmlns="http://www.w3.org/2000/svg"> <path :d="pathD" :viewBox="pathViewBox" /> - <!--<circle r="0.8" :cx="dotX" :cy="dotY" tranform="translate(0 -1)" /> --> + <circle r="1.5" :cx="dotX" :cy="dotY" tranform="translate(0 -1)" /> </svg> </div> `, diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.js b/app/assets/javascripts/vue_shared/components/table_pagination.vue index ebb14912b00..5e7df22dd83 100644 --- a/app/assets/javascripts/vue_shared/components/table_pagination.js +++ b/app/assets/javascripts/vue_shared/components/table_pagination.vue @@ -1,3 +1,4 @@ +<script> const PAGINATION_UI_BUTTON_LIMIT = 4; const UI_LIMIT = 6; const SPREAD = '...'; @@ -114,22 +115,23 @@ export default { return items; }, }, - template: ` - <div class="gl-pagination"> - <ul class="pagination clearfix"> - <li v-for='item in getItems' - :class='{ - page: item.page, - prev: item.prev, - next: item.next, - separator: item.separator, - active: item.active, - disabled: item.disabled - }' - > - <a @click="changePage($event)">{{item.title}}</a> - </li> - </ul> - </div> - `, }; +</script> +<template> + <div class="gl-pagination"> + <ul class="pagination clearfix"> + <li + v-for="item in getItems" + :class="{ + page: item.page, + prev: item.prev, + next: item.next, + separator: item.separator, + active: item.active, + disabled: item.disabled + }"> + <a @click="changePage($event)">{{item.title}}</a> + </li> + </ul> + </div> +</template> |