Commit 729d2ea0 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into ci-tables-ui-improvements

* master: (196 commits)
  Add quotes to coverage pattern
  Update CHANGELOG.md
  Bump omniauth to 1.4.2
  Bump Hashie to 3.5.5 to eliminate warning noise
  use single backticks for inline code
  Add performance query regression fix for !9088 affecting #27267
  Fix spec
  API: Return 400 for all validation erros in the mebers API
  Adds confirmation for cancel button
  Add newline
  Corrected indentation on the template string
  Adds backoff algo from EE to CE
  We don't need these checks anymore
  Raise error when no content is provided
  Address review
  Update API v3 in line with v4
  Fix new offenses
  Fix spec
  Fix specs
  Rename commit_file, commit_dir and remove_file and update specs
  ...
parents f95d46c9 c425f366
......@@ -51,3 +51,4 @@ eslint-report.html
/builds/*
/shared/*
/.gitlab_workhorse_secret
/webpack-report/
......@@ -240,6 +240,25 @@ rake db:seed_fu:
paths:
- log/development.log
rake gitlab:assets:compile:
stage: test
<<: *dedicated-runner
dependencies: []
variables:
NODE_ENV: "production"
RAILS_ENV: "production"
SETUP_DB: "false"
USE_DB: "false"
SKIP_STORAGE_VALIDATION: "true"
WEBPACK_REPORT: "true"
script:
- bundle exec rake yarn:install gitlab:assets:compile
artifacts:
name: webpack-report
expire_in: 31d
paths:
- webpack-report/
rake karma:
cache:
paths:
......@@ -388,6 +407,7 @@ pages:
dependencies:
- coverage
- rake karma
- rake gitlab:assets:compile
- lint:javascript:report
script:
- mv public/ .public/
......@@ -395,6 +415,7 @@ pages:
- mv coverage/ public/coverage-ruby/ || true
- mv coverage-javascript/ public/coverage-javascript/ || true
- mv eslint-report.html public/ || true
- mv webpack-report/ public/webpack-report/ || true
artifacts:
paths:
- public
......
This diff is collapsed.
This diff is collapsed.
......@@ -426,7 +426,7 @@ merge request:
1. [Ruby](https://github.com/bbatsov/ruby-style-guide).
Important sections include [Source Code Layout][rss-source] and
[Naming][rss-naming]. Use:
- multi-line method chaining style **Option B**: dot `.` on previous line
- multi-line method chaining style **Option A**: dot `.` on the second line
- string literal quoting style **Option A**: single quoted by default
1. [Rails](https://github.com/bbatsov/rails-style-guide)
1. [Newlines styleguide][newlines-styleguide]
......
......@@ -20,7 +20,7 @@ gem 'rugged', '~> 0.24.0'
# Authentication libraries
gem 'devise', '~> 4.2'
gem 'doorkeeper', '~> 4.2.0'
gem 'omniauth', '~> 1.3.2'
gem 'omniauth', '~> 1.4.2'
gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6'
gem 'omniauth-cas3', '~> 1.1.2'
......@@ -201,7 +201,7 @@ gem 'babosa', '~> 1.0.2'
gem 'loofah', '~> 2.0.3'
# Working with license
gem 'licensee', '~> 8.0.0'
gem 'licensee', '~> 8.7.0'
# Protect against bruteforcing
gem 'rack-attack', '~> 4.4.1'
......@@ -301,10 +301,10 @@ group :development, :test do
gem 'spring-commands-rspec', '~> 1.0.4'
gem 'spring-commands-spinach', '~> 1.1.0'
gem 'rubocop', '~> 0.46.0', require: false
gem 'rubocop-rspec', '~> 1.9.1', require: false
gem 'rubocop', '~> 0.47.1', require: false
gem 'rubocop-rspec', '~> 1.12.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false
gem 'haml_lint', '~> 0.18.2', require: false
gem 'haml_lint', '~> 0.21.0', require: false
gem 'simplecov', '0.12.0', require: false
gem 'flay', '~> 2.6.1', require: false
gem 'bundler-audit', '~> 0.5.0', require: false
......
......@@ -319,16 +319,16 @@ GEM
multi_json (>= 1.3.2)
haml (4.0.7)
tilt
haml_lint (0.18.2)
haml_lint (0.21.0)
haml (~> 4.0)
rake (>= 10, < 12)
rubocop (>= 0.36.0)
rake (>= 10, < 13)
rubocop (>= 0.47.0)
sysexits (~> 1.1)
hamlit (2.6.1)
temple (~> 0.7.6)
thor
tilt
hashie (3.4.4)
hashie (3.5.5)
health_check (2.2.1)
rails (>= 4.0)
hipchat (1.5.2)
......@@ -398,8 +398,8 @@ GEM
rubyzip
thor
xml-simple
licensee (8.0.0)
rugged (>= 0.24b)
licensee (8.7.0)
rugged (~> 0.24)
little-plugger (1.1.4)
logging (2.1.0)
little-plugger (~> 1.1)
......@@ -441,7 +441,7 @@ GEM
octokit (4.6.2)
sawyer (~> 0.8.0, >= 0.5.3)
oj (2.17.4)
omniauth (1.3.2)
omniauth (1.4.2)
hashie (>= 1.2, < 4)
rack (>= 1.0, < 3)
omniauth-auth0 (1.4.1)
......@@ -501,7 +501,7 @@ GEM
os (0.9.6)
paranoia (2.2.0)
activerecord (>= 4.0, < 5.1)
parser (2.3.1.4)
parser (2.4.0.0)
ast (~> 2.2)
pg (0.18.4)
poltergeist (1.9.0)
......@@ -642,13 +642,13 @@ GEM
pg
rails
sqlite3
rubocop (0.46.0)
parser (>= 2.3.1.1, < 3.0)
rubocop (0.47.1)
parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
rubocop-rspec (1.9.1)
rubocop-rspec (1.12.0)
rubocop (>= 0.42.0)
ruby-fogbugz (0.2.1)
crack (~> 0.4)
......@@ -759,7 +759,7 @@ GEM
rack (>= 1, < 3)
thor (0.19.4)
thread_safe (0.3.5)
tilt (2.0.5)
tilt (2.0.6)
timecop (0.8.1)
timfel-krb5-auth (0.8.3)
tool (0.2.3)
......@@ -776,7 +776,7 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.7.2)
unicode-display_width (1.1.1)
unicode-display_width (1.1.3)
unicorn (5.1.0)
kgio (~> 2.6)
raindrops (~> 0.7)
......@@ -888,7 +888,7 @@ DEPENDENCIES
google-api-client (~> 0.8.6)
grape (~> 0.18.0)
grape-entity (~> 0.6.0)
haml_lint (~> 0.18.2)
haml_lint (~> 0.21.0)
hamlit (~> 2.6.1)
health_check (~> 2.2.0)
hipchat (~> 1.5.0)
......@@ -907,7 +907,7 @@ DEPENDENCIES
kubeclient (~> 2.2.0)
letter_opener_web (~> 1.3.0)
license_finder (~> 2.1.0)
licensee (~> 8.0.0)
licensee (~> 8.7.0)
loofah (~> 2.0.3)
mail_room (~> 0.9.1)
method_source (~> 0.8)
......@@ -920,7 +920,7 @@ DEPENDENCIES
oauth2 (~> 1.2.0)
octokit (~> 4.6.2)
oj (~> 2.17.4)
omniauth (~> 1.3.2)
omniauth (~> 1.4.2)
omniauth-auth0 (~> 1.4.1)
omniauth-authentiq (~> 0.3.0)
omniauth-azure-oauth2 (~> 0.0.6)
......@@ -963,8 +963,8 @@ DEPENDENCIES
rspec-rails (~> 3.5.0)
rspec-retry (~> 0.4.5)
rspec_profiling (~> 0.0.5)
rubocop (~> 0.46.0)
rubocop-rspec (~> 1.9.1)
rubocop (~> 0.47.1)
rubocop-rspec (~> 1.12.0)
ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.16.2)
rugged (~> 0.24.0)
......
class AjaxLoadingSpinner {
static init() {
const $elements = $('.js-ajax-loading-spinner');
$elements.on('ajax:beforeSend', AjaxLoadingSpinner.ajaxBeforeSend);
$elements.on('ajax:complete', AjaxLoadingSpinner.ajaxComplete);
}
static ajaxBeforeSend(e) {
e.target.setAttribute('disabled', '');
const iconElement = e.target.querySelector('i');
// get first fa- icon
const originalIcon = iconElement.className.match(/(fa-)([^\s]+)/g).first();
iconElement.dataset.icon = originalIcon;
AjaxLoadingSpinner.toggleLoadingIcon(iconElement);
$(e.target).off('ajax:beforeSend', AjaxLoadingSpinner.ajaxBeforeSend);
}
static ajaxComplete(e) {
e.target.removeAttribute('disabled');
const iconElement = e.target.querySelector('i');
AjaxLoadingSpinner.toggleLoadingIcon(iconElement);
$(e.target).off('ajax:complete', AjaxLoadingSpinner.ajaxComplete);
}
static toggleLoadingIcon(iconElement) {
const classList = iconElement.classList;
classList.toggle(iconElement.dataset.icon);
classList.toggle('fa-spinner');
classList.toggle('fa-spin');
}
}
window.gl = window.gl || {};
gl.AjaxLoadingSpinner = AjaxLoadingSpinner;
......@@ -6,12 +6,8 @@
/* global AwardsHandler */
/* global Aside */
function requireAll(context) { return context.keys().map(context); }
window.$ = window.jQuery = require('jquery');
require('jquery-ui/ui/autocomplete');
require('jquery-ui/ui/draggable');
require('jquery-ui/ui/effect-highlight');
require('jquery-ui/ui/sortable');
require('jquery-ujs');
require('vendor/jquery.endless-scroll');
......@@ -46,15 +42,176 @@ require('./shortcuts_dashboard_navigation');
require('./shortcuts_issuable');
require('./shortcuts_network');
require('vendor/jquery.nicescroll');
requireAll(require.context('./behaviors', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./blob', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./templates', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./commit', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./extensions', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./lib/utils', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./u2f', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./droplab', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('.', false, /^\.\/(?!application\.js).*\.(js|es6)$/));
// behaviors
require('./behaviors/autosize');
require('./behaviors/details_behavior');
require('./behaviors/quick_submit');
require('./behaviors/requires_input');
require('./behaviors/toggler_behavior');
// blob
require('./blob/blob_ci_yaml');
require('./blob/blob_dockerfile_selector');
require('./blob/blob_dockerfile_selectors');
require('./blob/blob_file_dropzone');
require('./blob/blob_gitignore_selector');
require('./blob/blob_gitignore_selectors');
require('./blob/blob_license_selector');
require('./blob/blob_license_selectors');
require('./blob/template_selector');
// templates
require('./templates/issuable_template_selector');
require('./templates/issuable_template_selectors');
// commit
require('./commit/file.js');
require('./commit/image_file.js');
// extensions
require('./extensions/array');
require('./extensions/custom_event');
require('./extensions/element');
require('./extensions/jquery');
require('./extensions/object');
// lib/utils
require('./lib/utils/animate');
require('./lib/utils/bootstrap_linked_tabs');
require('./lib/utils/common_utils');
require('./lib/utils/datetime_utility');
require('./lib/utils/notify');
require('./lib/utils/pretty_time');
require('./lib/utils/text_utility');
require('./lib/utils/type_utility');
require('./lib/utils/url_utility');
// u2f
require('./u2f/authenticate');
require('./u2f/error');
require('./u2f/register');
require('./u2f/util');
// droplab
require('./droplab/droplab');
require('./droplab/droplab_ajax');
require('./droplab/droplab_ajax_filter');
require('./droplab/droplab_filter');
// everything else
require('./abuse_reports');
require('./activities');
require('./admin');
require('./ajax_loading_spinner');
require('./api');
require('./aside');
require('./autosave');
require('./awards_handler');
require('./breakpoints');
require('./broadcast_message');
require('./build');
require('./build_artifacts');
require('./build_variables');
require('./ci_lint_editor');
require('./commit');
require('./commits');
require('./compare');
require('./compare_autocomplete');
require('./confirm_danger_modal');
require('./copy_as_gfm');
require('./copy_to_clipboard');
require('./create_label');
require('./diff');
require('./dispatcher');
require('./dropzone_input');
require('./due_date_select');
require('./files_comment_button');
require('./flash');
require('./gfm_auto_complete');
require('./gl_dropdown');
require('./gl_field_error');
require('./gl_field_errors');
require('./gl_form');
require('./group_avatar');
require('./group_label_subscription');
require('./groups_select');
require('./header');
require('./importer_status');
require('./issuable');
require('./issuable_context');
require('./issuable_form');
require('./issue');
require('./issue_status_select');
require('./issues_bulk_assignment');
require('./label_manager');
require('./labels');
require('./labels_select');
require('./layout_nav');
require('./line_highlighter');
require('./logo');
require('./member_expiration_date');
require('./members');
require('./merge_request');
require('./merge_request_tabs');
require('./merge_request_widget');
require('./merged_buttons');
require('./milestone');
require('./milestone_select');
require('./mini_pipeline_graph_dropdown');
require('./namespace_select');
require('./new_branch_form');
require('./new_commit_form');
require('./notes');
require('./notifications_dropdown');
require('./notifications_form');
require('./pager');
require('./pipelines');
require('./preview_markdown');
require('./project');
require('./project_avatar');
require('./project_find_file');
require('./project_fork');
require('./project_import');
require('./project_label_subscription');
require('./project_new');
require('./project_select');
require('./project_show');
require('./project_variables');
require('./projects_list');
require('./render_gfm');
require('./render_math');
require('./right_sidebar');
require('./search');
require('./search_autocomplete');
require('./shortcuts');
require('./shortcuts_blob');
require('./shortcuts_dashboard_navigation');
require('./shortcuts_find_file');
require('./shortcuts_issuable');
require('./shortcuts_navigation');
require('./shortcuts_network');
require('./signin_tabs_memoizer');
require('./single_file_diff');
require('./smart_interval');
require('./snippets_list');
require('./star');
require('./subbable_resource');
require('./subscription');
require('./subscription_select');
require('./syntax_highlight');
require('./task_list');
require('./todos');
require('./tree');
require('./user');
require('./user_tabs');
require('./username_validator');
require('./users_select');
require('./version_check_image');
require('./visibility_select');
require('./wikis');
require('./zen_mode');
require('vendor/fuzzaldrin-plus');
require('es6-promise').polyfill();
......
/* global Vue */
require('./issue_card_inner');
const Store = gl.issueBoards.BoardsStore;
module.exports = {
name: 'BoardsIssueCard',
template: `
<li class="card"
:class="{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id, 'is-active': issueDetailVisible }"
:index="index"
:data-issue-id="issue.id"
@mousedown="mouseDown"
@mousemove="mouseMove"
@mouseup="showIssue($event)">
<issue-card-inner
:list="list"
:issue="issue"
:issue-link-base="issueLinkBase"
:root-path="rootPath" />
</li>
`,
components: {
'issue-card-inner': gl.issueBoards.IssueCardInner,
},
props: {
list: Object,
issue: Object,
issueLinkBase: String,
disabled: Boolean,
index: Number,
rootPath: String,
},
data() {
return {
showDetail: false,
detailIssue: Store.detail,
};
},
computed: {
issueDetailVisible() {
return this.detailIssue.issue && this.detailIssue.issue.id === this.issue.id;
},
},
methods: {
mouseDown() {
this.showDetail = true;
},
mouseMove() {
this.showDetail = false;
},
showIssue(e) {
const targetTagName = e.target.tagName.toLowerCase();
if (targetTagName === 'a' || targetTagName === 'button') return;
if (this.showDetail) {
this.showDetail = false;
if (Store.detail.issue && Store.detail.issue.id === this.issue.id) {
Store.detail.issue = {};
} else {
Store.detail.issue = this.issue;
Store.detail.list = this.list;
}
}
},
},
};
/* eslint-disable comma-dangle, space-before-function-paren, dot-notation */
/* global Vue */
require('./issue_card_inner');
(() => {
const Store = gl.issueBoards.BoardsStore;
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.BoardCard = Vue.extend({
template: '#js-board-list-card',
components: {
'issue-card-inner': gl.issueBoards.IssueCardInner,
},
props: {
list: Object,
issue: Object,
issueLinkBase: String,
disabled: Boolean,
index: Number,
rootPath: String,
},
data () {
return {
showDetail: false,
detailIssue: Store.detail
};
},
computed: {
issueDetailVisible () {
return this.detailIssue.issue && this.detailIssue.issue.id === this.issue.id;
}
},
methods: {
mouseDown () {
this.showDetail = true;
},
mouseMove() {
this.showDetail = false;
},
showIssue (e) {
const targetTagName = e.target.tagName.toLowerCase();
if (targetTagName === 'a' || targetTagName === 'button') return;
if (this.showDetail) {
this.showDetail = false;
if (Store.detail.issue && Store.detail.issue.id === this.issue.id) {
Store.detail.issue = {};
} else {
Store.detail.issue = this.issue;
Store.detail.list = this.list;
}
}
}
}
});
})();
......@@ -2,7 +2,7 @@
/* global Vue */
/* global Sortable */
require('./board_card');
const boardCard = require('./board_card');
require('./board_new_issue');
(() => {
......@@ -14,7 +14,7 @@ require('./board_new_issue');
gl.issueBoards.BoardList = Vue.extend({
template: '#js-board-list-template',
components: {
'board-card': gl.issueBoards.BoardCard,
boardCard,
'board-new-issue': gl.issueBoards.BoardNewIssue
},
props: {
......
......@@ -123,14 +123,18 @@ class List {
if (listFrom) {
this.issuesSize += 1;
gl.boardService.moveIssue(issue.id, listFrom.id, this.id)
.then(() => {
listFrom.getIssues(false);
});
this.updateIssueLabel(issue, listFrom);
}
}
}
updateIssueLabel(issue, listFrom) {
gl.boardService.moveIssue(issue.id, listFrom.id, this.id)
.then(() => {
listFrom.getIssues(false);
});
}
findIssue (id) {
return this.issues.filter(issue => issue.id === id)[0];
}
......
......@@ -92,9 +92,12 @@
const issueLists = issue.getLists();
const listLabels = issueLists.map(listIssue => listIssue.label);
// Add to new lists issues if it doesn't already exist
if (!issueTo) {
// Add to new lists issues if it doesn't already exist
listTo.addIssue(issue, listFrom, newIndex);
} else {
listTo.updateIssueLabel(issue, listFrom);
issueTo.removeLabel(listFrom.label);
}
if (listTo.type === 'done') {
......
......@@ -36,6 +36,7 @@
/* global Shortcuts */
const ShortcutsBlob = require('./shortcuts_blob');
const UserCallout = require('./user_callout');
(function() {
var Dispatcher;
......@@ -108,6 +109,9 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'projects:compare:show':
new gl.Diff();
break;
case 'projects:branches:index':
gl.AjaxLoadingSpinner.init();
break;
case 'projects:issues:new':
case 'projects:issues:edit':
shortcut_handler = new ShortcutsNavigation();
......@@ -274,6 +278,9 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'ci:lints:show':
new gl.CILintEditor();
break;
case 'users:show':
new UserCallout();
break;
}
switch (path.first()) {
case 'sessions':
......@@ -310,6 +317,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'dashboard':
case 'root':
shortcut_handler = new ShortcutsDashboardNavigation();
new UserCallout();
break;
case 'profiles':
new NotificationsForm();
......
require('./stat_graph_contributors_graph');
require('./stat_graph_contributors_util');
require('./stat_graph_contributors');
require('./stat_graph');
import ContributorsStatGraph from './stat_graph_contributors';
// export to global scope
window.ContributorsStatGraph = ContributorsStatGraph;
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-return-assign, max-len */
(function() {
this.StatGraph = (function() {
function StatGraph() {}
StatGraph.log = {};
StatGraph.get_log = function() {
return this.log;
};
StatGraph.set_log = function(data) {
return this.log = data;
};
return StatGraph;
})();
}).call(window);
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign */
/* global ContributorsGraph */
/* global ContributorsAuthorGraph */
/* global ContributorsMasterGraph */
/* global ContributorsStatGraphUtil */
/* global d3 */
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign, no-shadow */
window.d3 = require('d3');
import d3 from 'd3';
import { ContributorsGraph, ContributorsAuthorGraph, ContributorsMasterGraph } from './stat_graph_contributors_graph';
import ContributorsStatGraphUtil from './stat_graph_contributors_util';
(function() {
this.ContributorsStatGraph = (function() {
function ContributorsStatGraph() {}
export default (function() {
function ContributorsStatGraph() {}
ContributorsStatGraph.prototype.init = function(log) {
var author_commits, total_commits;
this.parsed_log = ContributorsStatGraphUtil.parse_log(log);
this.set_current_field("commits");
total_commits = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field);
this.add_master_graph(total_commits);
this.add_authors_graph(author_commits);
return this.change_date_header();
};
ContributorsStatGraph.prototype.init = function(log) {
var author_commits, total_commits;
this.parsed_log = ContributorsStatGraphUtil.parse_log(log);
this.set_current_field("commits");
total_commits = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field);
this.add_master_graph(total_commits);
this.add_authors_graph(author_commits);
return this.change_date_header();
};
ContributorsStatGraph.prototype.add_master_graph = function(total_data) {
this.master_graph = new ContributorsMasterGraph(total_data);
return this.master_graph.draw();
};
ContributorsStatGraph.prototype.add_master_graph = function(total_data) {
this.master_graph = new ContributorsMasterGraph(total_data);
return this.master_graph.draw();
};
ContributorsStatGraph.prototype.add_authors_graph = function(author_data) {
var limited_author_data;
this.authors = [];
limited_author_data = author_data.slice(0, 100);
return _.each(limited_author_data, (function(_this) {
return function(d) {
var author_graph, author_header;
author_header = _this.create_author_header(d);
$(".contributors-list").append(author_header);
_this.authors[d.author_name] = author_graph = new ContributorsAuthorGraph(d.dates);
return author_graph.draw();
};
})(this));
};
ContributorsStatGraph.prototype.add_authors_graph = function(author_data) {
var limited_author_data;
this.authors = [];
limited_author_data = author_data.slice(0, 100);
return _.each(limited_author_data, (function(_this) {
return function(d) {
var author_graph, author_header;
author_header = _this.create_author_header(d);
$(".contributors-list").append(author_header);
_this.authors[d.author_name] = author_graph = new ContributorsAuthorGraph(d.dates);
return author_graph.draw();
};
})(this));
};
ContributorsStatGraph.prototype.format_author_commit_info = function(author) {
var commits;
commits = $('<span/>', {
"class": 'graph-author-commits-count'
});
commits.text(author.commits + " commits");
return $('<span/>').append(commits);
};
ContributorsStatGraph.prototype.format_author_commit_info = function(author) {
var commits;
commits = $('<span/>', {
"class": 'graph-author-commits-count'
});
commits.text(author.commits + " commits");
return $('<span/>').append(commits);
};
ContributorsStatGraph.prototype.create_author_header = function(author) {
var author_commit_info, author_commit_info_span, author_email, author_name, list_item;
list_item = $('<li/>', {
"class": 'person',
style: 'display: block;'
});
author_name = $('<h4>' + author.author_name + '</h4>');
author_email = $('<p class="graph-author-email">' + author.author_email + '</p>');
author_commit_info_span = $('<span/>', {
"class": 'commits'
});
author_commit_info = this.format_author_commit_info(author);
author_commit_info_span.html(author_commit_info);
list_item.append(author_name);
list_item.append(author_email);
list_item.append(author_commit_info_span);
return list_item;
};
ContributorsStatGraph.prototype.create_author_header = function(author) {
var author_commit_info, author_commit_info_span, author_email, author_name, list_item;
list_item = $('<li/>', {
"class": 'person',
style: 'display: block;'
});
author_name = $('<h4>' + author.author_name + '</h4>');
author_email = $('<p class="graph-author-email">' + author.author_email + '</p>');
author_commit_info_span = $('<span/>', {
"class": 'commits'
});
author_commit_info = this.format_author_commit_info(author);
author_commit_info_span.html(author_commit_info);
list_item.append(author_name);
list_item.append(author_email);
list_item.append(author_commit_info_span);
return list_item;
};
ContributorsStatGraph.prototype.redraw_master = function() {
var total_data;
total_data = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
this.master_graph.set_data(total_data);
return this.master_graph.redraw();
};
ContributorsStatGraph.prototype.redraw_master = function() {
var total_data;
total_data = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
this.master_graph.set_data(total_data);
return this.master_graph.redraw();
};
ContributorsStatGraph.prototype.redraw_authors = function() {
var author_commits, x_domain;
$("ol").html("");
x_domain = ContributorsGraph.prototype.x_domain;
author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field, x_domain);
return _.each(author_commits, (function(_this) {
return function(d) {
_this.redraw_author_commit_info(d);
$(_this.authors[d.author_name].list_item).appendTo("ol");
_this.authors[d.author_name].set_data(d.dates);
return _this.authors[d.author_name].redraw();
};
})(this));
};
ContributorsStatGraph.prototype.redraw_authors = function() {
var author_commits, x_domain;
$("ol").html("");
x_domain = ContributorsGraph.prototype.x_domain;
author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field, x_domain);
return _.each(author_commits, (function(_this) {
return function(d) {
_this.redraw_author_commit_info(d);
$(_this.authors[d.author_name].list_item).appendTo("ol");
_this.authors[d.author_name].set_data(d.dates);
return _this.authors[d.author_name].redraw();
};
})(this));
};
ContributorsStatGraph.prototype.set_current_field = function(field) {
return this.field = field;
};
ContributorsStatGraph.prototype.set_current_field = function(field) {
return this.field = field;
};
ContributorsStatGraph.prototype.change_date_header = function() {
var print, print_date_format, x_domain;
x_domain = ContributorsGraph.prototype.x_domain;
print_date_format = d3.time.format("%B %e %Y");
print = print_date_format(x_domain[0]) + " - " + print_date_format(x_domain[1]);
return $("#date_header").text(print);
};
ContributorsStatGraph.prototype.change_date_header = function() {
var print, print_date_format, x_domain;
x_domain = ContributorsGraph.prototype.x_domain;
print_date_format = d3.time.format("%B %e %Y");
print = print_date_format(x_domain[0]) + " - " + print_date_format(x_domain[1]);
return $("#date_header").text(print);
};
ContributorsStatGraph.prototype.redraw_author_commit_info = function(author) {
var author_commit_info, author_list_item;
author_list_item = $(this.authors[author.author_name].list_item);
author_commit_info = this.format_author_commit_info(author);
return author_list_item.find("span").html(author_commit_info);
};
ContributorsStatGraph.prototype.redraw_author_commit_info = function(author) {
var author_commit_info, author_list_item;
author_list_item = $(this.authors[author.author_name].list_item);
author_commit_info = this.format_author_commit_info(author);
return author_list_item.find("span").html(author_commit_info);
};
return ContributorsStatGraph;
})();
}).call(window);
return ContributorsStatGraph;
})();
/* eslint-disable func-names, space-before-function-paren, object-shorthand, no-var, one-var, camelcase, one-var-declaration-per-line, comma-dangle, no-param-reassign, no-return-assign, quotes, prefer-arrow-callback, wrap-iife, consistent-return, no-unused-vars, max-len, no-cond-assign, no-else-return, max-len */
(function() {
window.ContributorsStatGraphUtil = {
parse_log: function(log) {
var by_author, by_email, data, entry, i, len, total, normalized_email;
total = {};
by_author = {};
by_email = {};
for (i = 0, len = log.length; i < len; i += 1) {
entry = log[i];
if (total[entry.date] == null) {
this.add_date(entry.date, total);
}
normalized_email = entry.author_email.toLowerCase();
data = by_author[entry.author_name] || by_email[normalized_email];
if (data == null) {
data = this.add_author(entry, by_author, by_email);
}
if (!data[entry.date]) {
this.add_date(entry.date, data);
}
this.store_data(entry, total[entry.date], data[entry.date]);
}
total = _.toArray(total);
by_author = _.toArray(by_author);
return {
total: total,
by_author: by_author
};
},
add_date: function(date, collection) {
collection[date] = {};
return collection[date].date = date;
},
add_author: function(author, by_author, by_email) {
var data, normalized_email;
data = {};
data.author_name = author.author_name;
data.author_email = author.author_email;
normalized_email = author.author_email.toLowerCase();
by_author[author.author_name] = data;
by_email[normalized_email] = data;
return data;
},
store_data: function(entry, total, by_author) {
this.store_commits(total, by_author);
this.store_additions(entry, total, by_author);
return this.store_deletions(entry, total, by_author);
},
store_commits: function(total, by_author) {
this.add(total, "commits", 1);
return this.add(by_author, "commits", 1);
},
add: function(collection, field, value) {
if (collection[field] == null) {
collection[field] = 0;
}
return collection[field] += value;
},
store_additions: function(entry, total, by_author) {
if (entry.additions == null) {
entry.additions = 0;
export default {
parse_log: function(log) {
var by_author, by_email, data, entry, i, len, total, normalized_email;
total = {};
by_author = {};
by_email = {};
for (i = 0, len = log.length; i < len; i += 1) {
entry = log[i];
if (total[entry.date] == null) {
this.add_date(entry.date, total);
}
this.add(total, "additions", entry.additions);
return this.add(by_author, "additions", entry.additions);
},
store_deletions: function(entry, total, by_author) {
if (entry.deletions == null) {
entry.deletions = 0;
normalized_email = entry.author_email.toLowerCase();
data = by_author[entry.author_name] || by_email[normalized_email];
if (data == null) {
data = this.add_author(entry, by_author, by_email);
}
this.add(total, "deletions", entry.deletions);
return this.add(by_author, "deletions", entry.deletions);
},
get_total_data: function(parsed_log, field) {
var log, total_data;
log = parsed_log.total;
total_data = this.pick_field(log, field);
return _.sortBy(total_data, function(d) {
return d.date;
});
},
pick_field: function(log, field) {
var total_data;
total_data = [];
_.each(log, function(d) {
return total_data.push(_.pick(d, [field, 'date']));
});
return total_data;
},
get_author_data: function(parsed_log, field, date_range) {
var author_data, log;
if (date_range == null) {
date_range = null;
}
log = parsed_log.by_author;
author_data = [];
_.each(log, (function(_this) {
return function(log_entry) {
var parsed_log_entry;
parsed_log_entry = _this.parse_log_entry(log_entry, field, date_range);
if (!_.isEmpty(parsed_log_entry.dates)) {
return author_data.push(parsed_log_entry);
}
};
})(this));
return _.sortBy(author_data, function(d) {
return d[field];
}).reverse();
},
parse_log_entry: function(log_entry, field, date_range) {
var parsed_entry;
parsed_entry = {};
parsed_entry.author_name = log_entry.author_name;
parsed_entry.author_email = log_entry.author_email;
parsed_entry.dates = {};
parsed_entry.commits = parsed_entry.additions = parsed_entry.deletions = 0;
_.each(_.omit(log_entry, 'author_name', 'author_email'), (function(_this) {
return function(value, key) {
if (_this.in_range(value.date, date_range)) {
parsed_entry.dates[value.date] = value[field];
parsed_entry.commits += value.commits;
parsed_entry.additions += value.additions;
return parsed_entry.deletions += value.deletions;
}
};
})(this));
return parsed_entry;
},
in_range: function(date, date_range) {
var ref;
if (date_range === null || (date_range[0] <= (ref = new Date(date)) && ref <= date_range[1])) {
return true;
} else {
return false;
if (!data[entry.date]) {
this.add_date(entry.date, data);
}
this.store_data(entry, total[entry.date], data[entry.date]);
}
total = _.toArray(total);
by_author = _.toArray(by_author);
return {
total: total,
by_author: by_author
};
},
add_date: function(date, collection) {
collection[date] = {};
return collection[date].date = date;
},
add_author: function(author, by_author, by_email) {
var data, normalized_email;
data = {};
data.author_name = author.author_name;
data.author_email = author.author_email;
normalized_email = author.author_email.toLowerCase();
by_author[author.author_name] = data;
by_email[normalized_email] = data;
return data;
},
store_data: function(entry, total, by_author) {
this.store_commits(total, by_author);
this.store_additions(entry, total, by_author);
return this.store_deletions(entry, total, by_author);
},
store_commits: function(total, by_author) {
this.add(total, "commits", 1);
return this.add(by_author, "commits", 1);
},
add: function(collection, field, value) {
if (collection[field] == null) {
collection[field] = 0;
}
return collection[field] += value;
},
store_additions: function(entry, total, by_author) {
if (entry.additions == null) {
entry.additions = 0;
}
this.add(total, "additions", entry.additions);
return this.add(by_author, "additions", entry.additions);
},
store_deletions: function(entry, total, by_author) {
if (entry.deletions == null) {
entry.deletions = 0;
}
this.add(total, "deletions", entry.deletions);
return this.add(by_author, "deletions", entry.deletions);
},
get_total_data: function(parsed_log, field) {
var log, total_data;
log = parsed_log.total;
total_data = this.pick_field(log, field);
return _.sortBy(total_data, function(d) {
return d.date;
});
},
pick_field: function(log, field) {
var total_data;
total_data = [];
_.each(log, function(d) {
return total_data.push(_.pick(d, [field, 'date']));
});
return total_data;
},
get_author_data: function(parsed_log, field, date_range) {
var author_data, log;
if (date_range == null) {
date_range = null;
}
log = parsed_log.by_author;
author_data = [];
_.each(log, (function(_this) {
return function(log_entry) {
var parsed_log_entry;
parsed_log_entry = _this.parse_log_entry(log_entry, field, date_range);
if (!_.isEmpty(parsed_log_entry.dates)) {
return author_data.push(parsed_log_entry);
}
};
})(this));
return _.sortBy(author_data, function(d) {
return d[field];
}).reverse();
},
parse_log_entry: function(log_entry, field, date_range) {
var parsed_entry;
parsed_entry = {};
parsed_entry.author_name = log_entry.author_name;
parsed_entry.author_email = log_entry.author_email;
parsed_entry.dates = {};
parsed_entry.commits = parsed_entry.additions = parsed_entry.deletions = 0;
_.each(_.omit(log_entry, 'author_name', 'author_email'), (function(_this) {
return function(value, key) {
if (_this.in_range(value.date, date_range)) {
parsed_entry.dates[value.date] = value[field];
parsed_entry.commits += value.commits;
parsed_entry.additions += value.additions;
return parsed_entry.deletions += value.deletions;
}
};
})(this));
return parsed_entry;
},
in_range: function(date, date_range) {
var ref;
if (date_range === null || (date_range[0] <= (ref = new Date(date)) && ref <= date_range[1])) {
return true;
} else {
return false;
}
};
}).call(window);
}
};
......@@ -296,5 +296,57 @@
* @returns {Boolean}
*/
w.gl.utils.convertPermissionToBoolean = permission => permission === 'true';
/**
* Back Off exponential algorithm
* backOff :: (Function<next, stop>, Number) -> Promise<Any, Error>
*
* @param {Function<next, stop>} fn function to be called
* @param {Number} timeout
* @return {Promise<Any, Error>}
* @example
* ```
* backOff(function (next, stop) {
* // Let's perform this function repeatedly for 60s or for the timeout provided.
*
* ourFunction()
* .then(function (result) {
* // continue if result is not what we need
* next();
*
* // when result is what we need let's stop with the repetions and jump out of the cycle
* stop(result);
* })
* .catch(function (error) {
* // if there is an error, we need to stop this with an error.
* stop(error);
* })
* }, 60000)
* .then(function (result) {})
* .catch(function (error) {
* // deal with errors passed to stop()
* })
* ```
*/
w.gl.utils.backOff = (fn, timeout = 60000) => {
let nextInterval = 2000;
const startTime = (+new Date());
return new Promise((resolve, reject) => {
const stop = arg => ((arg instanceof Error) ? reject(arg) : resolve(arg));
const next = () => {
if (new Date().getTime() - startTime < timeout) {
setTimeout(fn.bind(null, next, stop), nextInterval);
nextInterval *= 2;
} else {
reject(new Error('BACKOFF_TIMEOUT'));
}
};
fn(next, stop);
});
};
})(window);
}).call(window);
......@@ -78,7 +78,6 @@
} else {
$(element).find('.assignee-icon').empty();
}
return $(element).effect('highlight');
};
function Milestone() {
......
......@@ -39,7 +39,7 @@
$value = $block.find('.value');
$loading = $block.find('.block-loading').fadeOut();
if (issueUpdateURL) {
milestoneLinkTemplate = _.template('<a href="/<%- namespace %>/<%- path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>');
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>';
collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left"> <%- title %> </span>');
}
......@@ -181,8 +181,7 @@
$selectbox.hide();
$value.css('display', '');
if (data.milestone != null) {
data.milestone.namespace = _this.currentProject.namespace;
data.milestone.path = _this.currentProject.path;
data.milestone.full_path = _this.currentProject.full_path;
data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date);
$value.html(milestoneLinkTemplate(data.milestone));
return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
......
/* 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 */
/* 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; };
......@@ -20,15 +20,35 @@
};
NewBranchForm.prototype.init = function() {
if (this.name.val().length > 0) {
if (this.name.length && this.name.val().length > 0) {
return this.name.trigger('blur');
}
};
NewBranchForm.prototype.setupAvailableRefs = function(availableRefs) {
return this.ref.autocomplete({
source: availableRefs,
minLength: 1
var $branchSelect = $('.js-branch-select');
$branchSelect.glDropdown({
data: availableRefs,
filterable: true,
filterByText: true,
remote: false,
fieldName: $branchSelect.data('field-name'),
selectable: true,
isSelectable: function(branch, $el) {
return !$el.hasClass('is-active');
},
text: function(branch) {
return branch;
},
id: function(branch) {
return branch;
},
toggleLabel: function(branch) {
if (branch) {
return branch;
}
}
});
};
......
// require everything else in this directory
function requireAll(context) { return context.keys().map(context); }
requireAll(require.context('.', false, /^\.\/(?!profile_bundle).*\.(js|es6)$/));
require('./gl_crop');
require('./profile');
......@@ -36,6 +36,9 @@
// Do not update if one dropdown has not selected any option
if (!($allowedToMergeInput.length && $allowedToPushInput.length)) return;
this.$allowedToMergeDropdown.disable();
this.$allowedToPushDropdown.disable();
$.ajax({
type: 'POST',
url: this.$wrap.data('url'),
......@@ -53,13 +56,13 @@
}]
}
},
success: () => {
this.$wrap.effect('highlight');
},
error() {
$.scrollTo(0);
new Flash('Failed to update branch!');
}
}).always(() => {
this.$allowedToMergeDropdown.enable();
this.$allowedToPushDropdown.enable();
});
}
};
......
// require everything else in this directory
function requireAll(context) { return context.keys().map(context); }
requireAll(require.context('.', false, /^\.\/(?!protected_branches_bundle).*\.(js|es6)$/));
require('./protected_branch_access_dropdown');
require('./protected_branch_create');
require('./protected_branch_dropdown');
require('./protected_branch_edit');
require('./protected_branch_edit_list');
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, max-len */
/* global ace */
// require everything else in this directory
function requireAll(context) { return context.keys().map(context); }
requireAll(require.context('.', false, /^\.\/(?!snippet_bundle).*\.(js|es6)$/));
(function() {
$(function() {
var editor = ace.edit("editor");
......
/* global Cookies */
const userCalloutElementName = '.user-callout';
const closeButton = '.close-user-callout';
const userCalloutBtn = '.user-callout-btn';
const userCalloutSvgAttrName = 'callout-svg';
const USER_CALLOUT_COOKIE = 'user_callout_dismissed';
const USER_CALLOUT_TEMPLATE = `
<div class="bordered-box landing content-block">
<button class="btn btn-default close close-user-callout" type="button">
<i class="fa fa-times dismiss-icon"></i>
</button>
<div class="row">
<div class="col-sm-3 col-xs-12 svg-container">
</div>
<div class="col-sm-8 col-xs-12 inner-content">
<h4>
Customize your experience
</h4>
<p>
Change syntax themes, default project pages, and more in preferences.
</p>
<a class="btn user-callout-btn" href="/profile/preferences">Check it out</a>
</div>
</div>
</div>`;
class UserCallout {
constructor() {
this.isCalloutDismissed = Cookies.get(USER_CALLOUT_COOKIE);
this.userCalloutBody = $(userCalloutElementName);
this.userCalloutSvg = $(userCalloutElementName).attr(userCalloutSvgAttrName);
$(userCalloutElementName).removeAttr(userCalloutSvgAttrName);
this.init();
}
init() {
const $template = $(USER_CALLOUT_TEMPLATE);
if (!this.isCalloutDismissed || this.isCalloutDismissed === 'false') {
$template.find('.svg-container').append(this.userCalloutSvg);
this.userCalloutBody.append($template);
$template.find(closeButton).on('click', e => this.dismissCallout(e));
$template.find(userCalloutBtn).on('click', e => this.dismissCallout(e));
}
}
dismissCallout(e) {
Cookies.set(USER_CALLOUT_COOKIE, 'true');
const $currentTarget = $(e.currentTarget);
if ($currentTarget.hasClass('close-user-callout')) {
this.userCalloutBody.empty();
}
}
}
module.exports = UserCallout;
// require everything else in this directory
function requireAll(context) { return context.keys().map(context); }
requireAll(require.context('.', false, /^\.\/(?!users_bundle).*\.(js|es6)$/));
require('./calendar');
/* global Vue, Flash, gl */
/* eslint-disable no-param-reassign */
/* eslint-disable no-param-reassign, no-alert */
((gl) => {
gl.VuePipelineActions = Vue.extend({
......@@ -16,6 +16,20 @@
download(name) {
return `Download ${name} artifacts`;
},
/**
* Shows a dialog when the user clicks in the cancel button.
* We need to prevent the default behavior and stop propagation because the
* link relies on UJS.
*
* @param {Event} event
*/
confirmAction(event) {
if (!confirm('Are you sure you want to cancel this pipeline?')) {
event.preventDefault();
event.stopPropagation();
}
},
},
template: `
<td class="pipeline-actions hidden-xs">
......
......@@ -23,7 +23,7 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s
apiScope: 'all',
pageInfo: {},
pagenum: 1,
count: { all: 0, running_or_pending: 0 },
count: {},
pageRequest: false,
};
},
......
......@@ -23,6 +23,13 @@
required: true,
},
},
updated() {
if (this.builds) {
this.stopDropdownClickPropagation();
}
},
methods: {
fetchBuilds(e) {
const areaExpanded = e.currentTarget.attributes['aria-expanded'];
......@@ -37,17 +44,19 @@
return flash;
});
},
keepGraph(e) {
const { target } = e;
if (target.className.indexOf('js-ci-action-icon') >= 0) return null;
if (
target.parentElement &&
(target.parentElement.className.indexOf('js-ci-action-icon') >= 0)
) return null;
return e.stopPropagation();
/**
* When the user right clicks or cmd/ctrl + click in the job name
* the dropdown should not be closed and the link should open in another tab,
* so we stop propagation of the click event inside the dropdown.
*
* Since this component is rendered multiple times per page we need to guarantee we only
* target the click event of this component.
*/
stopDropdownClickPropagation() {
$(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item')).on('click', (e) => {
e.stopPropagation();
});
},
},
computed: {
......@@ -76,13 +85,13 @@
template: `
<div>
<button
@click='fetchBuilds($event)'
@click="fetchBuilds($event)"
:class="triggerButtonClass"
:title='stage.title'
:title="stage.title"
data-placement="top"
data-toggle="dropdown"
type="button"
:aria-label='stage.title'
:aria-label="stage.title"
>
<span v-html="svg" aria-hidden="true"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i>
......@@ -90,7 +99,6 @@
<ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
<div class="arrow-up" aria-hidden="true"></div>
<div
@click='keepGraph($event)'
:class="dropdownClass"
class="js-builds-dropdown-list scrollable-menu"
v-html="buildsOrSpinner"
......
......@@ -2,7 +2,6 @@
* This is a manifest file that'll automatically include all the stylesheets available in this directory
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
* the top of the compiled file, but it's generally better to create a new file per style scope.
*= require jquery-ui/autocomplete
*= require jquery.atwho
*= require select2
*= require_self
......
......@@ -2,17 +2,6 @@
font-family: $regular_font;
font-size: $font-size-base;
&.ui-autocomplete {
border-color: $jq-ui-border;
padding: 0;
margin-top: 2px;
z-index: 1001;
.ui-menu-item a {
padding: 4px 10px;
}
}
.ui-state-default {
border: 1px solid $white-light;
background: $white-light;
......
......@@ -29,16 +29,14 @@
}
}
@media (min-width: $screen-sm-min) {
.content-wrapper {
padding-right: $gutter_collapsed_width;
}
}
.right-sidebar-collapsed {
padding-right: 0;
@media (min-width: $screen-sm-min) {
.content-wrapper {
padding-right: $gutter_collapsed_width;
}
.merge-request-tabs-holder.affix {
right: $gutter_collapsed_width;
}
......@@ -56,6 +54,12 @@
.right-sidebar-expanded {
padding-right: 0;
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
.content-wrapper {
padding-right: $gutter_collapsed_width;
}
}
@media (min-width: $screen-md-min) {
.content-wrapper {
padding-right: $gutter_width;
......
......@@ -277,3 +277,41 @@ table.u2f-registrations {
padding-left: 18px;
}
}
.user-callout {
margin: 24px auto 0;
.bordered-box {
border: 1px solid $border-color;
border-radius: $border-radius-default;
}
.landing {
margin-bottom: $gl-padding;
.close {
margin-right: 20px;
}
.dismiss-icon {
float: right;
cursor: pointer;
color: $cycle-analytics-dismiss-icon-color;
}
.svg-container {
text-align: center;
svg {
width: 136px;
height: 136px;
}
}
}
@media(max-width: $screen-xs-max) {
.inner-content {
padding-left: 30px;
}
}
}
......@@ -83,6 +83,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:akismet_api_key,
:akismet_enabled,
:container_registry_token_expire_delay,
:default_artifacts_expire_in,
:default_branch_protection,
:default_group_visibility,
:default_project_visibility,
......
......@@ -3,7 +3,7 @@ class Admin::SystemInfoController < Admin::ApplicationController
'nobrowse',
'read-only',
'ro'
]
].freeze
EXCLUDED_MOUNT_TYPES = [
'autofs',
......@@ -27,7 +27,7 @@ class Admin::SystemInfoController < Admin::ApplicationController
'tmpfs',
'tracefs',
'vfat'
]
].freeze
def show
@cpus = Vmstat.cpu rescue nil
......
......@@ -181,7 +181,7 @@ class ApplicationController < ActionController::Base
end
def gitlab_ldap_access(&block)
Gitlab::LDAP::Access.open { |access| block.call(access) }
Gitlab::LDAP::Access.open { |access| yield(access) }
end
# JSON for infinite scroll via Pager object
......
......@@ -101,13 +101,14 @@ module CreatesCommit
# TODO: We should really clean this up
def set_commit_variables
if can?(current_user, :push_code, @project)
# Edit file in this project
@mr_source_project = @project
else
# Merge request from fork to this project
@mr_source_project = current_user.fork_of(@project)
end
@mr_source_project =
if can?(current_user, :push_code, @project)
# Edit file in this project
@project
else
# Merge request from fork to this project
current_user.fork_of(@project)
end
# Merge request to this project
@mr_target_project = @project
......
......@@ -59,10 +59,10 @@ module ServiceParams
:user_key,
:username,
:webhook
]
].freeze
# Parameters to ignore if no value is specified
FILTER_BLANK_PARAMS = [:password]
FILTER_BLANK_PARAMS = [:password].freeze
def service_params
dynamic_params = @service.event_channel_names + @service.event_names
......
......@@ -27,7 +27,7 @@ module SpammableActions
render :verify
else
fallback.call
yield
end
end
......
......@@ -5,7 +5,7 @@ class JwtController < ApplicationController
SERVICES = {
Auth::ContainerRegistryAuthenticationService::AUDIENCE => Auth::ContainerRegistryAuthenticationService,
}
}.freeze
def auth
service = SERVICES[params[:service]]
......@@ -39,7 +39,8 @@ class JwtController < ApplicationController
message: "HTTP Basic: Access denied\n" \
"You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \
"You can generate one at #{profile_personal_access_tokens_url}" }
] }, status: 401
]
}, status: 401
end
def render_unauthorized
......@@ -47,7 +48,8 @@ class JwtController < ApplicationController
errors: [
{ code: 'UNAUTHORIZED',
message: 'HTTP Basic: Access denied' }
] }, status: 401
]
}, status: 401
end
def auth_params
......
......@@ -80,7 +80,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def build_qr_code
uri = current_user.otp_provisioning_uri(account_string, issuer: issuer_host)
RQRCode::render_qrcode(uri, :svg, level: :m, unit: 3)
RQRCode.render_qrcode(uri, :svg, level: :m, unit: 3)
end
def account_string
......
class Projects::BranchesController < Projects::ApplicationController
include ActionView::Helpers::SanitizeHelper
include SortingHelper
# Authorize
before_action :require_non_empty_project
before_action :require_non_empty_project, except: :create
before_action :authorize_download_code!
before_action :authorize_push_code!, only: [:new, :create, :destroy, :destroy_all_merged]
......@@ -32,6 +33,8 @@ class Projects::BranchesController < Projects::ApplicationController
branch_name = sanitize(strip_tags(params[:branch_name]))
branch_name = Addressable::URI.unescape(branch_name)
redirect_to_autodeploy = project.empty_repo? && project.deployment_services.present?
result = CreateBranchService.new(project, current_user).
execute(branch_name, ref)
......@@ -42,8 +45,15 @@ class Projects::BranchesController < Projects::ApplicationController
if result[:status] == :success
@branch = result[:branch]
redirect_to namespace_project_tree_path(@project.namespace, @project,
@branch.name)
if redirect_to_autodeploy
redirect_to(
url_to_autodeploy_setup(project, branch_name),
notice: view_context.autodeploy_flash_notice(branch_name))
else
redirect_to namespace_project_tree_path(@project.namespace, @project,
@branch.name)
end
else
@error = result[:message]
render action: 'new'
......@@ -76,7 +86,19 @@ class Projects::BranchesController < Projects::ApplicationController
ref_escaped = sanitize(strip_tags(params[:ref]))
Addressable::URI.unescape(ref_escaped)
else
@project.default_branch
@project.default_branch || 'master'
end
end
def url_to_autodeploy_setup(project, branch_name)
namespace_project_new_blob_path(
project.namespace,
project,
branch_name,
file_name: '.gitlab-ci.yml',
commit_message: 'Set up auto deploy',
target_branch: branch_name,
context: 'autodeploy'
)
end
end
......@@ -76,11 +76,12 @@ class Projects::GitHttpClientController < Projects::ApplicationController
return @project if defined?(@project)
project_id, _ = project_id_with_suffix
if project_id.blank?
@project = nil
else
@project = Project.find_by_full_path("#{params[:namespace_id]}/#{project_id}")
end
@project =
if project_id.blank?
nil
else
Project.find_by_full_path("#{params[:namespace_id]}/#{project_id}")
end
end
# This method returns two values so that we can parse
......
......@@ -381,14 +381,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def merge_widget_refresh
if merge_request.merge_when_build_succeeds
@status = :merge_when_build_succeeds
else
# Only MRs that can be merged end in this action
# MR can be already picked up for merge / merged already or can be waiting for worker to be picked up
# in last case it does not have any special status. Possible error is handled inside widget js function
@status = :success
end
@status =
if merge_request.merge_when_build_succeeds
:merge_when_build_succeeds
else
# Only MRs that can be merged end in this action
# MR can be already picked up for merge / merged already or can be waiting for worker to be picked up
# in last case it does not have any special status. Possible error is handled inside widget js function
:success
end
render 'merge'
end
......
......@@ -13,9 +13,15 @@ class Projects::PipelinesController < Projects::ApplicationController
.page(params[:page])
.per(30)
@running_or_pending_count = PipelinesFinder
@running_count = PipelinesFinder
.new(project).execute(scope: 'running').count
@pending_count = PipelinesFinder
.new(project).execute(scope: 'pending').count
@finished_count = PipelinesFinder
.new(project).execute(scope: 'finished').count
@pipelines_count = PipelinesFinder
.new(project).execute.count
......@@ -29,7 +35,9 @@ class Projects::PipelinesController < Projects::ApplicationController
.represent(@pipelines),
count: {
all: @pipelines_count,
running_or_pending: @running_or_pending_count
running: @running_count,
pending: @pending_count,
finished: @finished_count,
}
}
end
......
......@@ -15,11 +15,12 @@ class SessionsController < Devise::SessionsController
def new
set_minimum_password_length
if Gitlab.config.ldap.enabled
@ldap_servers = Gitlab::LDAP::Config.servers
else
@ldap_servers = []
end
@ldap_servers =
if Gitlab.config.ldap.enabled
Gitlab::LDAP::Config.servers
else
[]
end
super
end
......
......@@ -28,8 +28,9 @@ class SnippetsController < ApplicationController
@snippets = SnippetsFinder.new.execute(current_user, {
filter: :by_user,
user: @user,
scope: params[:scope] }).
page(params[:page])
scope: params[:scope]
})
.page(params[:page])
render 'index'
else
......
......@@ -19,7 +19,7 @@
# iids: integer[]
#
class IssuableFinder
NONE = '0'
NONE = '0'.freeze
attr_accessor :current_user, :params
......
......@@ -28,11 +28,12 @@ class NotesFinder
private
def init_collection
if @params[:target_id]
@notes = on_target(@params[:target_type], @params[:target_id])
else
@notes = notes_of_any_type
end
@notes =
if @params[:target_id]
on_target(@params[:target_type], @params[:target_id])
else
notes_of_any_type
end
end
def notes_of_any_type
......
......@@ -10,7 +10,11 @@ class PipelinesFinder
scoped_pipelines =
case scope
when 'running'
pipelines.running_or_pending
pipelines.running
when 'pending'
pipelines.pending
when 'finished'
pipelines.finished
when 'branches'
from_ids(ids_for_ref(branches))
when 'tags'
......
......@@ -13,7 +13,7 @@
#
class TodosFinder
NONE = '0'
NONE = '0'.freeze
attr_accessor :current_user, :params
......@@ -99,7 +99,7 @@ class TodosFinder
end
def type?
type.present? && ['Issue', 'MergeRequest'].include?(type)
type.present? && %w(Issue MergeRequest).include?(type)
end
def type
......
......@@ -69,11 +69,12 @@ module ApplicationHelper
end
def avatar_icon(user_or_email = nil, size = nil, scale = 2)
if user_or_email.is_a?(User)
user = user_or_email
else
user = User.find_by_any_email(user_or_email.try(:downcase))
end
user =
if user_or_email.is_a?(User)
user_or_email
else
User.find_by_any_email(user_or_email.try(:downcase))
end
if user
user.avatar_url(size) || default_avatar
......
module ApplicationSettingsHelper
def gravatar_enabled?
current_application_settings.gravatar_enabled?
end
def signup_enabled?
current_application_settings.signup_enabled?
end
def signin_enabled?
current_application_settings.signin_enabled?
end
delegate :gravatar_enabled?,
:signup_enabled?,
:signin_enabled?,
:akismet_enabled?,
:koding_enabled?,
to: :current_application_settings
def user_oauth_applications?
current_application_settings.user_oauth_applications
end
def askimet_enabled?
current_application_settings.akismet_enabled?
end
def koding_enabled?
current_application_settings.koding_enabled?
end
def allowed_protocols_present?
current_application_settings.enabled_git_access_protocol.present?
end
......
......@@ -153,16 +153,17 @@ module BlobHelper
# Because we are opionated we set the cache headers ourselves.
response.cache_control[:public] = @project.public?
if @ref && @commit && @ref == @commit.id
# This is a link to a commit by its commit SHA. That means that the blob
# is immutable. The only reason to invalidate the cache is if the commit
# was deleted or if the user lost access to the repository.
response.cache_control[:max_age] = Blob::CACHE_TIME_IMMUTABLE
else
# A branch or tag points at this blob. That means that the expected blob
# value may change over time.
response.cache_control[:max_age] = Blob::CACHE_TIME
end
response.cache_control[:max_age] =
if @ref && @commit && @ref == @commit.id
# This is a link to a commit by its commit SHA. That means that the blob
# is immutable. The only reason to invalidate the cache is if the commit
# was deleted or if the user lost access to the repository.
Blob::CACHE_TIME_IMMUTABLE
else
# A branch or tag points at this blob. That means that the expected blob
# value may change over time.
Blob::CACHE_TIME
end
response.etag = @blob.id
!stale
......
......@@ -34,7 +34,7 @@ module ButtonHelper
content_tag (append_link ? :a : :span), protocol,
class: klass,
href: (project.http_url_to_repo if append_link),
href: (project.http_url_to_repo(current_user) if append_link),
data: {
html: true,
placement: placement,
......
......@@ -24,7 +24,7 @@ module EmailsHelper
def action_title(url)
return unless url
["merge_requests", "issues", "commit"].each do |action|
%w(merge_requests issues commit).each do |action|
if url.split("/").include?(action)
return "View #{action.humanize.singularize}"
end
......
......@@ -23,7 +23,7 @@ module IssuablesHelper
def issuable_json_path(issuable)
project = issuable.project
if issuable.kind_of?(MergeRequest)
if issuable.is_a?(MergeRequest)
namespace_project_merge_request_path(project.namespace, project, issuable.iid, :json)
else
namespace_project_issue_path(project.namespace, project, issuable.iid, :json)
......@@ -52,7 +52,7 @@ module IssuablesHelper
field_name: 'issuable_template',
selected: selected_template(issuable),
project_path: ref_project.path,
namespace_path: ref_project.namespace.path
namespace_path: ref_project.namespace.full_path
}
}
......@@ -198,7 +198,7 @@ module IssuablesHelper
@counts[issuable_type][state]
end
IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page]
IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze
private_constant :IRRELEVANT_PARAMS_FOR_CACHE_KEY
def issuables_state_counter_cache_key(issuable_type, state)
......
......@@ -2,6 +2,7 @@ module JavascriptHelper
def page_specific_javascript_tag(js)
javascript_include_tag asset_path(js)
end
def page_specific_javascript_bundle_tag(js)
javascript_include_tag(*webpack_asset_paths(js))
end
......
......@@ -33,7 +33,7 @@ module NamespacesHelper
end
def namespace_icon(namespace, size = 40)
if namespace.kind_of?(Group)
if namespace.is_a?(Group)
group_icon(namespace)
else
avatar_icon(namespace.owner.email, size)
......
......@@ -150,6 +150,15 @@ module ProjectsHelper
).html_safe
end
def link_to_autodeploy_doc
link_to 'About auto deploy', help_page_path('ci/autodeploy/index'), target: '_blank'
end
def autodeploy_flash_notice(branch_name)
"Branch <strong>#{truncate(sanitize(branch_name))}</strong> was created. To set up auto deploy, \
choose a GitLab CI Yaml template and commit your changes. #{link_to_autodeploy_doc}".html_safe
end
private
def repo_children_classes(field)
......@@ -232,7 +241,7 @@ module ProjectsHelper
when 'ssh'
project.ssh_url_to_repo
else
project.http_url_to_repo
project.http_url_to_repo(current_user)
end
end
......
......@@ -30,7 +30,7 @@ module SortingHelper
}
if current_controller?('admin/projects')
options.merge!(sort_value_largest_repo => sort_title_largest_repo)
options[sort_value_largest_repo] = sort_title_largest_repo
end
options
......
......@@ -37,8 +37,8 @@ module SubmoduleHelper
end
def self_url?(url, namespace, project)
return true if url == [ Gitlab.config.gitlab.url, '/', namespace, '/',
project, '.git' ].join('')
return true if url == [Gitlab.config.gitlab.url, '/', namespace, '/',
project, '.git'].join('')
url == gitlab_shell.url_to_repo([namespace, '/', project].join(''))
end
......@@ -48,8 +48,8 @@ module SubmoduleHelper
end
def standard_links(host, namespace, project, commit)
base = [ 'https://', host, '/', namespace, '/', project ].join('')
[base, [ base, '/tree/', commit ].join('')]
base = ['https://', host, '/', namespace, '/', project].join('')
[base, [base, '/tree/', commit].join('')]
end
def relative_self_links(url, commit)
......
......@@ -99,7 +99,7 @@ module TabHelper
return 'active'
end
if ['services', 'hooks', 'deploy_keys', 'protected_branches'].include? controller.controller_name
if %w(services hooks deploy_keys protected_branches).include? controller.controller_name
"active"
end
end
......
......@@ -150,6 +150,6 @@ module TodosHelper
private
def show_todo_state?(todo)
(todo.target.is_a?(MergeRequest) || todo.target.is_a?(Issue)) && ['closed', 'merged'].include?(todo.target.state)
(todo.target.is_a?(MergeRequest) || todo.target.is_a?(Issue)) && %w(closed merged).include?(todo.target.state)
end
end
......@@ -89,13 +89,9 @@ module VisibilityLevelHelper
current_application_settings.restricted_visibility_levels || []
end
def default_project_visibility
current_application_settings.default_project_visibility
end
def default_group_visibility
current_application_settings.default_group_visibility
end
delegate :default_project_visibility,
:default_group_visibility,
to: :current_application_settings
def skip_level?(form_model, level)
form_model.is_a?(Project) && !form_model.visibility_level_allowed?(level)
......
class RepositoryCheckMailer < BaseMailer
def notify(failed_count)
if failed_count == 1
@message = "One project failed its last repository check"
else
@message = "#{failed_count} projects failed their last repository check"
end
@message =
if failed_count == 1
"One project failed its last repository check"
else
"#{failed_count} projects failed their last repository check"
end
mail(
to: User.admins.pluck(:email),
......
......@@ -5,7 +5,7 @@ class ApplicationSetting < ActiveRecord::Base
add_authentication_token_field :runners_registration_token
add_authentication_token_field :health_check_access_token
CACHE_KEY = 'application_setting.last'
CACHE_KEY = 'application_setting.last'.freeze
DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace
| # or
\s # any whitespace character
......@@ -76,6 +76,12 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
numericality: { only_integer: true, greater_than: 0 }
validates :max_artifacts_size,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
validates :default_artifacts_expire_in, presence: true, duration: true
validates :container_registry_token_expire_delay,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
......@@ -168,6 +174,7 @@ class ApplicationSetting < ActiveRecord::Base
after_sign_up_text: nil,
akismet_enabled: false,
container_registry_token_expire_delay: 5,
default_artifacts_expire_in: '30 days',
default_branch_protection: Settings.gitlab['default_branch_protection'],
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_projects_limit: Settings.gitlab['default_projects_limit'],
......@@ -201,9 +208,9 @@ class ApplicationSetting < ActiveRecord::Base
sign_in_text: nil,
signin_enabled: Settings.gitlab['signin_enabled'],
signup_enabled: Settings.gitlab['signup_enabled'],
terminal_max_session_time: 0,
two_factor_grace_period: 48,
user_default_external: false,
terminal_max_session_time: 0
user_default_external: false
}
end
......@@ -215,6 +222,14 @@ class ApplicationSetting < ActiveRecord::Base
create(defaults)
end
def self.human_attribute_name(attr, _options = {})
if attr == :default_artifacts_expire_in
'Default artifacts expiration'
else
super
end
end
def home_page_url_column_exist
ActiveRecord::Base.connection.column_exists?(:application_settings, :home_page_url)
end
......
......@@ -22,8 +22,10 @@ module Ci
serialize :options
serialize :yaml_variables, Gitlab::Serializer::Ci::Variables
delegate :name, to: :project, prefix: true
validates :coverage, numericality: true, allow_blank: true
validates_presence_of :ref
validates :ref, presence: true
scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) }
......@@ -233,10 +235,6 @@ module Ci
gl_project_id
end
def project_name
project.name
end
def repo_url
auth = "gitlab-ci-token:#{ensure_token!}@"
project.http_url_to_repo.sub(/^https?:\/\//) do |prefix|
......@@ -257,7 +255,7 @@ module Ci
return unless regex
matches = text.scan(Regexp.new(regex)).last
matches = matches.last if matches.kind_of?(Array)
matches = matches.last if matches.is_a?(Array)
coverage = matches.gsub(/\d+(\.\d+)?/).first
if coverage.present?
......@@ -486,7 +484,7 @@ module Ci
def artifacts_expire_in=(value)
self.artifacts_expire_at =
if value
Time.now + ChronicDuration.parse(value)
ChronicDuration.parse(value)&.seconds&.from_now
end
end
......
......@@ -14,9 +14,11 @@ module Ci
has_many :builds, foreign_key: :commit_id
has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id
validates_presence_of :sha, unless: :importing?
validates_presence_of :ref, unless: :importing?
validates_presence_of :status, unless: :importing?
delegate :id, to: :project, prefix: true
validates :sha, presence: { unless: :importing? }
validates :ref, presence: { unless: :importing? }
validates :status, presence: { unless: :importing? }
validate :valid_commit_sha, unless: :importing?
after_create :keep_around_commits, unless: :importing?
......@@ -93,8 +95,11 @@ module Ci
.select("max(#{quoted_table_name}.id)")
.group(:ref, :sha)
relation = ref ? where(ref: ref) : self
relation.where(id: max_id)
if ref
where(ref: ref, id: max_id.where(ref: ref))
else
where(id: max_id)
end
end
def self.latest_status(ref = nil)
......@@ -150,10 +155,6 @@ module Ci
builds.latest.with_artifacts_not_expired.includes(project: [:namespace])
end
def project_id
project.id
end
# For now the only user who participates is the user who triggered
def participants(_current_user = nil)
Array(user)
......
......@@ -4,8 +4,8 @@ module Ci
RUNNER_QUEUE_EXPIRY_TIME = 60.minutes
LAST_CONTACT_TIME = 1.hour.ago
AVAILABLE_SCOPES = %w[specific shared active paused online]
FORM_EDITABLE = %i[description tag_list active run_untagged locked]
AVAILABLE_SCOPES = %w[specific shared active paused online].freeze
FORM_EDITABLE = %i[description tag_list active run_untagged locked].freeze
has_many :builds
has_many :runner_projects, dependent: :destroy
......
......@@ -5,6 +5,6 @@ module Ci
belongs_to :runner
belongs_to :project, foreign_key: :gl_project_id
validates_uniqueness_of :runner_id, scope: :gl_project_id
validates :runner_id, uniqueness: { scope: :gl_project_id }
end
end
......@@ -7,8 +7,8 @@ module Ci
belongs_to :project, foreign_key: :gl_project_id
has_many :trigger_requests, dependent: :destroy
validates_presence_of :token
validates_uniqueness_of :token
validates :token, presence: true
validates :token, uniqueness: true
before_validation :set_default_values
......
......@@ -22,12 +22,12 @@ class Commit
DIFF_HARD_LIMIT_LINES = 50000
# The SHA can be between 7 and 40 hex characters.
COMMIT_SHA_PATTERN = '\h{7,40}'
COMMIT_SHA_PATTERN = '\h{7,40}'.freeze
class << self
def decorate(commits, project)
commits.map do |commit|
if commit.kind_of?(Commit)
if commit.is_a?(Commit)
commit
else
self.new(commit, project)
......@@ -105,7 +105,7 @@ class Commit
end
def diff_line_count
@diff_line_count ||= Commit::diff_line_count(raw_diffs)
@diff_line_count ||= Commit.diff_line_count(raw_diffs)
@diff_line_count
end
......@@ -122,11 +122,12 @@ class Commit
def full_title
return @full_title if @full_title
if safe_message.blank?
@full_title = no_commit_message
else
@full_title = safe_message.split("\n", 2).first
end
@full_title =
if safe_message.blank?
no_commit_message
else
safe_message.split("\n", 2).first
end
end
# Returns the commits description
......
......@@ -10,10 +10,11 @@ class CommitStatus < ActiveRecord::Base
belongs_to :user
delegate :commit, to: :pipeline
delegate :sha, :short_sha, to: :pipeline
validates :pipeline, presence: true, unless: :importing?
validates_presence_of :name
validates :name, presence: true
alias_attribute :author, :user
......@@ -102,8 +103,6 @@ class CommitStatus < ActiveRecord::Base
end
end
delegate :sha, :short_sha, to: :pipeline
def before_sha
pipeline.before_sha || Gitlab::Git::BLANK_SHA
end
......
......@@ -11,14 +11,15 @@ module CacheMarkdownField
# Knows about the relationship between markdown and html field names, and
# stores the rendering contexts for the latter
class FieldData
extend Forwardable
def initialize
@data = {}
end
def_delegators :@data, :[], :[]=
def_delegator :@data, :keys, :markdown_fields
delegate :[], :[]=, to: :@data
def markdown_fields
@data.keys
end
def html_field(markdown_field)
"#{markdown_field}_html"
......@@ -45,7 +46,7 @@ module CacheMarkdownField
Project
Release
Snippet
]
].freeze
def self.caching_classes
CACHING_CLASSES.map(&:constantize)
......
......@@ -13,11 +13,12 @@ module CaseSensitivity
params.each do |key, value|
column = ActiveRecord::Base.connection.quote_table_name(key)
if cast_lower
condition = "LOWER(#{column}) = LOWER(:value)"
else
condition = "#{column} = :value"
end
condition =
if cast_lower
"LOWER(#{column}) = LOWER(:value)"
else
"#{column} = :value"
end
criteria = criteria.where(condition, value: value)
end
......
module HasStatus
extend ActiveSupport::Concern
DEFAULT_STATUS = 'created'
AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped]
STARTED_STATUSES = %w[running success failed skipped]
ACTIVE_STATUSES = %w[pending running]
COMPLETED_STATUSES = %w[success failed canceled skipped]
ORDERED_STATUSES = %w[failed pending running canceled success skipped]
DEFAULT_STATUS = 'created'.freeze
AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped].freeze
STARTED_STATUSES = %w[running success failed skipped].freeze
ACTIVE_STATUSES = %w[pending running].freeze
COMPLETED_STATUSES = %w[success failed canceled skipped].freeze
ORDERED_STATUSES = %w[failed pending running canceled success skipped].freeze
class_methods do
def status_sql
......
......@@ -46,6 +46,17 @@ module Issuable
has_one :metrics
delegate :name,
:email,
to: :author,
prefix: true
delegate :name,
:email,
to: :assignee,
allow_nil: true,
prefix: true
validates :author, presence: true
validates :title, presence: true, length: { maximum: 255 }
......@@ -68,21 +79,10 @@ module Issuable
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :join_project, -> { joins(:project) }
scope :inc_notes_with_associations, -> { includes(notes: [ :project, :author, :award_emoji ]) }
scope :inc_notes_with_associations, -> { includes(notes: [:project, :author, :award_emoji]) }
scope :references_project, -> { references(:project) }
scope :non_archived, -> { join_project.where(projects: { archived: false }) }
delegate :name,
:email,
to: :author,
prefix: true
delegate :name,
:email,
to: :assignee,
allow_nil: true,
prefix: true
attr_mentionable :title, pipeline: :single_line
attr_mentionable :description
......@@ -182,7 +182,7 @@ module Issuable
def grouping_columns(sort)
grouping_columns = [arel_table[:id]]
if ["milestone_due_desc", "milestone_due_asc"].include?(sort)
if %w(milestone_due_desc milestone_due_asc).include?(sort)
milestone_table = Milestone.arel_table
grouping_columns << milestone_table[:id]
grouping_columns << milestone_table[:due_date]
......@@ -235,7 +235,7 @@ module Issuable
# DEPRECATED
repository: project.hook_attrs.slice(:name, :url, :description, :homepage)
}
hook_data.merge!(assignee: assignee.hook_attrs) if assignee
hook_data[:assignee] = assignee.hook_attrs if assignee
hook_data
end
......
......@@ -5,6 +5,6 @@ module ReactiveService
include ReactiveCaching
# Default cache key: class name + project_id
self.reactive_cache_key = ->(service) { [ service.class.model_name.singular, service.project_id ] }
self.reactive_cache_key = ->(service) { [service.class.model_name.singular, service.project_id] }
end
end
......@@ -46,11 +46,12 @@ module Sortable
where("label_links.target_id = #{target_column}").
reorder(nil)
if target_type_column
query = query.where("label_links.target_type = #{target_type_column}")
else
query = query.where(label_links: { target_type: target_type })
end
query =
if target_type_column
query.where("label_links.target_type = #{target_type_column}")
else
query.where(label_links: { target_type: target_type })
end
query = query.where.not(title: excluded_labels) if excluded_labels.present?
......
class Uniquify
# Return a version of the given 'base' string that is unique
# by appending a counter to it. Uniqueness is determined by
# repeated calls to the passed block.
#
# If `base` is a function/proc, we expect that calling it with a
# candidate counter returns a string to test/return.
def string(base)
@base = base
@counter = nil
increment_counter! while yield(base_string)
base_string
end
private
def base_string
if @base.respond_to?(:call)
@base.call(@counter)
else
"#{@base}#{@counter}"
end
end
def increment_counter!
@counter ||= 0
@counter += 1
end
end
......@@ -8,7 +8,7 @@ class DiffNote < Note
validates :position, presence: true
validates :diff_line, presence: true
validates :line_code, presence: true, line_code: true
validates :noteable_type, inclusion: { in: ['Commit', 'MergeRequest'] }
validates :noteable_type, inclusion: { in: %w(Commit MergeRequest) }
validates :resolved_by, presence: true, if: :resolved?
validate :positions_complete
validate :verify_supported
......
......@@ -36,7 +36,7 @@ class Event < ActiveRecord::Base
scope :code_push, -> { where(action: PUSHED) }
scope :in_projects, ->(projects) do
where(project_id: projects).recent
where(project_id: projects.pluck(:id)).recent
end
scope :with_associations, -> { includes(:author, :project, project: :namespace).preload(:target) }
......@@ -47,7 +47,7 @@ class Event < ActiveRecord::Base
def contributions
where("action = ? OR (target_type IN (?) AND action IN (?)) OR (target_type = ? AND action = ?)",
Event::PUSHED,
["MergeRequest", "Issue"], [Event::CREATED, Event::CLOSED, Event::MERGED],
%w(MergeRequest Issue), [Event::CREATED, Event::CLOSED, Event::MERGED],
"Note", Event::COMMENTED)
end
......
......@@ -43,7 +43,7 @@ class ExternalIssue
end
def reference_link_text(from_project = nil)
return "##{id}" if /^\d+$/.match(id)
return "##{id}" if id =~ /^\d+$/
id
end
......
......@@ -15,8 +15,6 @@ class Issue < ActiveRecord::Base
DueThisWeek = DueDateStruct.new('Due This Week', 'week').freeze
DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze
ActsAsTaggableOn.strict_case_match = true
belongs_to :project
belongs_to :moved_to, class_name: 'Issue'
......
......@@ -11,7 +11,7 @@ class Label < ActiveRecord::Base
cache_markdown_field :description, pipeline: :single_line
DEFAULT_COLOR = '#428BCA'
DEFAULT_COLOR = '#428BCA'.freeze
default_value_for :color, DEFAULT_COLOR
......
......@@ -10,6 +10,8 @@ class Member < ActiveRecord::Base
belongs_to :user
belongs_to :source, polymorphic: true
delegate :name, :username, :email, to: :user, prefix: true
validates :user, presence: true, unless: :invite?
validates :source, presence: true
validates :user_id, uniqueness: { scope: [:source_type, :source_id],
......@@ -73,8 +75,6 @@ class Member < ActiveRecord::Base
after_destroy :post_destroy_hook, unless: :pending?
after_commit :refresh_member_authorized_projects
delegate :name, :username, :email, to: :user, prefix: true
default_value_for :notification_level, NotificationSetting.levels[:global]
class << self
......
class GroupMember < Member
SOURCE_TYPE = 'Namespace'
SOURCE_TYPE = 'Namespace'.freeze
belongs_to :group, foreign_key: 'source_id'
# Make sure group member points only to group as it source
default_value_for :source_type, SOURCE_TYPE
validates_format_of :source_type, with: /\ANamespace\z/
validates :source_type, format: { with: /\ANamespace\z/ }
default_scope { where(source_type: SOURCE_TYPE) }
def self.access_level_roles
......
class ProjectMember < Member
SOURCE_TYPE = 'Project'
SOURCE_TYPE = 'Project'.freeze
include Gitlab::ShellAdapter
......@@ -7,7 +7,7 @@ class ProjectMember < Member
# Make sure project member points only to project as it source
default_value_for :source_type, SOURCE_TYPE
validates_format_of :source_type, with: /\AProject\z/
validates :source_type, format: { with: /\AProject\z/ }
validates :access_level, inclusion: { in: Gitlab::Access.values }
default_scope { where(source_type: SOURCE_TYPE) }
......
......@@ -203,7 +203,11 @@ class MergeRequest < ActiveRecord::Base
end
def diff_size
opts = diff_options || {}
# The `#diffs` method ends up at an instance of a class inheriting from
# `Gitlab::Diff::FileCollection::Base`, so use those options as defaults
# here too, to get the same diff size without performing highlighting.
#
opts = Gitlab::Diff::FileCollection::Base.default_options.merge(diff_options || {})
raw_diffs(opts).size
end
......@@ -527,7 +531,7 @@ class MergeRequest < ActiveRecord::Base
}
if diff_head_commit
attrs.merge!(last_commit: diff_head_commit.hook_attrs)
attrs[:last_commit] = diff_head_commit.hook_attrs
end
attributes.merge!(attrs)
......
......@@ -7,7 +7,7 @@ class MergeRequestDiff < ActiveRecord::Base
COMMITS_SAFE_SIZE = 100
# Valid types of serialized diffs allowed by Gitlab::Git::Diff
VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta]
VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta].freeze
belongs_to :merge_request
......
......@@ -98,14 +98,8 @@ class Namespace < ActiveRecord::Base
# Work around that by setting their username to "blank", followed by a counter.
path = "blank" if path.blank?
counter = 0
base = path
while Namespace.find_by_path_or_name(path)
counter += 1
path = "#{base}#{counter}"
end
path
uniquify = Uniquify.new
uniquify.string(path) { |s| Namespace.find_by_path_or_name(s) }
end
end
......
......@@ -188,11 +188,12 @@ module Network
end
# and mark it as reserved
if parent_time.nil?
min_time = leaves.first.time
else
min_time = parent_time + 1
end
min_time =
if parent_time.nil?
leaves.first.time
else
parent_time + 1
end
max_time = leaves.last.time
leaves.last.parents(@map).each do |parent|
......
......@@ -72,7 +72,7 @@ class Note < ActiveRecord::Base
scope :inc_author, ->{ includes(:author) }
scope :inc_relations_for_view, ->{ includes(:project, :author, :updated_by, :resolved_by, :award_emoji) }
scope :diff_notes, ->{ where(type: ['LegacyDiffNote', 'DiffNote']) }
scope :diff_notes, ->{ where(type: %w(LegacyDiffNote DiffNote)) }
scope :non_diff_notes, ->{ where(type: ['Note', nil]) }
scope :with_associations, -> do
......
......@@ -35,11 +35,11 @@ class NotificationSetting < ActiveRecord::Base
:merge_merge_request,
:failed_pipeline,
:success_pipeline
]
].freeze
EXCLUDED_WATCHER_EVENTS = [
:success_pipeline
]
].freeze
store :events, accessors: EMAIL_EVENTS, coder: JSON
......
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.
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.
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