Commit 9c7d87ef authored by Achilleas Pipinellis's avatar Achilleas Pipinellis

Merge branch 'master' into 'docs/hide-gcp-install'

# Conflicts:
#   doc/install/README.md
parents 1e0df21a 180ec711
......@@ -145,12 +145,12 @@ gem 'acts-as-taggable-on', '~> 4.0'
# Background jobs
gem 'sidekiq', '~> 5.0'
gem 'sidekiq-cron', '~> 0.4.4'
gem 'sidekiq-cron', '~> 0.6.0'
gem 'redis-namespace', '~> 1.5.2'
gem 'sidekiq-limit_fetch', '~> 3.4'
# Cron Parser
gem 'rufus-scheduler', '~> 3.1.10'
gem 'rufus-scheduler', '~> 3.4'
# HTTP requests
gem 'httparty', '~> 0.13.3'
......
......@@ -181,6 +181,8 @@ GEM
equalizer (0.0.11)
erubis (2.7.0)
escape_utils (1.1.1)
et-orbi (1.0.3)
tzinfo
eventmachine (1.0.8)
excon (0.55.0)
execjs (2.6.0)
......@@ -697,7 +699,8 @@ GEM
rubyntlm (0.5.2)
rubypants (0.2.0)
rubyzip (1.2.1)
rufus-scheduler (3.1.10)
rufus-scheduler (3.4.0)
et-orbi (~> 1.0)
rugged (0.25.1.1)
safe_yaml (1.0.4)
sanitize (2.1.0)
......@@ -734,9 +737,8 @@ GEM
connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0)
redis (~> 3.3, >= 3.3.3)
sidekiq-cron (0.4.4)
redis-namespace (>= 1.5.2)
rufus-scheduler (>= 2.0.24)
sidekiq-cron (0.6.0)
rufus-scheduler (>= 3.3.0)
sidekiq (>= 4.2.1)
sidekiq-limit_fetch (3.4.0)
sidekiq (>= 4)
......@@ -1013,7 +1015,7 @@ DEPENDENCIES
ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.16.2)
ruby_parser (~> 3.8.4)
rufus-scheduler (~> 3.1.10)
rufus-scheduler (~> 3.4)
rugged (~> 0.25.1.1)
sanitize (~> 2.0)
sass-rails (~> 5.0.6)
......@@ -1025,7 +1027,7 @@ DEPENDENCIES
sham_rack (~> 1.3.6)
shoulda-matchers (~> 2.8.0)
sidekiq (~> 5.0)
sidekiq-cron (~> 0.4.4)
sidekiq-cron (~> 0.6.0)
sidekiq-limit_fetch (~> 3.4)
simplecov (~> 0.14.0)
slack-notifier (~> 1.5.1)
......
......@@ -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();
......
......@@ -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,
},
......
......@@ -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(() => {
......
......@@ -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>
......
......@@ -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: {
......
......@@ -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>
......
......@@ -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) {
......
......@@ -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"
......
<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>
......@@ -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"
......
......@@ -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':
......
<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"
......
<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>
......
<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);
......
......@@ -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>
......@@ -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>
......@@ -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>
......
<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"
......
......@@ -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);
......
/* 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>";
......
......@@ -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;
......
......@@ -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();
......
/* 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();
......
/* 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;
......@@ -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();
......
This diff is collapsed.
......@@ -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
......
......@@ -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() {
......
......@@ -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":
......
......@@ -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
......
......@@ -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;
......
/* 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) {
......
/* 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');
......
......@@ -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;
......
/* 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();
}
......
......@@ -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>
......@@ -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>
......
......@@ -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>
......
......@@ -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">
......
......@@ -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
......
......@@ -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>
`,
......
......@@ -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
......
/* 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');
......
......@@ -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();
......
/* 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);
......@@ -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();
......
/* 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');
......
......@@ -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;
......
......@@ -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;
}
......
......@@ -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;
......
......@@ -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;
......
......@@ -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) {
......
......@@ -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>
......
......@@ -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>
......
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 = '';
}
......
<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>
......@@ -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>
`,
......
<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>
......@@ -312,7 +312,7 @@
}
.empty-state {
margin: 100px 0 0;
margin: 5% auto 0;
.text-content {
max-width: 460px;
......@@ -335,27 +335,12 @@
}
.btn {
margin: $btn-side-margin $btn-side-margin 0 0;
}
@media(max-width: $screen-xs-max) {
margin-top: 50px;
text-align: center;
margin: $btn-side-margin 5px;
.btn {
@media(max-width: $screen-xs-max) {
width: 100%;
}
}
@media(min-width: $screen-xs-max) {
&.merge-requests .text-content {
margin-top: 40px;
}
&.labels .text-content {
margin-top: 70px;
}
}
}
.flex-container-block {
......
......@@ -4,13 +4,14 @@
*/
.file-holder {
border: 1px solid $border-color;
border-radius: $border-radius-default;
&.file-holder-no-border {
border: 0;
}
&.readme-holder {
margin: $gl-padding-top 0;
margin: $gl-padding 0;
}
table {
......@@ -25,7 +26,7 @@
text-align: left;
padding: 10px $gl-padding;
word-wrap: break-word;
border-radius: 3px 3px 0 0;
border-radius: $border-radius-default $border-radius-default 0 0;
&.file-title-clear {
padding-left: 0;
......@@ -94,9 +95,16 @@
tr {
border-bottom: 1px solid $blame-border;
&:last-child {
border-bottom: none;
}
}
td {
border-top: none;
border-bottom: none;
&:first-child {
border-left: none;
}
......@@ -107,7 +115,7 @@
}
td.blame-commit {
padding: 0 10px;
padding: 5px 10px;
min-width: 400px;
background: $gray-light;
}
......@@ -246,7 +254,7 @@ span.idiff {
border-bottom: 1px solid $border-color;
padding: 5px $gl-padding;
margin: 0;
border-radius: 3px 3px 0 0;
border-radius: $border-radius-default $border-radius-default 0 0;
.file-header-content {
white-space: nowrap;
......
.memory-graph-container {
svg {
background: $white-light;
cursor: pointer;
&:hover {
box-shadow: 0 0 4px $gray-darkest inset;
}
}
path {
fill: none;
stroke: $blue-500;
stroke-width: 1px;
stroke-width: 2px;
}
circle {
stroke: $blue-700;
fill: $blue-700;
stroke-width: 4px;
}
}
......@@ -163,7 +163,7 @@ $fixed-layout-width: 1280px;
$limited-layout-width: 990px;
$gl-avatar-size: 40px;
$error-exclamation-point: $red-500;
$border-radius-default: 2px;
$border-radius-default: 3px;
$settings-icon-size: 18px;
$provider-btn-not-active-color: $blue-500;
$link-underline-blue: $blue-500;
......
......@@ -163,7 +163,6 @@
.avatar-cell {
width: 46px;
padding-left: 10px;
img {
margin-right: 0;
......@@ -175,7 +174,6 @@
justify-content: space-between;
align-items: flex-start;
flex-grow: 1;
padding-left: 10px;
.merge-request-branches & {
flex-direction: column;
......
// Common
.diff-file {
border: 1px solid $border-color;
margin-bottom: $gl-padding;
border-radius: 3px;
.commit-short-id {
font-family: $regular_font;
font-weight: 400;
}
.diff-header {
position: relative;
background: $gray-light;
border-bottom: 1px solid $border-color;
padding: 10px 16px;
color: $gl-text-color;
z-index: 10;
border-radius: 3px 3px 0 0;
.diff-title {
font-family: $monospace_font;
word-break: break-all;
display: block;
.file-mode {
color: $file-mode-changed;
}
}
.commit-short-id {
font-family: $monospace_font;
font-size: smaller;
}
}
.file-title,
.file-title-flex-parent {
cursor: pointer;
......
......@@ -5,11 +5,6 @@
}
}
.environments-list-loading {
width: 100%;
font-size: 34px;
}
.environments-folder-name {
font-weight: normal;
padding-top: 20px;
......
......@@ -51,6 +51,7 @@ ul.related-merge-requests > li {
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
align-items: center;
.merge-request-id {
flex-shrink: 0;
......@@ -59,6 +60,14 @@ ul.related-merge-requests > li {
.merge-request-info {
margin-left: 5px;
}
.row_title {
vertical-align: bottom;
}
gl-emoji {
font-size: 1em;
}
}
.merge-requests-title,
......@@ -114,7 +123,6 @@ ul.related-merge-requests > li {
.related-merge-requests {
.ci-status-link {
display: block;
margin-top: 3px;
margin-right: 5px;
}
......
......@@ -131,12 +131,6 @@
line-height: 16px;
}
@media (min-width: $screen-sm-min) {
.stage-cell {
padding: 0 4px;
}
}
@media (max-width: $screen-xs-max) {
order: 1;
margin-top: $gl-padding-top;
......@@ -182,8 +176,7 @@
}
&.mr-memory-usage {
margin-top: 10px;
margin-bottom: 10px;
margin: 5px 0 10px 25px;
}
}
......@@ -511,7 +504,12 @@
.mr-info-list.mr-memory-usage {
.legend {
height: 75%;
height: 65%;
top: 0;
@media (max-width: $screen-xs-max) {
height: 20px;
}
}
p {
......@@ -731,13 +729,15 @@
}
.mr-memory-usage {
p.usage-info-loading {
margin-bottom: 6px;
p.usage-info-loading,
p.usage-info-unavailable,
p.usage-info-failed {
margin-bottom: 5px;
}
.usage-info-load-spinner {
margin-right: 10px;
font-size: 16px;
}
p.usage-info-loading .usage-info-load-spinner {
margin-right: 10px;
font-size: 16px;
}
@media (max-width: $screen-md-min) {
......
......@@ -284,10 +284,6 @@ ul.notes {
}
}
.diff-header > span {
margin-right: 10px;
}
.line_content {
white-space: pre-wrap;
}
......
.pipelines {
.realtime-loading {
font-size: 40px;
text-align: center;
margin: 0 auto;
}
.stage {
max-width: 90px;
width: 90px;
......@@ -14,10 +8,6 @@
white-space: nowrap;
}
.empty-state {
margin: 5% auto 0;
}
.table-holder {
width: 100%;
......@@ -257,7 +247,7 @@
.stage-cell {
font-size: 0;
padding: 10px 4px;
padding: 0 4px;
> .stage-container > div > button > span > svg,
> .stage-container > button > svg {
......@@ -384,9 +374,9 @@
content: '';
position: absolute;
top: 48%;
left: -48px;
left: -44px;
border-top: 2px solid $border-color;
width: 48px;
width: 44px;
height: 1px;
}
}
......@@ -486,7 +476,7 @@
color: $gl-text-color-secondary;
// Action Icons in big pipeline-graph nodes
> div > .ci-action-icon-container .ci-action-icon-wrapper {
.ci-action-icon-container .ci-action-icon-wrapper {
height: 30px;
width: 30px;
background: $white-light;
......@@ -511,7 +501,7 @@
}
}
> div > .ci-action-icon-container {
.ci-action-icon-container {
position: absolute;
right: 5px;
top: 5px;
......@@ -541,7 +531,7 @@
}
}
> div > .build-content {
.build-content {
display: inline-block;
padding: 8px 10px 9px;
width: 100%;
......
......@@ -138,11 +138,12 @@
.blob-commit-info {
list-style: none;
background: $gray-light;
padding: 16px 16px 16px 6px;
border: 1px solid $border-color;
border-bottom: none;
margin: 0;
padding: 0;
}
.blob-content-holder {
margin-top: $gl-padding;
}
.blob-upload-dropzone-previews {
......
......@@ -47,7 +47,7 @@ module IssuableCollections
end
def merge_requests_collection
merge_requests_finder.execute.preload(:source_project, :target_project, :author, :assignee, :labels, :milestone, :merge_request_diff, target_project: :namespace)
merge_requests_finder.execute.preload(:source_project, :target_project, :author, :assignee, :labels, :milestone, :merge_request_diff, :head_pipeline, target_project: :namespace)
end
def issues_finder
......
class Dashboard::SnippetsController < Dashboard::ApplicationController
def index
@snippets = SnippetsFinder.new.execute(
@snippets = SnippetsFinder.new(
current_user,
filter: :by_user,
user: current_user,
author: current_user,
scope: params[:scope]
)
).execute
@snippets = @snippets.page(params[:page])
end
end
class Explore::GroupsController < Explore::ApplicationController
def index
@groups = GroupsFinder.new.execute(current_user)
@groups = GroupsFinder.new(current_user).execute
@groups = @groups.search(params[:filter_groups]) if params[:filter_groups].present?
@groups = @groups.sort(@sort = params[:sort])
@groups = @groups.page(params[:page])
......
class Explore::SnippetsController < Explore::ApplicationController
def index
@snippets = SnippetsFinder.new.execute(current_user, filter: :all)
@snippets = SnippetsFinder.new(current_user).execute
@snippets = @snippets.page(params[:page])
end
end
......@@ -64,7 +64,7 @@ class GroupsController < Groups::ApplicationController
end
def subgroups
@nested_groups = group.children
@nested_groups = GroupsFinder.new(current_user, parent: group).execute
@nested_groups = @nested_groups.search(params[:filter_groups]) if params[:filter_groups].present?
end
......
......@@ -227,7 +227,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue
# The Sortable default scope causes performance issues when used with find_by
@noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take || redirect_old
@noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take!
end
alias_method :subscribable_resource, :issue
alias_method :issuable, :issue
......@@ -266,21 +266,6 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
# Since iids are implemented only in 6.1
# user may navigate to issue page using old global ids.
#
# To prevent 404 errors we provide a redirect to correct iids until 7.0 release
#
def redirect_old
issue = @project.issues.find_by(id: params[:id])
if issue
redirect_to issue_path(issue)
else
raise ActiveRecord::RecordNotFound.new
end
end
def issue_params
params.require(:issue).permit(
:title, :assignee_id, :position, :description, :confidential,
......
......@@ -410,10 +410,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
metrics_url =
if can?(current_user, :read_environment, environment) && environment.has_metrics?
metrics_namespace_project_environment_path(environment.project.namespace,
environment.project,
environment,
deployment)
metrics_namespace_project_environment_deployment_path(environment.project.namespace,
environment.project,
environment,
deployment)
end
{
......
......@@ -23,12 +23,11 @@ class Projects::SnippetsController < Projects::ApplicationController
respond_to :html
def index
@snippets = SnippetsFinder.new.execute(
@snippets = SnippetsFinder.new(
current_user,
filter: :by_project,
project: @project,
scope: params[:scope]
)
).execute
@snippets = @snippets.page(params[:page])
if @snippets.out_of_range? && @snippets.total_pages != 0
redirect_to namespace_project_snippets_path(page: @snippets.total_pages)
......
......@@ -27,12 +27,8 @@ class SnippetsController < ApplicationController
return render_404 unless @user
@snippets = SnippetsFinder.new.execute(current_user, {
filter: :by_user,
user: @user,
scope: params[:scope]
})
.page(params[:page])
@snippets = SnippetsFinder.new(current_user, author: @user, scope: params[:scope])
.execute.page(params[:page])
render 'index'
else
......@@ -103,20 +99,20 @@ class SnippetsController < ApplicationController
protected
def snippet
@snippet ||= if current_user
PersonalSnippet.where("author_id = ? OR visibility_level IN (?)",
current_user.id,
[Snippet::PUBLIC, Snippet::INTERNAL]).
find(params[:id])
else
PersonalSnippet.find(params[:id])
end
@snippet ||= PersonalSnippet.find_by(id: params[:id])
end
alias_method :awardable, :snippet
alias_method :spammable, :snippet
def authorize_read_snippet!
authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet)
return if can?(current_user, :read_personal_snippet, @snippet)
if current_user
render_404
else
authenticate_user!
end
end
def authorize_update_snippet!
......
......@@ -128,12 +128,11 @@ class UsersController < ApplicationController
end
def load_snippets
@snippets = SnippetsFinder.new.execute(
@snippets = SnippetsFinder.new(
current_user,
filter: :by_user,
user: user,
author: user,
scope: params[:scope]
).page(params[:page])
).execute.page(params[:page])
end
def projects_for_current_user
......
class GroupsFinder < UnionFinder
def execute(current_user = nil)
segments = all_groups(current_user)
def initialize(current_user = nil, params = {})
@current_user = current_user
@params = params
end
find_union(segments, Group).with_route.order_id_desc
def execute
groups = find_union(all_groups, Group).with_route.order_id_desc
by_parent(groups)
end
private
def all_groups(current_user)
attr_reader :current_user, :params
def all_groups
groups = []
groups << current_user.authorized_groups if current_user
......@@ -15,4 +21,10 @@ class GroupsFinder < UnionFinder
groups
end
def by_parent(groups)
return groups unless params[:parent]
groups.where(parent: params[:parent])
end
end
......@@ -67,7 +67,7 @@ class NotesFinder
when "merge_request"
MergeRequestsFinder.new(@current_user, project_id: @project.id).execute
when "snippet", "project_snippet"
SnippetsFinder.new.execute(@current_user, filter: :by_project, project: @project)
SnippetsFinder.new(@current_user, project: @project).execute
when "personal_snippet"
PersonalSnippet.all
else
......
class SnippetsFinder
def execute(current_user, params = {})
filter = params[:filter]
user = params.fetch(:user, current_user)
case filter
when :all then
snippets(current_user).fresh
when :public then
Snippet.are_public.fresh
when :by_user then
by_user(current_user, user, params[:scope])
when :by_project
by_project(current_user, params[:project], params[:scope])
end
class SnippetsFinder < UnionFinder
attr_accessor :current_user, :params
def initialize(current_user, params = {})
@current_user = current_user
@params = params
end
def execute
items = init_collection
items = by_project(items)
items = by_author(items)
items = by_visibility(items)
items.fresh
end
private
def snippets(current_user)
if current_user
Snippet.public_and_internal
else
# Not authenticated
#
# Return only:
# public snippets
Snippet.are_public
end
def init_collection
items = Snippet.all
accessible(items)
end
def by_user(current_user, user, scope)
snippets = user.snippets.fresh
def accessible(items)
segments = []
segments << items.public_to_user(current_user)
segments << authorized_to_user(items) if current_user
if current_user
include_private = user == current_user
by_scope(snippets, scope, include_private)
else
snippets.are_public
end
find_union(segments, Snippet)
end
def by_project(current_user, project, scope)
snippets = project.snippets.fresh
def authorized_to_user(items)
items.where(
'author_id = :author_id
OR project_id IN (:project_ids)',
author_id: current_user.id,
project_ids: current_user.authorized_projects.select(:id))
end
if current_user
include_private = project.team.member?(current_user) || current_user.admin?
by_scope(snippets, scope, include_private)
else
snippets.are_public
end
def by_visibility(items)
visibility = params[:visibility] || visibility_from_scope
return items unless visibility
items.where(visibility_level: visibility)
end
def by_author(items)
return items unless params[:author]
items.where(author_id: params[:author].id)
end
def by_project(items)
return items unless params[:project]
items.where(project_id: params[:project].id)
end
def by_scope(snippets, scope = nil, include_private = false)
case scope.to_s
def visibility_from_scope
case params[:scope].to_s
when 'are_private'
include_private ? snippets.are_private : Snippet.none
Snippet::PRIVATE
when 'are_internal'
snippets.are_internal
Snippet::INTERNAL
when 'are_public'
snippets.are_public
Snippet::PUBLIC
else
include_private ? snippets : snippets.public_and_internal
nil
end
end
end
......@@ -77,7 +77,7 @@ module ApplicationHelper
end
if user
user.avatar_url(size) || default_avatar
user.avatar_url(size: size) || default_avatar
else
gravatar_icon(user_or_email, size, scale)
end
......
......@@ -18,7 +18,7 @@ module BlobHelper
blob = options.delete(:blob)
blob ||= project.repository.blob_at(ref, path) rescue nil
return unless blob
return unless blob && blob.readable_text?
common_classes = "btn js-edit-blob #{options[:extra_class]}"
......
......@@ -100,17 +100,15 @@ module CommitsHelper
end
def link_to_browse_code(project, commit)
return unless current_controller?(:projects, :commits)
if @path.blank?
return link_to(
"Browse Files",
namespace_project_tree_path(project.namespace, project, commit),
class: "btn btn-default"
)
end
return unless current_controller?(:projects, :commits)
if @repo.blob_at(commit.id, @path)
elsif @repo.blob_at(commit.id, @path)
return link_to(
"Browse File",
namespace_project_blob_path(project.namespace, project,
......
......@@ -116,13 +116,13 @@ module MarkupHelper
if gitlab_markdown?(file_name)
markdown_unsafe(text, context)
elsif asciidoc?(file_name)
asciidoc_unsafe(text)
asciidoc_unsafe(text, context)
elsif plain?(file_name)
content_tag :pre, class: 'plain-readme' do
text
end
else
other_markup_unsafe(file_name, text)
other_markup_unsafe(file_name, text, context)
end
rescue RuntimeError
simple_format(text)
......@@ -217,12 +217,12 @@ module MarkupHelper
Banzai.render(text, context)
end
def asciidoc_unsafe(text)
Gitlab::Asciidoc.render(text)
def asciidoc_unsafe(text, context = {})
Gitlab::Asciidoc.render(text, context)
end
def other_markup_unsafe(file_name, text)
Gitlab::OtherMarkup.render(file_name, text)
def other_markup_unsafe(file_name, text, context = {})
Gitlab::OtherMarkup.render(file_name, text, context)
end
def prepare_for_rendering(html, context = {})
......
module SubmoduleHelper
include Gitlab::ShellAdapter
VALID_SUBMODULE_PROTOCOLS = %w[http https git ssh].freeze
# links to files listing for submodule if submodule is a project on this server
def submodule_links(submodule_item, ref = nil, repository = @repository)
url = repository.submodule_url_for(ref, submodule_item.path)
return url, nil unless url =~ /([^\/:]+)\/([^\/]+(?:\.git)?)\Z/
namespace = $1
project = $2
project.chomp!('.git')
if url =~ /([^\/:]+)\/([^\/]+(?:\.git)?)\Z/
namespace, project = $1, $2
project.sub!(/\.git\z/, '')
if self_url?(url, namespace, project)
return namespace_project_path(namespace, project),
namespace_project_tree_path(namespace, project,
submodule_item.id)
elsif relative_self_url?(url)
relative_self_links(url, submodule_item.id)
elsif github_dot_com_url?(url)
standard_links('github.com', namespace, project, submodule_item.id)
elsif gitlab_dot_com_url?(url)
standard_links('gitlab.com', namespace, project, submodule_item.id)
if self_url?(url, namespace, project)
[namespace_project_path(namespace, project),
namespace_project_tree_path(namespace, project, submodule_item.id)]
elsif relative_self_url?(url)
relative_self_links(url, submodule_item.id)
elsif github_dot_com_url?(url)
standard_links('github.com', namespace, project, submodule_item.id)
elsif gitlab_dot_com_url?(url)
standard_links('gitlab.com', namespace, project, submodule_item.id)
else
[sanitize_submodule_url(url), nil]
end
else
return url, nil
[sanitize_submodule_url(url), nil]
end
end
......@@ -73,4 +75,16 @@ module SubmoduleHelper
namespace_project_tree_path(namespace, base, commit)
]
end
def sanitize_submodule_url(url)
uri = URI.parse(url)
if uri.scheme.in?(VALID_SUBMODULE_PROTOCOLS)
uri.to_s
else
nil
end
rescue URI::InvalidURIError
nil
end
end
......@@ -81,7 +81,7 @@ module TreeHelper
part_path = ""
parts = @path.split('/')
yield('..', nil) if parts.count > max_links
yield('..', File.join(*parts.first(parts.count - 2))) if parts.count > max_links
parts.each do |part|
part_path = File.join(part_path, part) unless part_path.empty?
......
......@@ -18,6 +18,10 @@ module Ci
has_many :builds, foreign_key: :commit_id
has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id
# Merge requests for which the current pipeline is running against
# the merge request's latest commit.
has_many :merge_requests, foreign_key: "head_pipeline_id"
has_many :pending_builds, -> { pending }, foreign_key: :commit_id, class_name: 'Ci::Build'
has_many :retryable_builds, -> { latest.failed_or_canceled }, foreign_key: :commit_id, class_name: 'Ci::Build'
has_many :cancelable_statuses, -> { cancelable }, foreign_key: :commit_id, class_name: 'CommitStatus'
......@@ -381,14 +385,6 @@ module Ci
project.execute_services(data, :pipeline_hooks)
end
# Merge requests for which the current pipeline is running against
# the merge request's latest commit.
def merge_requests
@merge_requests ||= project.merge_requests
.where(source_branch: self.ref)
.select { |merge_request| merge_request.head_pipeline.try(:id) == self.id }
end
# All the merge requests for which the current pipeline runs/ran against
def all_merge_requests
@all_merge_requests ||= project.merge_requests.where(source_branch: ref)
......
......@@ -336,6 +336,8 @@ class Commit
end
end
delegate :deltas, to: :raw, prefix: :raw
def diffs(diff_options = nil)
Gitlab::Diff::FileCollection::Commit.new(self, diff_options: diff_options)
end
......@@ -373,7 +375,7 @@ class Commit
def repo_changes
changes = { added: [], modified: [], removed: [] }
raw_diffs(deltas_only: true).each do |diff|
raw_deltas.each do |diff|
if diff.deleted_file
changes[:removed] << diff.old_path
elsif diff.renamed_file || diff.new_file
......
module Avatarable
extend ActiveSupport::Concern
def avatar_path(only_path: true)
return unless self[:avatar].present?
# If only_path is true then use the relative path of avatar.
# Otherwise use full path (including host).
asset_host = ActionController::Base.asset_host
gitlab_host = only_path ? gitlab_config.relative_url_root : gitlab_config.url
# If asset_host is set then it is expected that assets are handled by a standalone host.
# That means we do not want to get GitLab's relative_url_root option anymore.
host = asset_host.present? ? asset_host : gitlab_host
[host, avatar.url].join
end
end
......@@ -4,6 +4,7 @@ class Group < Namespace
include Gitlab::ConfigHelper
include Gitlab::VisibilityLevel
include AccessRequestable
include Avatarable
include Referable
include SelectForProjectAuthorization
......@@ -111,10 +112,10 @@ class Group < Namespace
allowed_by_projects
end
def avatar_url(size = nil)
if self[:avatar].present?
[gitlab_config.url, avatar.url].join
end
def avatar_url(**args)
# We use avatar_path instead of overriding avatar_url because of carrierwave.
# See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11001/diffs#note_28659864
avatar_path(args)
end
def lfs_enabled?
......
......@@ -13,6 +13,8 @@ class MergeRequest < ActiveRecord::Base
has_one :merge_request_diff,
-> { order('merge_request_diffs.id DESC') }
belongs_to :head_pipeline, foreign_key: "head_pipeline_id", class_name: "Ci::Pipeline"
has_many :events, as: :target, dependent: :destroy
has_many :merge_requests_closing_issues, class_name: 'MergeRequestsClosingIssues', dependent: :delete_all
......@@ -829,12 +831,6 @@ class MergeRequest < ActiveRecord::Base
diverged_commits_count > 0
end
def head_pipeline
return unless diff_head_sha && source_project
@head_pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha)
end
def all_pipelines
return Ci::Pipeline.none unless source_project
......
......@@ -6,6 +6,7 @@ class Project < ActiveRecord::Base
include Gitlab::VisibilityLevel
include Gitlab::CurrentSettings
include AccessRequestable
include Avatarable
include CacheMarkdownField
include Referable
include Sortable
......@@ -798,12 +799,10 @@ class Project < ActiveRecord::Base
repository.avatar
end
def avatar_url
if self[:avatar].present?
[gitlab_config.url, avatar.url].join
elsif avatar_in_git
Gitlab::Routing.url_helpers.namespace_project_avatar_url(namespace, self)
end
def avatar_url(**args)
# We use avatar_path instead of overriding avatar_url because of carrierwave.
# See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11001/diffs#note_28659864
avatar_path(args) || (Gitlab::Routing.url_helpers.namespace_project_avatar_url(namespace, self) if avatar_in_git)
end
# For compatibility with old code
......
class ReadmeBlob < SimpleDelegator
attr_reader :repository
def initialize(blob, repository)
@repository = repository
super(blob)
end
def rendered_markup
repository.rendered_readme
end
end
......@@ -518,7 +518,7 @@ class Repository
def readme
if head = tree(:head)
head.readme
ReadmeBlob.new(head.readme, self)
end
end
......
......@@ -152,18 +152,5 @@ class Snippet < ActiveRecord::Base
where(table[:content].matches(pattern))
end
def accessible_to(user)
return are_public unless user.present?
return all if user.admin?
where(
'visibility_level IN (:visibility_levels)
OR author_id = :author_id
OR project_id IN (:project_ids)',
visibility_levels: [Snippet::PUBLIC, Snippet::INTERNAL],
author_id: user.id,
project_ids: user.authorized_projects.select(:id))
end
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment