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 ...@@ -51,3 +51,4 @@ eslint-report.html
/builds/* /builds/*
/shared/* /shared/*
/.gitlab_workhorse_secret /.gitlab_workhorse_secret
/webpack-report/
...@@ -240,6 +240,25 @@ rake db:seed_fu: ...@@ -240,6 +240,25 @@ rake db:seed_fu:
paths: paths:
- log/development.log - 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: rake karma:
cache: cache:
paths: paths:
...@@ -388,6 +407,7 @@ pages: ...@@ -388,6 +407,7 @@ pages:
dependencies: dependencies:
- coverage - coverage
- rake karma - rake karma
- rake gitlab:assets:compile
- lint:javascript:report - lint:javascript:report
script: script:
- mv public/ .public/ - mv public/ .public/
...@@ -395,6 +415,7 @@ pages: ...@@ -395,6 +415,7 @@ pages:
- mv coverage/ public/coverage-ruby/ || true - mv coverage/ public/coverage-ruby/ || true
- mv coverage-javascript/ public/coverage-javascript/ || true - mv coverage-javascript/ public/coverage-javascript/ || true
- mv eslint-report.html public/ || true - mv eslint-report.html public/ || true
- mv webpack-report/ public/webpack-report/ || true
artifacts: artifacts:
paths: paths:
- public - public
......
This diff is collapsed.
This diff is collapsed.
...@@ -426,7 +426,7 @@ merge request: ...@@ -426,7 +426,7 @@ merge request:
1. [Ruby](https://github.com/bbatsov/ruby-style-guide). 1. [Ruby](https://github.com/bbatsov/ruby-style-guide).
Important sections include [Source Code Layout][rss-source] and Important sections include [Source Code Layout][rss-source] and
[Naming][rss-naming]. Use: [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 - string literal quoting style **Option A**: single quoted by default
1. [Rails](https://github.com/bbatsov/rails-style-guide) 1. [Rails](https://github.com/bbatsov/rails-style-guide)
1. [Newlines styleguide][newlines-styleguide] 1. [Newlines styleguide][newlines-styleguide]
......
...@@ -20,7 +20,7 @@ gem 'rugged', '~> 0.24.0' ...@@ -20,7 +20,7 @@ gem 'rugged', '~> 0.24.0'
# Authentication libraries # Authentication libraries
gem 'devise', '~> 4.2' gem 'devise', '~> 4.2'
gem 'doorkeeper', '~> 4.2.0' gem 'doorkeeper', '~> 4.2.0'
gem 'omniauth', '~> 1.3.2' gem 'omniauth', '~> 1.4.2'
gem 'omniauth-auth0', '~> 1.4.1' gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6' gem 'omniauth-azure-oauth2', '~> 0.0.6'
gem 'omniauth-cas3', '~> 1.1.2' gem 'omniauth-cas3', '~> 1.1.2'
...@@ -201,7 +201,7 @@ gem 'babosa', '~> 1.0.2' ...@@ -201,7 +201,7 @@ gem 'babosa', '~> 1.0.2'
gem 'loofah', '~> 2.0.3' gem 'loofah', '~> 2.0.3'
# Working with license # Working with license
gem 'licensee', '~> 8.0.0' gem 'licensee', '~> 8.7.0'
# Protect against bruteforcing # Protect against bruteforcing
gem 'rack-attack', '~> 4.4.1' gem 'rack-attack', '~> 4.4.1'
...@@ -301,10 +301,10 @@ group :development, :test do ...@@ -301,10 +301,10 @@ group :development, :test do
gem 'spring-commands-rspec', '~> 1.0.4' gem 'spring-commands-rspec', '~> 1.0.4'
gem 'spring-commands-spinach', '~> 1.1.0' gem 'spring-commands-spinach', '~> 1.1.0'
gem 'rubocop', '~> 0.46.0', require: false gem 'rubocop', '~> 0.47.1', require: false
gem 'rubocop-rspec', '~> 1.9.1', require: false gem 'rubocop-rspec', '~> 1.12.0', require: false
gem 'scss_lint', '~> 0.47.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 'simplecov', '0.12.0', require: false
gem 'flay', '~> 2.6.1', require: false gem 'flay', '~> 2.6.1', require: false
gem 'bundler-audit', '~> 0.5.0', require: false gem 'bundler-audit', '~> 0.5.0', require: false
......
...@@ -319,16 +319,16 @@ GEM ...@@ -319,16 +319,16 @@ GEM
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
haml (4.0.7) haml (4.0.7)
tilt tilt
haml_lint (0.18.2) haml_lint (0.21.0)
haml (~> 4.0) haml (~> 4.0)
rake (>= 10, < 12) rake (>= 10, < 13)
rubocop (>= 0.36.0) rubocop (>= 0.47.0)
sysexits (~> 1.1) sysexits (~> 1.1)
hamlit (2.6.1) hamlit (2.6.1)
temple (~> 0.7.6) temple (~> 0.7.6)
thor thor
tilt tilt
hashie (3.4.4) hashie (3.5.5)
health_check (2.2.1) health_check (2.2.1)
rails (>= 4.0) rails (>= 4.0)
hipchat (1.5.2) hipchat (1.5.2)
...@@ -398,8 +398,8 @@ GEM ...@@ -398,8 +398,8 @@ GEM
rubyzip rubyzip
thor thor
xml-simple xml-simple
licensee (8.0.0) licensee (8.7.0)
rugged (>= 0.24b) rugged (~> 0.24)
little-plugger (1.1.4) little-plugger (1.1.4)
logging (2.1.0) logging (2.1.0)
little-plugger (~> 1.1) little-plugger (~> 1.1)
...@@ -441,7 +441,7 @@ GEM ...@@ -441,7 +441,7 @@ GEM
octokit (4.6.2) octokit (4.6.2)
sawyer (~> 0.8.0, >= 0.5.3) sawyer (~> 0.8.0, >= 0.5.3)
oj (2.17.4) oj (2.17.4)
omniauth (1.3.2) omniauth (1.4.2)
hashie (>= 1.2, < 4) hashie (>= 1.2, < 4)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
omniauth-auth0 (1.4.1) omniauth-auth0 (1.4.1)
...@@ -501,7 +501,7 @@ GEM ...@@ -501,7 +501,7 @@ GEM
os (0.9.6) os (0.9.6)
paranoia (2.2.0) paranoia (2.2.0)
activerecord (>= 4.0, < 5.1) activerecord (>= 4.0, < 5.1)
parser (2.3.1.4) parser (2.4.0.0)
ast (~> 2.2) ast (~> 2.2)
pg (0.18.4) pg (0.18.4)
poltergeist (1.9.0) poltergeist (1.9.0)
...@@ -642,13 +642,13 @@ GEM ...@@ -642,13 +642,13 @@ GEM
pg pg
rails rails
sqlite3 sqlite3
rubocop (0.46.0) rubocop (0.47.1)
parser (>= 2.3.1.1, < 3.0) parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1) powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0) rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1) unicode-display_width (~> 1.0, >= 1.0.1)
rubocop-rspec (1.9.1) rubocop-rspec (1.12.0)
rubocop (>= 0.42.0) rubocop (>= 0.42.0)
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
crack (~> 0.4) crack (~> 0.4)
...@@ -759,7 +759,7 @@ GEM ...@@ -759,7 +759,7 @@ GEM
rack (>= 1, < 3) rack (>= 1, < 3)
thor (0.19.4) thor (0.19.4)
thread_safe (0.3.5) thread_safe (0.3.5)
tilt (2.0.5) tilt (2.0.6)
timecop (0.8.1) timecop (0.8.1)
timfel-krb5-auth (0.8.3) timfel-krb5-auth (0.8.3)
tool (0.2.3) tool (0.2.3)
...@@ -776,7 +776,7 @@ GEM ...@@ -776,7 +776,7 @@ GEM
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.2) unf_ext (0.0.7.2)
unicode-display_width (1.1.1) unicode-display_width (1.1.3)
unicorn (5.1.0) unicorn (5.1.0)
kgio (~> 2.6) kgio (~> 2.6)
raindrops (~> 0.7) raindrops (~> 0.7)
...@@ -888,7 +888,7 @@ DEPENDENCIES ...@@ -888,7 +888,7 @@ DEPENDENCIES
google-api-client (~> 0.8.6) google-api-client (~> 0.8.6)
grape (~> 0.18.0) grape (~> 0.18.0)
grape-entity (~> 0.6.0) grape-entity (~> 0.6.0)
haml_lint (~> 0.18.2) haml_lint (~> 0.21.0)
hamlit (~> 2.6.1) hamlit (~> 2.6.1)
health_check (~> 2.2.0) health_check (~> 2.2.0)
hipchat (~> 1.5.0) hipchat (~> 1.5.0)
...@@ -907,7 +907,7 @@ DEPENDENCIES ...@@ -907,7 +907,7 @@ DEPENDENCIES
kubeclient (~> 2.2.0) kubeclient (~> 2.2.0)
letter_opener_web (~> 1.3.0) letter_opener_web (~> 1.3.0)
license_finder (~> 2.1.0) license_finder (~> 2.1.0)
licensee (~> 8.0.0) licensee (~> 8.7.0)
loofah (~> 2.0.3) loofah (~> 2.0.3)
mail_room (~> 0.9.1) mail_room (~> 0.9.1)
method_source (~> 0.8) method_source (~> 0.8)
...@@ -920,7 +920,7 @@ DEPENDENCIES ...@@ -920,7 +920,7 @@ DEPENDENCIES
oauth2 (~> 1.2.0) oauth2 (~> 1.2.0)
octokit (~> 4.6.2) octokit (~> 4.6.2)
oj (~> 2.17.4) oj (~> 2.17.4)
omniauth (~> 1.3.2) omniauth (~> 1.4.2)
omniauth-auth0 (~> 1.4.1) omniauth-auth0 (~> 1.4.1)
omniauth-authentiq (~> 0.3.0) omniauth-authentiq (~> 0.3.0)
omniauth-azure-oauth2 (~> 0.0.6) omniauth-azure-oauth2 (~> 0.0.6)
...@@ -963,8 +963,8 @@ DEPENDENCIES ...@@ -963,8 +963,8 @@ DEPENDENCIES
rspec-rails (~> 3.5.0) rspec-rails (~> 3.5.0)
rspec-retry (~> 0.4.5) rspec-retry (~> 0.4.5)
rspec_profiling (~> 0.0.5) rspec_profiling (~> 0.0.5)
rubocop (~> 0.46.0) rubocop (~> 0.47.1)
rubocop-rspec (~> 1.9.1) rubocop-rspec (~> 1.12.0)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.16.2) ruby-prof (~> 0.16.2)
rugged (~> 0.24.0) 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 @@ ...@@ -6,12 +6,8 @@
/* global AwardsHandler */ /* global AwardsHandler */
/* global Aside */ /* global Aside */
function requireAll(context) { return context.keys().map(context); }
window.$ = window.jQuery = require('jquery'); window.$ = window.jQuery = require('jquery');
require('jquery-ui/ui/autocomplete');
require('jquery-ui/ui/draggable'); require('jquery-ui/ui/draggable');
require('jquery-ui/ui/effect-highlight');
require('jquery-ui/ui/sortable'); require('jquery-ui/ui/sortable');
require('jquery-ujs'); require('jquery-ujs');
require('vendor/jquery.endless-scroll'); require('vendor/jquery.endless-scroll');
...@@ -46,15 +42,176 @@ require('./shortcuts_dashboard_navigation'); ...@@ -46,15 +42,176 @@ require('./shortcuts_dashboard_navigation');
require('./shortcuts_issuable'); require('./shortcuts_issuable');
require('./shortcuts_network'); require('./shortcuts_network');
require('vendor/jquery.nicescroll'); require('vendor/jquery.nicescroll');
requireAll(require.context('./behaviors', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./blob', false, /^\.\/.*\.(js|es6)$/)); // behaviors
requireAll(require.context('./templates', false, /^\.\/.*\.(js|es6)$/)); require('./behaviors/autosize');
requireAll(require.context('./commit', false, /^\.\/.*\.(js|es6)$/)); require('./behaviors/details_behavior');
requireAll(require.context('./extensions', false, /^\.\/.*\.(js|es6)$/)); require('./behaviors/quick_submit');
requireAll(require.context('./lib/utils', false, /^\.\/.*\.(js|es6)$/)); require('./behaviors/requires_input');
requireAll(require.context('./u2f', false, /^\.\/.*\.(js|es6)$/)); require('./behaviors/toggler_behavior');
requireAll(require.context('./droplab', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('.', false, /^\.\/(?!application\.js).*\.(js|es6)$/)); // 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('vendor/fuzzaldrin-plus');
require('es6-promise').polyfill(); 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 @@ ...@@ -2,7 +2,7 @@
/* global Vue */ /* global Vue */
/* global Sortable */ /* global Sortable */
require('./board_card'); const boardCard = require('./board_card');
require('./board_new_issue'); require('./board_new_issue');
(() => { (() => {
...@@ -14,7 +14,7 @@ require('./board_new_issue'); ...@@ -14,7 +14,7 @@ require('./board_new_issue');
gl.issueBoards.BoardList = Vue.extend({ gl.issueBoards.BoardList = Vue.extend({
template: '#js-board-list-template', template: '#js-board-list-template',
components: { components: {
'board-card': gl.issueBoards.BoardCard, boardCard,
'board-new-issue': gl.issueBoards.BoardNewIssue 'board-new-issue': gl.issueBoards.BoardNewIssue
}, },
props: { props: {
......
...@@ -123,14 +123,18 @@ class List { ...@@ -123,14 +123,18 @@ class List {
if (listFrom) { if (listFrom) {
this.issuesSize += 1; this.issuesSize += 1;
gl.boardService.moveIssue(issue.id, listFrom.id, this.id) this.updateIssueLabel(issue, listFrom);
.then(() => {
listFrom.getIssues(false);
});
} }
} }
} }
updateIssueLabel(issue, listFrom) {
gl.boardService.moveIssue(issue.id, listFrom.id, this.id)
.then(() => {
listFrom.getIssues(false);
});
}
findIssue (id) { findIssue (id) {
return this.issues.filter(issue => issue.id === id)[0]; return this.issues.filter(issue => issue.id === id)[0];
} }
......
...@@ -92,9 +92,12 @@ ...@@ -92,9 +92,12 @@
const issueLists = issue.getLists(); const issueLists = issue.getLists();
const listLabels = issueLists.map(listIssue => listIssue.label); const listLabels = issueLists.map(listIssue => listIssue.label);
// Add to new lists issues if it doesn't already exist
if (!issueTo) { if (!issueTo) {
// Add to new lists issues if it doesn't already exist
listTo.addIssue(issue, listFrom, newIndex); listTo.addIssue(issue, listFrom, newIndex);
} else {
listTo.updateIssueLabel(issue, listFrom);
issueTo.removeLabel(listFrom.label);
} }
if (listTo.type === 'done') { if (listTo.type === 'done') {
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
/* global Shortcuts */ /* global Shortcuts */
const ShortcutsBlob = require('./shortcuts_blob'); const ShortcutsBlob = require('./shortcuts_blob');
const UserCallout = require('./user_callout');
(function() { (function() {
var Dispatcher; var Dispatcher;
...@@ -108,6 +109,9 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -108,6 +109,9 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'projects:compare:show': case 'projects:compare:show':
new gl.Diff(); new gl.Diff();
break; break;
case 'projects:branches:index':
gl.AjaxLoadingSpinner.init();
break;
case 'projects:issues:new': case 'projects:issues:new':
case 'projects:issues:edit': case 'projects:issues:edit':
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
...@@ -274,6 +278,9 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -274,6 +278,9 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'ci:lints:show': case 'ci:lints:show':
new gl.CILintEditor(); new gl.CILintEditor();
break; break;
case 'users:show':
new UserCallout();
break;
} }
switch (path.first()) { switch (path.first()) {
case 'sessions': case 'sessions':
...@@ -310,6 +317,7 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -310,6 +317,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'dashboard': case 'dashboard':
case 'root': case 'root':
shortcut_handler = new ShortcutsDashboardNavigation(); shortcut_handler = new ShortcutsDashboardNavigation();
new UserCallout();
break; break;
case 'profiles': case 'profiles':
new NotificationsForm(); new NotificationsForm();
......
require('./stat_graph_contributors_graph'); import ContributorsStatGraph from './stat_graph_contributors';
require('./stat_graph_contributors_util');
require('./stat_graph_contributors'); // export to global scope
require('./stat_graph'); 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 */ /* 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 */
/* global ContributorsGraph */
/* global ContributorsAuthorGraph */
/* global ContributorsMasterGraph */
/* global ContributorsStatGraphUtil */
/* global d3 */
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() { export default (function() {
this.ContributorsStatGraph = (function() { function ContributorsStatGraph() {}
function ContributorsStatGraph() {}
ContributorsStatGraph.prototype.init = function(log) { ContributorsStatGraph.prototype.init = function(log) {
var author_commits, total_commits; var author_commits, total_commits;
this.parsed_log = ContributorsStatGraphUtil.parse_log(log); this.parsed_log = ContributorsStatGraphUtil.parse_log(log);
this.set_current_field("commits"); this.set_current_field("commits");
total_commits = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field); total_commits = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
author_commits = ContributorsStatGraphUtil.get_author_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_master_graph(total_commits);
this.add_authors_graph(author_commits); this.add_authors_graph(author_commits);
return this.change_date_header(); return this.change_date_header();
}; };
ContributorsStatGraph.prototype.add_master_graph = function(total_data) { ContributorsStatGraph.prototype.add_master_graph = function(total_data) {
this.master_graph = new ContributorsMasterGraph(total_data); this.master_graph = new ContributorsMasterGraph(total_data);
return this.master_graph.draw(); return this.master_graph.draw();
}; };
ContributorsStatGraph.prototype.add_authors_graph = function(author_data) { ContributorsStatGraph.prototype.add_authors_graph = function(author_data) {
var limited_author_data; var limited_author_data;
this.authors = []; this.authors = [];
limited_author_data = author_data.slice(0, 100); limited_author_data = author_data.slice(0, 100);
return _.each(limited_author_data, (function(_this) { return _.each(limited_author_data, (function(_this) {
return function(d) { return function(d) {
var author_graph, author_header; var author_graph, author_header;
author_header = _this.create_author_header(d); author_header = _this.create_author_header(d);
$(".contributors-list").append(author_header); $(".contributors-list").append(author_header);
_this.authors[d.author_name] = author_graph = new ContributorsAuthorGraph(d.dates); _this.authors[d.author_name] = author_graph = new ContributorsAuthorGraph(d.dates);
return author_graph.draw(); return author_graph.draw();
}; };
})(this)); })(this));
}; };
ContributorsStatGraph.prototype.format_author_commit_info = function(author) { ContributorsStatGraph.prototype.format_author_commit_info = function(author) {
var commits; var commits;
commits = $('<span/>', { commits = $('<span/>', {
"class": 'graph-author-commits-count' "class": 'graph-author-commits-count'
}); });
commits.text(author.commits + " commits"); commits.text(author.commits + " commits");
return $('<span/>').append(commits); return $('<span/>').append(commits);
}; };
ContributorsStatGraph.prototype.create_author_header = function(author) { ContributorsStatGraph.prototype.create_author_header = function(author) {
var author_commit_info, author_commit_info_span, author_email, author_name, list_item; var author_commit_info, author_commit_info_span, author_email, author_name, list_item;
list_item = $('<li/>', { list_item = $('<li/>', {
"class": 'person', "class": 'person',
style: 'display: block;' style: 'display: block;'
}); });
author_name = $('<h4>' + author.author_name + '</h4>'); author_name = $('<h4>' + author.author_name + '</h4>');
author_email = $('<p class="graph-author-email">' + author.author_email + '</p>'); author_email = $('<p class="graph-author-email">' + author.author_email + '</p>');
author_commit_info_span = $('<span/>', { author_commit_info_span = $('<span/>', {
"class": 'commits' "class": 'commits'
}); });
author_commit_info = this.format_author_commit_info(author); author_commit_info = this.format_author_commit_info(author);
author_commit_info_span.html(author_commit_info); author_commit_info_span.html(author_commit_info);
list_item.append(author_name); list_item.append(author_name);
list_item.append(author_email); list_item.append(author_email);
list_item.append(author_commit_info_span); list_item.append(author_commit_info_span);
return list_item; return list_item;
}; };
ContributorsStatGraph.prototype.redraw_master = function() { ContributorsStatGraph.prototype.redraw_master = function() {
var total_data; var total_data;
total_data = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field); total_data = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
this.master_graph.set_data(total_data); this.master_graph.set_data(total_data);
return this.master_graph.redraw(); return this.master_graph.redraw();
}; };
ContributorsStatGraph.prototype.redraw_authors = function() { ContributorsStatGraph.prototype.redraw_authors = function() {
var author_commits, x_domain; var author_commits, x_domain;
$("ol").html(""); $("ol").html("");
x_domain = ContributorsGraph.prototype.x_domain; x_domain = ContributorsGraph.prototype.x_domain;
author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field, x_domain); author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field, x_domain);
return _.each(author_commits, (function(_this) { return _.each(author_commits, (function(_this) {
return function(d) { return function(d) {
_this.redraw_author_commit_info(d); _this.redraw_author_commit_info(d);
$(_this.authors[d.author_name].list_item).appendTo("ol"); $(_this.authors[d.author_name].list_item).appendTo("ol");
_this.authors[d.author_name].set_data(d.dates); _this.authors[d.author_name].set_data(d.dates);
return _this.authors[d.author_name].redraw(); return _this.authors[d.author_name].redraw();
}; };
})(this)); })(this));
}; };
ContributorsStatGraph.prototype.set_current_field = function(field) { ContributorsStatGraph.prototype.set_current_field = function(field) {
return this.field = field; return this.field = field;
}; };
ContributorsStatGraph.prototype.change_date_header = function() { ContributorsStatGraph.prototype.change_date_header = function() {
var print, print_date_format, x_domain; var print, print_date_format, x_domain;
x_domain = ContributorsGraph.prototype.x_domain; x_domain = ContributorsGraph.prototype.x_domain;
print_date_format = d3.time.format("%B %e %Y"); print_date_format = d3.time.format("%B %e %Y");
print = print_date_format(x_domain[0]) + " - " + print_date_format(x_domain[1]); print = print_date_format(x_domain[0]) + " - " + print_date_format(x_domain[1]);
return $("#date_header").text(print); return $("#date_header").text(print);
}; };
ContributorsStatGraph.prototype.redraw_author_commit_info = function(author) { ContributorsStatGraph.prototype.redraw_author_commit_info = function(author) {
var author_commit_info, author_list_item; var author_commit_info, author_list_item;
author_list_item = $(this.authors[author.author_name].list_item); author_list_item = $(this.authors[author.author_name].list_item);
author_commit_info = this.format_author_commit_info(author); author_commit_info = this.format_author_commit_info(author);
return author_list_item.find("span").html(author_commit_info); return author_list_item.find("span").html(author_commit_info);
}; };
return ContributorsStatGraph; return ContributorsStatGraph;
})(); })();
}).call(window);
/* 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 */ /* 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 = { export default {
parse_log: function(log) { parse_log: function(log) {
var by_author, by_email, data, entry, i, len, total, normalized_email; var by_author, by_email, data, entry, i, len, total, normalized_email;
total = {}; total = {};
by_author = {}; by_author = {};
by_email = {}; by_email = {};
for (i = 0, len = log.length; i < len; i += 1) { for (i = 0, len = log.length; i < len; i += 1) {
entry = log[i]; entry = log[i];
if (total[entry.date] == null) { if (total[entry.date] == null) {
this.add_date(entry.date, total); 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;
} }
this.add(total, "additions", entry.additions); normalized_email = entry.author_email.toLowerCase();
return this.add(by_author, "additions", entry.additions); data = by_author[entry.author_name] || by_email[normalized_email];
}, if (data == null) {
store_deletions: function(entry, total, by_author) { data = this.add_author(entry, by_author, by_email);
if (entry.deletions == null) {
entry.deletions = 0;
} }
this.add(total, "deletions", entry.deletions); if (!data[entry.date]) {
return this.add(by_author, "deletions", entry.deletions); this.add_date(entry.date, data);
},
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;
} }
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 @@ ...@@ -296,5 +296,57 @@
* @returns {Boolean} * @returns {Boolean}
*/ */
w.gl.utils.convertPermissionToBoolean = permission => permission === 'true'; 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); })(window);
}).call(window); }).call(window);
...@@ -78,7 +78,6 @@ ...@@ -78,7 +78,6 @@
} else { } else {
$(element).find('.assignee-icon').empty(); $(element).find('.assignee-icon').empty();
} }
return $(element).effect('highlight');
}; };
function Milestone() { function Milestone() {
......
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
$value = $block.find('.value'); $value = $block.find('.value');
$loading = $block.find('.block-loading').fadeOut(); $loading = $block.find('.block-loading').fadeOut();
if (issueUpdateURL) { 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>'; milestoneLinkNoneTemplate = '<span class="no-value">None</span>';
collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left"> <%- title %> </span>'); collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left"> <%- title %> </span>');
} }
...@@ -181,8 +181,7 @@ ...@@ -181,8 +181,7 @@
$selectbox.hide(); $selectbox.hide();
$value.css('display', ''); $value.css('display', '');
if (data.milestone != null) { if (data.milestone != null) {
data.milestone.namespace = _this.currentProject.namespace; data.milestone.full_path = _this.currentProject.full_path;
data.milestone.path = _this.currentProject.path;
data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date); data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date);
$value.html(milestoneLinkTemplate(data.milestone)); $value.html(milestoneLinkTemplate(data.milestone));
return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(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() { (function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }, 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; }; 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 @@ ...@@ -20,15 +20,35 @@
}; };
NewBranchForm.prototype.init = function() { NewBranchForm.prototype.init = function() {
if (this.name.val().length > 0) { if (this.name.length && this.name.val().length > 0) {
return this.name.trigger('blur'); return this.name.trigger('blur');
} }
}; };
NewBranchForm.prototype.setupAvailableRefs = function(availableRefs) { NewBranchForm.prototype.setupAvailableRefs = function(availableRefs) {
return this.ref.autocomplete({ var $branchSelect = $('.js-branch-select');
source: availableRefs,
minLength: 1 $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 require('./gl_crop');
function requireAll(context) { return context.keys().map(context); } require('./profile');
requireAll(require.context('.', false, /^\.\/(?!profile_bundle).*\.(js|es6)$/));
...@@ -36,6 +36,9 @@ ...@@ -36,6 +36,9 @@
// Do not update if one dropdown has not selected any option // Do not update if one dropdown has not selected any option
if (!($allowedToMergeInput.length && $allowedToPushInput.length)) return; if (!($allowedToMergeInput.length && $allowedToPushInput.length)) return;
this.$allowedToMergeDropdown.disable();
this.$allowedToPushDropdown.disable();
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: this.$wrap.data('url'), url: this.$wrap.data('url'),
...@@ -53,13 +56,13 @@ ...@@ -53,13 +56,13 @@
}] }]
} }
}, },
success: () => {
this.$wrap.effect('highlight');
},
error() { error() {
$.scrollTo(0); $.scrollTo(0);
new Flash('Failed to update branch!'); new Flash('Failed to update branch!');
} }
}).always(() => {
this.$allowedToMergeDropdown.enable();
this.$allowedToPushDropdown.enable();
}); });
} }
}; };
......
// require everything else in this directory require('./protected_branch_access_dropdown');
function requireAll(context) { return context.keys().map(context); } require('./protected_branch_create');
requireAll(require.context('.', false, /^\.\/(?!protected_branches_bundle).*\.(js|es6)$/)); 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 */ /* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, max-len */
/* global ace */ /* 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() {
$(function() { $(function() {
var editor = ace.edit("editor"); 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 require('./calendar');
function requireAll(context) { return context.keys().map(context); }
requireAll(require.context('.', false, /^\.\/(?!users_bundle).*\.(js|es6)$/));
/* global Vue, Flash, gl */ /* global Vue, Flash, gl */
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign, no-alert */
((gl) => { ((gl) => {
gl.VuePipelineActions = Vue.extend({ gl.VuePipelineActions = Vue.extend({
...@@ -16,6 +16,20 @@ ...@@ -16,6 +16,20 @@
download(name) { download(name) {
return `Download ${name} artifacts`; 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: ` template: `
<td class="pipeline-actions hidden-xs"> <td class="pipeline-actions hidden-xs">
......
...@@ -23,7 +23,7 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s ...@@ -23,7 +23,7 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s
apiScope: 'all', apiScope: 'all',
pageInfo: {}, pageInfo: {},
pagenum: 1, pagenum: 1,
count: { all: 0, running_or_pending: 0 }, count: {},
pageRequest: false, pageRequest: false,
}; };
}, },
......
...@@ -23,6 +23,13 @@ ...@@ -23,6 +23,13 @@
required: true, required: true,
}, },
}, },
updated() {
if (this.builds) {
this.stopDropdownClickPropagation();
}
},
methods: { methods: {
fetchBuilds(e) { fetchBuilds(e) {
const areaExpanded = e.currentTarget.attributes['aria-expanded']; const areaExpanded = e.currentTarget.attributes['aria-expanded'];
...@@ -37,17 +44,19 @@ ...@@ -37,17 +44,19 @@
return flash; 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: { computed: {
...@@ -76,13 +85,13 @@ ...@@ -76,13 +85,13 @@
template: ` template: `
<div> <div>
<button <button
@click='fetchBuilds($event)' @click="fetchBuilds($event)"
:class="triggerButtonClass" :class="triggerButtonClass"
:title='stage.title' :title="stage.title"
data-placement="top" data-placement="top"
data-toggle="dropdown" data-toggle="dropdown"
type="button" type="button"
:aria-label='stage.title' :aria-label="stage.title"
> >
<span v-html="svg" aria-hidden="true"></span> <span v-html="svg" aria-hidden="true"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i> <i class="fa fa-caret-down" aria-hidden="true"></i>
...@@ -90,7 +99,6 @@ ...@@ -90,7 +99,6 @@
<ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"> <ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
<div class="arrow-up" aria-hidden="true"></div> <div class="arrow-up" aria-hidden="true"></div>
<div <div
@click='keepGraph($event)'
:class="dropdownClass" :class="dropdownClass"
class="js-builds-dropdown-list scrollable-menu" class="js-builds-dropdown-list scrollable-menu"
v-html="buildsOrSpinner" v-html="buildsOrSpinner"
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
* This is a manifest file that'll automatically include all the stylesheets available in this directory * 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 * 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. * 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 jquery.atwho
*= require select2 *= require select2
*= require_self *= require_self
......
...@@ -2,17 +2,6 @@ ...@@ -2,17 +2,6 @@
font-family: $regular_font; font-family: $regular_font;
font-size: $font-size-base; 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 { .ui-state-default {
border: 1px solid $white-light; border: 1px solid $white-light;
background: $white-light; background: $white-light;
......
...@@ -29,16 +29,14 @@ ...@@ -29,16 +29,14 @@
} }
} }
@media (min-width: $screen-sm-min) {
.content-wrapper {
padding-right: $gutter_collapsed_width;
}
}
.right-sidebar-collapsed { .right-sidebar-collapsed {
padding-right: 0; padding-right: 0;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
.content-wrapper {
padding-right: $gutter_collapsed_width;
}
.merge-request-tabs-holder.affix { .merge-request-tabs-holder.affix {
right: $gutter_collapsed_width; right: $gutter_collapsed_width;
} }
...@@ -56,6 +54,12 @@ ...@@ -56,6 +54,12 @@
.right-sidebar-expanded { .right-sidebar-expanded {
padding-right: 0; 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) { @media (min-width: $screen-md-min) {
.content-wrapper { .content-wrapper {
padding-right: $gutter_width; padding-right: $gutter_width;
......
...@@ -277,3 +277,41 @@ table.u2f-registrations { ...@@ -277,3 +277,41 @@ table.u2f-registrations {
padding-left: 18px; 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 ...@@ -83,6 +83,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:akismet_api_key, :akismet_api_key,
:akismet_enabled, :akismet_enabled,
:container_registry_token_expire_delay, :container_registry_token_expire_delay,
:default_artifacts_expire_in,
:default_branch_protection, :default_branch_protection,
:default_group_visibility, :default_group_visibility,
:default_project_visibility, :default_project_visibility,
......
...@@ -3,7 +3,7 @@ class Admin::SystemInfoController < Admin::ApplicationController ...@@ -3,7 +3,7 @@ class Admin::SystemInfoController < Admin::ApplicationController
'nobrowse', 'nobrowse',
'read-only', 'read-only',
'ro' 'ro'
] ].freeze
EXCLUDED_MOUNT_TYPES = [ EXCLUDED_MOUNT_TYPES = [
'autofs', 'autofs',
...@@ -27,7 +27,7 @@ class Admin::SystemInfoController < Admin::ApplicationController ...@@ -27,7 +27,7 @@ class Admin::SystemInfoController < Admin::ApplicationController
'tmpfs', 'tmpfs',
'tracefs', 'tracefs',
'vfat' 'vfat'
] ].freeze
def show def show
@cpus = Vmstat.cpu rescue nil @cpus = Vmstat.cpu rescue nil
......
...@@ -181,7 +181,7 @@ class ApplicationController < ActionController::Base ...@@ -181,7 +181,7 @@ class ApplicationController < ActionController::Base
end end
def gitlab_ldap_access(&block) def gitlab_ldap_access(&block)
Gitlab::LDAP::Access.open { |access| block.call(access) } Gitlab::LDAP::Access.open { |access| yield(access) }
end end
# JSON for infinite scroll via Pager object # JSON for infinite scroll via Pager object
......
...@@ -101,13 +101,14 @@ module CreatesCommit ...@@ -101,13 +101,14 @@ module CreatesCommit
# TODO: We should really clean this up # TODO: We should really clean this up
def set_commit_variables def set_commit_variables
if can?(current_user, :push_code, @project) @mr_source_project =
# Edit file in this project if can?(current_user, :push_code, @project)
@mr_source_project = @project # Edit file in this project
else @project
# Merge request from fork to this project else
@mr_source_project = current_user.fork_of(@project) # Merge request from fork to this project
end current_user.fork_of(@project)
end
# Merge request to this project # Merge request to this project
@mr_target_project = @project @mr_target_project = @project
......
...@@ -59,10 +59,10 @@ module ServiceParams ...@@ -59,10 +59,10 @@ module ServiceParams
:user_key, :user_key,
:username, :username,
:webhook :webhook
] ].freeze
# Parameters to ignore if no value is specified # Parameters to ignore if no value is specified
FILTER_BLANK_PARAMS = [:password] FILTER_BLANK_PARAMS = [:password].freeze
def service_params def service_params
dynamic_params = @service.event_channel_names + @service.event_names dynamic_params = @service.event_channel_names + @service.event_names
......
...@@ -27,7 +27,7 @@ module SpammableActions ...@@ -27,7 +27,7 @@ module SpammableActions
render :verify render :verify
else else
fallback.call yield
end end
end end
......
...@@ -5,7 +5,7 @@ class JwtController < ApplicationController ...@@ -5,7 +5,7 @@ class JwtController < ApplicationController
SERVICES = { SERVICES = {
Auth::ContainerRegistryAuthenticationService::AUDIENCE => Auth::ContainerRegistryAuthenticationService, Auth::ContainerRegistryAuthenticationService::AUDIENCE => Auth::ContainerRegistryAuthenticationService,
} }.freeze
def auth def auth
service = SERVICES[params[:service]] service = SERVICES[params[:service]]
...@@ -39,7 +39,8 @@ class JwtController < ApplicationController ...@@ -39,7 +39,8 @@ class JwtController < ApplicationController
message: "HTTP Basic: Access denied\n" \ message: "HTTP Basic: Access denied\n" \
"You have 2FA enabled, please use a personal access token for Git over HTTP.\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}" } "You can generate one at #{profile_personal_access_tokens_url}" }
] }, status: 401 ]
}, status: 401
end end
def render_unauthorized def render_unauthorized
...@@ -47,7 +48,8 @@ class JwtController < ApplicationController ...@@ -47,7 +48,8 @@ class JwtController < ApplicationController
errors: [ errors: [
{ code: 'UNAUTHORIZED', { code: 'UNAUTHORIZED',
message: 'HTTP Basic: Access denied' } message: 'HTTP Basic: Access denied' }
] }, status: 401 ]
}, status: 401
end end
def auth_params def auth_params
......
...@@ -80,7 +80,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController ...@@ -80,7 +80,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def build_qr_code def build_qr_code
uri = current_user.otp_provisioning_uri(account_string, issuer: issuer_host) 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 end
def account_string def account_string
......
class Projects::BranchesController < Projects::ApplicationController class Projects::BranchesController < Projects::ApplicationController
include ActionView::Helpers::SanitizeHelper include ActionView::Helpers::SanitizeHelper
include SortingHelper include SortingHelper
# Authorize # Authorize
before_action :require_non_empty_project before_action :require_non_empty_project, except: :create
before_action :authorize_download_code! before_action :authorize_download_code!
before_action :authorize_push_code!, only: [:new, :create, :destroy, :destroy_all_merged] before_action :authorize_push_code!, only: [:new, :create, :destroy, :destroy_all_merged]
...@@ -32,6 +33,8 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -32,6 +33,8 @@ class Projects::BranchesController < Projects::ApplicationController
branch_name = sanitize(strip_tags(params[:branch_name])) branch_name = sanitize(strip_tags(params[:branch_name]))
branch_name = Addressable::URI.unescape(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). result = CreateBranchService.new(project, current_user).
execute(branch_name, ref) execute(branch_name, ref)
...@@ -42,8 +45,15 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -42,8 +45,15 @@ class Projects::BranchesController < Projects::ApplicationController
if result[:status] == :success if result[:status] == :success
@branch = result[:branch] @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 else
@error = result[:message] @error = result[:message]
render action: 'new' render action: 'new'
...@@ -76,7 +86,19 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -76,7 +86,19 @@ class Projects::BranchesController < Projects::ApplicationController
ref_escaped = sanitize(strip_tags(params[:ref])) ref_escaped = sanitize(strip_tags(params[:ref]))
Addressable::URI.unescape(ref_escaped) Addressable::URI.unescape(ref_escaped)
else else
@project.default_branch @project.default_branch || 'master'
end end
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 end
...@@ -76,11 +76,12 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -76,11 +76,12 @@ class Projects::GitHttpClientController < Projects::ApplicationController
return @project if defined?(@project) return @project if defined?(@project)
project_id, _ = project_id_with_suffix project_id, _ = project_id_with_suffix
if project_id.blank? @project =
@project = nil if project_id.blank?
else nil
@project = Project.find_by_full_path("#{params[:namespace_id]}/#{project_id}") else
end Project.find_by_full_path("#{params[:namespace_id]}/#{project_id}")
end
end end
# This method returns two values so that we can parse # This method returns two values so that we can parse
......
...@@ -381,14 +381,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -381,14 +381,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def merge_widget_refresh def merge_widget_refresh
if merge_request.merge_when_build_succeeds @status =
@status = :merge_when_build_succeeds if merge_request.merge_when_build_succeeds
else :merge_when_build_succeeds
# Only MRs that can be merged end in this action else
# MR can be already picked up for merge / merged already or can be waiting for worker to be picked up # Only MRs that can be merged end in this action
# in last case it does not have any special status. Possible error is handled inside widget js function # MR can be already picked up for merge / merged already or can be waiting for worker to be picked up
@status = :success # in last case it does not have any special status. Possible error is handled inside widget js function
end :success
end
render 'merge' render 'merge'
end end
......
...@@ -13,9 +13,15 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -13,9 +13,15 @@ class Projects::PipelinesController < Projects::ApplicationController
.page(params[:page]) .page(params[:page])
.per(30) .per(30)
@running_or_pending_count = PipelinesFinder @running_count = PipelinesFinder
.new(project).execute(scope: 'running').count .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 @pipelines_count = PipelinesFinder
.new(project).execute.count .new(project).execute.count
...@@ -29,7 +35,9 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -29,7 +35,9 @@ class Projects::PipelinesController < Projects::ApplicationController
.represent(@pipelines), .represent(@pipelines),
count: { count: {
all: @pipelines_count, all: @pipelines_count,
running_or_pending: @running_or_pending_count running: @running_count,
pending: @pending_count,
finished: @finished_count,
} }
} }
end end
......
...@@ -15,11 +15,12 @@ class SessionsController < Devise::SessionsController ...@@ -15,11 +15,12 @@ class SessionsController < Devise::SessionsController
def new def new
set_minimum_password_length set_minimum_password_length
if Gitlab.config.ldap.enabled @ldap_servers =
@ldap_servers = Gitlab::LDAP::Config.servers if Gitlab.config.ldap.enabled
else Gitlab::LDAP::Config.servers
@ldap_servers = [] else
end []
end
super super
end end
......
...@@ -28,8 +28,9 @@ class SnippetsController < ApplicationController ...@@ -28,8 +28,9 @@ class SnippetsController < ApplicationController
@snippets = SnippetsFinder.new.execute(current_user, { @snippets = SnippetsFinder.new.execute(current_user, {
filter: :by_user, filter: :by_user,
user: @user, user: @user,
scope: params[:scope] }). scope: params[:scope]
page(params[:page]) })
.page(params[:page])
render 'index' render 'index'
else else
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
# iids: integer[] # iids: integer[]
# #
class IssuableFinder class IssuableFinder
NONE = '0' NONE = '0'.freeze
attr_accessor :current_user, :params attr_accessor :current_user, :params
......
...@@ -28,11 +28,12 @@ class NotesFinder ...@@ -28,11 +28,12 @@ class NotesFinder
private private
def init_collection def init_collection
if @params[:target_id] @notes =
@notes = on_target(@params[:target_type], @params[:target_id]) if @params[:target_id]
else on_target(@params[:target_type], @params[:target_id])
@notes = notes_of_any_type else
end notes_of_any_type
end
end end
def notes_of_any_type def notes_of_any_type
......
...@@ -10,7 +10,11 @@ class PipelinesFinder ...@@ -10,7 +10,11 @@ class PipelinesFinder
scoped_pipelines = scoped_pipelines =
case scope case scope
when 'running' when 'running'
pipelines.running_or_pending pipelines.running
when 'pending'
pipelines.pending
when 'finished'
pipelines.finished
when 'branches' when 'branches'
from_ids(ids_for_ref(branches)) from_ids(ids_for_ref(branches))
when 'tags' when 'tags'
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
# #
class TodosFinder class TodosFinder
NONE = '0' NONE = '0'.freeze
attr_accessor :current_user, :params attr_accessor :current_user, :params
...@@ -99,7 +99,7 @@ class TodosFinder ...@@ -99,7 +99,7 @@ class TodosFinder
end end
def type? def type?
type.present? && ['Issue', 'MergeRequest'].include?(type) type.present? && %w(Issue MergeRequest).include?(type)
end end
def type def type
......
...@@ -69,11 +69,12 @@ module ApplicationHelper ...@@ -69,11 +69,12 @@ module ApplicationHelper
end end
def avatar_icon(user_or_email = nil, size = nil, scale = 2) def avatar_icon(user_or_email = nil, size = nil, scale = 2)
if user_or_email.is_a?(User) user =
user = user_or_email if user_or_email.is_a?(User)
else user_or_email
user = User.find_by_any_email(user_or_email.try(:downcase)) else
end User.find_by_any_email(user_or_email.try(:downcase))
end
if user if user
user.avatar_url(size) || default_avatar user.avatar_url(size) || default_avatar
......
module ApplicationSettingsHelper module ApplicationSettingsHelper
def gravatar_enabled? delegate :gravatar_enabled?,
current_application_settings.gravatar_enabled? :signup_enabled?,
end :signin_enabled?,
:akismet_enabled?,
def signup_enabled? :koding_enabled?,
current_application_settings.signup_enabled? to: :current_application_settings
end
def signin_enabled?
current_application_settings.signin_enabled?
end
def user_oauth_applications? def user_oauth_applications?
current_application_settings.user_oauth_applications current_application_settings.user_oauth_applications
end end
def askimet_enabled?
current_application_settings.akismet_enabled?
end
def koding_enabled?
current_application_settings.koding_enabled?
end
def allowed_protocols_present? def allowed_protocols_present?
current_application_settings.enabled_git_access_protocol.present? current_application_settings.enabled_git_access_protocol.present?
end end
......
...@@ -153,16 +153,17 @@ module BlobHelper ...@@ -153,16 +153,17 @@ module BlobHelper
# Because we are opionated we set the cache headers ourselves. # Because we are opionated we set the cache headers ourselves.
response.cache_control[:public] = @project.public? response.cache_control[:public] = @project.public?
if @ref && @commit && @ref == @commit.id response.cache_control[:max_age] =
# This is a link to a commit by its commit SHA. That means that the blob if @ref && @commit && @ref == @commit.id
# is immutable. The only reason to invalidate the cache is if the commit # This is a link to a commit by its commit SHA. That means that the blob
# was deleted or if the user lost access to the repository. # is immutable. The only reason to invalidate the cache is if the commit
response.cache_control[:max_age] = Blob::CACHE_TIME_IMMUTABLE # was deleted or if the user lost access to the repository.
else Blob::CACHE_TIME_IMMUTABLE
# A branch or tag points at this blob. That means that the expected blob else
# value may change over time. # A branch or tag points at this blob. That means that the expected blob
response.cache_control[:max_age] = Blob::CACHE_TIME # value may change over time.
end Blob::CACHE_TIME
end
response.etag = @blob.id response.etag = @blob.id
!stale !stale
......
...@@ -34,7 +34,7 @@ module ButtonHelper ...@@ -34,7 +34,7 @@ module ButtonHelper
content_tag (append_link ? :a : :span), protocol, content_tag (append_link ? :a : :span), protocol,
class: klass, class: klass,
href: (project.http_url_to_repo if append_link), href: (project.http_url_to_repo(current_user) if append_link),
data: { data: {
html: true, html: true,
placement: placement, placement: placement,
......
...@@ -24,7 +24,7 @@ module EmailsHelper ...@@ -24,7 +24,7 @@ module EmailsHelper
def action_title(url) def action_title(url)
return unless url return unless url
["merge_requests", "issues", "commit"].each do |action| %w(merge_requests issues commit).each do |action|
if url.split("/").include?(action) if url.split("/").include?(action)
return "View #{action.humanize.singularize}" return "View #{action.humanize.singularize}"
end end
......
...@@ -23,7 +23,7 @@ module IssuablesHelper ...@@ -23,7 +23,7 @@ module IssuablesHelper
def issuable_json_path(issuable) def issuable_json_path(issuable)
project = issuable.project project = issuable.project
if issuable.kind_of?(MergeRequest) if issuable.is_a?(MergeRequest)
namespace_project_merge_request_path(project.namespace, project, issuable.iid, :json) namespace_project_merge_request_path(project.namespace, project, issuable.iid, :json)
else else
namespace_project_issue_path(project.namespace, project, issuable.iid, :json) namespace_project_issue_path(project.namespace, project, issuable.iid, :json)
...@@ -52,7 +52,7 @@ module IssuablesHelper ...@@ -52,7 +52,7 @@ module IssuablesHelper
field_name: 'issuable_template', field_name: 'issuable_template',
selected: selected_template(issuable), selected: selected_template(issuable),
project_path: ref_project.path, project_path: ref_project.path,
namespace_path: ref_project.namespace.path namespace_path: ref_project.namespace.full_path
} }
} }
...@@ -198,7 +198,7 @@ module IssuablesHelper ...@@ -198,7 +198,7 @@ module IssuablesHelper
@counts[issuable_type][state] @counts[issuable_type][state]
end 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 private_constant :IRRELEVANT_PARAMS_FOR_CACHE_KEY
def issuables_state_counter_cache_key(issuable_type, state) def issuables_state_counter_cache_key(issuable_type, state)
......
...@@ -2,6 +2,7 @@ module JavascriptHelper ...@@ -2,6 +2,7 @@ module JavascriptHelper
def page_specific_javascript_tag(js) def page_specific_javascript_tag(js)
javascript_include_tag asset_path(js) javascript_include_tag asset_path(js)
end end
def page_specific_javascript_bundle_tag(js) def page_specific_javascript_bundle_tag(js)
javascript_include_tag(*webpack_asset_paths(js)) javascript_include_tag(*webpack_asset_paths(js))
end end
......
...@@ -33,7 +33,7 @@ module NamespacesHelper ...@@ -33,7 +33,7 @@ module NamespacesHelper
end end
def namespace_icon(namespace, size = 40) def namespace_icon(namespace, size = 40)
if namespace.kind_of?(Group) if namespace.is_a?(Group)
group_icon(namespace) group_icon(namespace)
else else
avatar_icon(namespace.owner.email, size) avatar_icon(namespace.owner.email, size)
......
...@@ -150,6 +150,15 @@ module ProjectsHelper ...@@ -150,6 +150,15 @@ module ProjectsHelper
).html_safe ).html_safe
end 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 private
def repo_children_classes(field) def repo_children_classes(field)
...@@ -232,7 +241,7 @@ module ProjectsHelper ...@@ -232,7 +241,7 @@ module ProjectsHelper
when 'ssh' when 'ssh'
project.ssh_url_to_repo project.ssh_url_to_repo
else else
project.http_url_to_repo project.http_url_to_repo(current_user)
end end
end end
......
...@@ -30,7 +30,7 @@ module SortingHelper ...@@ -30,7 +30,7 @@ module SortingHelper
} }
if current_controller?('admin/projects') 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 end
options options
......
...@@ -37,8 +37,8 @@ module SubmoduleHelper ...@@ -37,8 +37,8 @@ module SubmoduleHelper
end end
def self_url?(url, namespace, project) def self_url?(url, namespace, project)
return true if url == [ Gitlab.config.gitlab.url, '/', namespace, '/', return true if url == [Gitlab.config.gitlab.url, '/', namespace, '/',
project, '.git' ].join('') project, '.git'].join('')
url == gitlab_shell.url_to_repo([namespace, '/', project].join('')) url == gitlab_shell.url_to_repo([namespace, '/', project].join(''))
end end
...@@ -48,8 +48,8 @@ module SubmoduleHelper ...@@ -48,8 +48,8 @@ module SubmoduleHelper
end end
def standard_links(host, namespace, project, commit) def standard_links(host, namespace, project, commit)
base = [ 'https://', host, '/', namespace, '/', project ].join('') base = ['https://', host, '/', namespace, '/', project].join('')
[base, [ base, '/tree/', commit ].join('')] [base, [base, '/tree/', commit].join('')]
end end
def relative_self_links(url, commit) def relative_self_links(url, commit)
......
...@@ -99,7 +99,7 @@ module TabHelper ...@@ -99,7 +99,7 @@ module TabHelper
return 'active' return 'active'
end 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" "active"
end end
end end
......
...@@ -150,6 +150,6 @@ module TodosHelper ...@@ -150,6 +150,6 @@ module TodosHelper
private private
def show_todo_state?(todo) 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
end end
...@@ -89,13 +89,9 @@ module VisibilityLevelHelper ...@@ -89,13 +89,9 @@ module VisibilityLevelHelper
current_application_settings.restricted_visibility_levels || [] current_application_settings.restricted_visibility_levels || []
end end
def default_project_visibility delegate :default_project_visibility,
current_application_settings.default_project_visibility :default_group_visibility,
end to: :current_application_settings
def default_group_visibility
current_application_settings.default_group_visibility
end
def skip_level?(form_model, level) def skip_level?(form_model, level)
form_model.is_a?(Project) && !form_model.visibility_level_allowed?(level) form_model.is_a?(Project) && !form_model.visibility_level_allowed?(level)
......
class RepositoryCheckMailer < BaseMailer class RepositoryCheckMailer < BaseMailer
def notify(failed_count) def notify(failed_count)
if failed_count == 1 @message =
@message = "One project failed its last repository check" if failed_count == 1
else "One project failed its last repository check"
@message = "#{failed_count} projects failed their last repository check" else
end "#{failed_count} projects failed their last repository check"
end
mail( mail(
to: User.admins.pluck(:email), to: User.admins.pluck(:email),
......
...@@ -5,7 +5,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -5,7 +5,7 @@ class ApplicationSetting < ActiveRecord::Base
add_authentication_token_field :runners_registration_token add_authentication_token_field :runners_registration_token
add_authentication_token_field :health_check_access_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 DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace
| # or | # or
\s # any whitespace character \s # any whitespace character
...@@ -76,6 +76,12 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -76,6 +76,12 @@ class ApplicationSetting < ActiveRecord::Base
presence: true, presence: true,
numericality: { only_integer: true, greater_than: 0 } 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, validates :container_registry_token_expire_delay,
presence: true, presence: true,
numericality: { only_integer: true, greater_than: 0 } numericality: { only_integer: true, greater_than: 0 }
...@@ -168,6 +174,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -168,6 +174,7 @@ class ApplicationSetting < ActiveRecord::Base
after_sign_up_text: nil, after_sign_up_text: nil,
akismet_enabled: false, akismet_enabled: false,
container_registry_token_expire_delay: 5, container_registry_token_expire_delay: 5,
default_artifacts_expire_in: '30 days',
default_branch_protection: Settings.gitlab['default_branch_protection'], default_branch_protection: Settings.gitlab['default_branch_protection'],
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_projects_limit: Settings.gitlab['default_projects_limit'], default_projects_limit: Settings.gitlab['default_projects_limit'],
...@@ -201,9 +208,9 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -201,9 +208,9 @@ class ApplicationSetting < ActiveRecord::Base
sign_in_text: nil, sign_in_text: nil,
signin_enabled: Settings.gitlab['signin_enabled'], signin_enabled: Settings.gitlab['signin_enabled'],
signup_enabled: Settings.gitlab['signup_enabled'], signup_enabled: Settings.gitlab['signup_enabled'],
terminal_max_session_time: 0,
two_factor_grace_period: 48, two_factor_grace_period: 48,
user_default_external: false, user_default_external: false
terminal_max_session_time: 0
} }
end end
...@@ -215,6 +222,14 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -215,6 +222,14 @@ class ApplicationSetting < ActiveRecord::Base
create(defaults) create(defaults)
end 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 def home_page_url_column_exist
ActiveRecord::Base.connection.column_exists?(:application_settings, :home_page_url) ActiveRecord::Base.connection.column_exists?(:application_settings, :home_page_url)
end end
......
...@@ -22,8 +22,10 @@ module Ci ...@@ -22,8 +22,10 @@ module Ci
serialize :options serialize :options
serialize :yaml_variables, Gitlab::Serializer::Ci::Variables serialize :yaml_variables, Gitlab::Serializer::Ci::Variables
delegate :name, to: :project, prefix: true
validates :coverage, numericality: true, allow_blank: true validates :coverage, numericality: true, allow_blank: true
validates_presence_of :ref validates :ref, presence: true
scope :unstarted, ->() { where(runner_id: nil) } scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) } scope :ignore_failures, ->() { where(allow_failure: false) }
...@@ -233,10 +235,6 @@ module Ci ...@@ -233,10 +235,6 @@ module Ci
gl_project_id gl_project_id
end end
def project_name
project.name
end
def repo_url def repo_url
auth = "gitlab-ci-token:#{ensure_token!}@" auth = "gitlab-ci-token:#{ensure_token!}@"
project.http_url_to_repo.sub(/^https?:\/\//) do |prefix| project.http_url_to_repo.sub(/^https?:\/\//) do |prefix|
...@@ -257,7 +255,7 @@ module Ci ...@@ -257,7 +255,7 @@ module Ci
return unless regex return unless regex
matches = text.scan(Regexp.new(regex)).last 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 coverage = matches.gsub(/\d+(\.\d+)?/).first
if coverage.present? if coverage.present?
...@@ -486,7 +484,7 @@ module Ci ...@@ -486,7 +484,7 @@ module Ci
def artifacts_expire_in=(value) def artifacts_expire_in=(value)
self.artifacts_expire_at = self.artifacts_expire_at =
if value if value
Time.now + ChronicDuration.parse(value) ChronicDuration.parse(value)&.seconds&.from_now
end end
end end
......
...@@ -14,9 +14,11 @@ module Ci ...@@ -14,9 +14,11 @@ module Ci
has_many :builds, foreign_key: :commit_id has_many :builds, foreign_key: :commit_id
has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id
validates_presence_of :sha, unless: :importing? delegate :id, to: :project, prefix: true
validates_presence_of :ref, unless: :importing?
validates_presence_of :status, unless: :importing? validates :sha, presence: { unless: :importing? }
validates :ref, presence: { unless: :importing? }
validates :status, presence: { unless: :importing? }
validate :valid_commit_sha, unless: :importing? validate :valid_commit_sha, unless: :importing?
after_create :keep_around_commits, unless: :importing? after_create :keep_around_commits, unless: :importing?
...@@ -93,8 +95,11 @@ module Ci ...@@ -93,8 +95,11 @@ module Ci
.select("max(#{quoted_table_name}.id)") .select("max(#{quoted_table_name}.id)")
.group(:ref, :sha) .group(:ref, :sha)
relation = ref ? where(ref: ref) : self if ref
relation.where(id: max_id) where(ref: ref, id: max_id.where(ref: ref))
else
where(id: max_id)
end
end end
def self.latest_status(ref = nil) def self.latest_status(ref = nil)
...@@ -150,10 +155,6 @@ module Ci ...@@ -150,10 +155,6 @@ module Ci
builds.latest.with_artifacts_not_expired.includes(project: [:namespace]) builds.latest.with_artifacts_not_expired.includes(project: [:namespace])
end end
def project_id
project.id
end
# For now the only user who participates is the user who triggered # For now the only user who participates is the user who triggered
def participants(_current_user = nil) def participants(_current_user = nil)
Array(user) Array(user)
......
...@@ -4,8 +4,8 @@ module Ci ...@@ -4,8 +4,8 @@ module Ci
RUNNER_QUEUE_EXPIRY_TIME = 60.minutes RUNNER_QUEUE_EXPIRY_TIME = 60.minutes
LAST_CONTACT_TIME = 1.hour.ago LAST_CONTACT_TIME = 1.hour.ago
AVAILABLE_SCOPES = %w[specific shared active paused online] AVAILABLE_SCOPES = %w[specific shared active paused online].freeze
FORM_EDITABLE = %i[description tag_list active run_untagged locked] FORM_EDITABLE = %i[description tag_list active run_untagged locked].freeze
has_many :builds has_many :builds
has_many :runner_projects, dependent: :destroy has_many :runner_projects, dependent: :destroy
......
...@@ -5,6 +5,6 @@ module Ci ...@@ -5,6 +5,6 @@ module Ci
belongs_to :runner belongs_to :runner
belongs_to :project, foreign_key: :gl_project_id 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
end end
...@@ -7,8 +7,8 @@ module Ci ...@@ -7,8 +7,8 @@ module Ci
belongs_to :project, foreign_key: :gl_project_id belongs_to :project, foreign_key: :gl_project_id
has_many :trigger_requests, dependent: :destroy has_many :trigger_requests, dependent: :destroy
validates_presence_of :token validates :token, presence: true
validates_uniqueness_of :token validates :token, uniqueness: true
before_validation :set_default_values before_validation :set_default_values
......
...@@ -22,12 +22,12 @@ class Commit ...@@ -22,12 +22,12 @@ class Commit
DIFF_HARD_LIMIT_LINES = 50000 DIFF_HARD_LIMIT_LINES = 50000
# The SHA can be between 7 and 40 hex characters. # 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 class << self
def decorate(commits, project) def decorate(commits, project)
commits.map do |commit| commits.map do |commit|
if commit.kind_of?(Commit) if commit.is_a?(Commit)
commit commit
else else
self.new(commit, project) self.new(commit, project)
...@@ -105,7 +105,7 @@ class Commit ...@@ -105,7 +105,7 @@ class Commit
end end
def diff_line_count 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 @diff_line_count
end end
...@@ -122,11 +122,12 @@ class Commit ...@@ -122,11 +122,12 @@ class Commit
def full_title def full_title
return @full_title if @full_title return @full_title if @full_title
if safe_message.blank? @full_title =
@full_title = no_commit_message if safe_message.blank?
else no_commit_message
@full_title = safe_message.split("\n", 2).first else
end safe_message.split("\n", 2).first
end
end end
# Returns the commits description # Returns the commits description
......
...@@ -10,10 +10,11 @@ class CommitStatus < ActiveRecord::Base ...@@ -10,10 +10,11 @@ class CommitStatus < ActiveRecord::Base
belongs_to :user belongs_to :user
delegate :commit, to: :pipeline delegate :commit, to: :pipeline
delegate :sha, :short_sha, to: :pipeline
validates :pipeline, presence: true, unless: :importing? validates :pipeline, presence: true, unless: :importing?
validates_presence_of :name validates :name, presence: true
alias_attribute :author, :user alias_attribute :author, :user
...@@ -102,8 +103,6 @@ class CommitStatus < ActiveRecord::Base ...@@ -102,8 +103,6 @@ class CommitStatus < ActiveRecord::Base
end end
end end
delegate :sha, :short_sha, to: :pipeline
def before_sha def before_sha
pipeline.before_sha || Gitlab::Git::BLANK_SHA pipeline.before_sha || Gitlab::Git::BLANK_SHA
end end
......
...@@ -11,14 +11,15 @@ module CacheMarkdownField ...@@ -11,14 +11,15 @@ module CacheMarkdownField
# Knows about the relationship between markdown and html field names, and # Knows about the relationship between markdown and html field names, and
# stores the rendering contexts for the latter # stores the rendering contexts for the latter
class FieldData class FieldData
extend Forwardable
def initialize def initialize
@data = {} @data = {}
end end
def_delegators :@data, :[], :[]= delegate :[], :[]=, to: :@data
def_delegator :@data, :keys, :markdown_fields
def markdown_fields
@data.keys
end
def html_field(markdown_field) def html_field(markdown_field)
"#{markdown_field}_html" "#{markdown_field}_html"
...@@ -45,7 +46,7 @@ module CacheMarkdownField ...@@ -45,7 +46,7 @@ module CacheMarkdownField
Project Project
Release Release
Snippet Snippet
] ].freeze
def self.caching_classes def self.caching_classes
CACHING_CLASSES.map(&:constantize) CACHING_CLASSES.map(&:constantize)
......
...@@ -13,11 +13,12 @@ module CaseSensitivity ...@@ -13,11 +13,12 @@ module CaseSensitivity
params.each do |key, value| params.each do |key, value|
column = ActiveRecord::Base.connection.quote_table_name(key) column = ActiveRecord::Base.connection.quote_table_name(key)
if cast_lower condition =
condition = "LOWER(#{column}) = LOWER(:value)" if cast_lower
else "LOWER(#{column}) = LOWER(:value)"
condition = "#{column} = :value" else
end "#{column} = :value"
end
criteria = criteria.where(condition, value: value) criteria = criteria.where(condition, value: value)
end end
......
module HasStatus module HasStatus
extend ActiveSupport::Concern extend ActiveSupport::Concern
DEFAULT_STATUS = 'created' DEFAULT_STATUS = 'created'.freeze
AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped] AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped].freeze
STARTED_STATUSES = %w[running success failed skipped] STARTED_STATUSES = %w[running success failed skipped].freeze
ACTIVE_STATUSES = %w[pending running] ACTIVE_STATUSES = %w[pending running].freeze
COMPLETED_STATUSES = %w[success failed canceled skipped] COMPLETED_STATUSES = %w[success failed canceled skipped].freeze
ORDERED_STATUSES = %w[failed pending running canceled success skipped] ORDERED_STATUSES = %w[failed pending running canceled success skipped].freeze
class_methods do class_methods do
def status_sql def status_sql
......
...@@ -46,6 +46,17 @@ module Issuable ...@@ -46,6 +46,17 @@ module Issuable
has_one :metrics 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 :author, presence: true
validates :title, presence: true, length: { maximum: 255 } validates :title, presence: true, length: { maximum: 255 }
...@@ -68,21 +79,10 @@ module Issuable ...@@ -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 :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 :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 :references_project, -> { references(:project) }
scope :non_archived, -> { join_project.where(projects: { archived: false }) } 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 :title, pipeline: :single_line
attr_mentionable :description attr_mentionable :description
...@@ -182,7 +182,7 @@ module Issuable ...@@ -182,7 +182,7 @@ module Issuable
def grouping_columns(sort) def grouping_columns(sort)
grouping_columns = [arel_table[:id]] 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 milestone_table = Milestone.arel_table
grouping_columns << milestone_table[:id] grouping_columns << milestone_table[:id]
grouping_columns << milestone_table[:due_date] grouping_columns << milestone_table[:due_date]
...@@ -235,7 +235,7 @@ module Issuable ...@@ -235,7 +235,7 @@ module Issuable
# DEPRECATED # DEPRECATED
repository: project.hook_attrs.slice(:name, :url, :description, :homepage) 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 hook_data
end end
......
...@@ -5,6 +5,6 @@ module ReactiveService ...@@ -5,6 +5,6 @@ module ReactiveService
include ReactiveCaching include ReactiveCaching
# Default cache key: class name + project_id # 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
end end
...@@ -46,11 +46,12 @@ module Sortable ...@@ -46,11 +46,12 @@ module Sortable
where("label_links.target_id = #{target_column}"). where("label_links.target_id = #{target_column}").
reorder(nil) reorder(nil)
if target_type_column query =
query = query.where("label_links.target_type = #{target_type_column}") if target_type_column
else query.where("label_links.target_type = #{target_type_column}")
query = query.where(label_links: { target_type: target_type }) else
end query.where(label_links: { target_type: target_type })
end
query = query.where.not(title: excluded_labels) if excluded_labels.present? 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 ...@@ -8,7 +8,7 @@ class DiffNote < Note
validates :position, presence: true validates :position, presence: true
validates :diff_line, presence: true validates :diff_line, presence: true
validates :line_code, presence: true, line_code: 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? validates :resolved_by, presence: true, if: :resolved?
validate :positions_complete validate :positions_complete
validate :verify_supported validate :verify_supported
......
...@@ -36,7 +36,7 @@ class Event < ActiveRecord::Base ...@@ -36,7 +36,7 @@ class Event < ActiveRecord::Base
scope :code_push, -> { where(action: PUSHED) } scope :code_push, -> { where(action: PUSHED) }
scope :in_projects, ->(projects) do scope :in_projects, ->(projects) do
where(project_id: projects).recent where(project_id: projects.pluck(:id)).recent
end end
scope :with_associations, -> { includes(:author, :project, project: :namespace).preload(:target) } scope :with_associations, -> { includes(:author, :project, project: :namespace).preload(:target) }
...@@ -47,7 +47,7 @@ class Event < ActiveRecord::Base ...@@ -47,7 +47,7 @@ class Event < ActiveRecord::Base
def contributions def contributions
where("action = ? OR (target_type IN (?) AND action IN (?)) OR (target_type = ? AND action = ?)", where("action = ? OR (target_type IN (?) AND action IN (?)) OR (target_type = ? AND action = ?)",
Event::PUSHED, Event::PUSHED,
["MergeRequest", "Issue"], [Event::CREATED, Event::CLOSED, Event::MERGED], %w(MergeRequest Issue), [Event::CREATED, Event::CLOSED, Event::MERGED],
"Note", Event::COMMENTED) "Note", Event::COMMENTED)
end end
......
...@@ -43,7 +43,7 @@ class ExternalIssue ...@@ -43,7 +43,7 @@ class ExternalIssue
end end
def reference_link_text(from_project = nil) def reference_link_text(from_project = nil)
return "##{id}" if /^\d+$/.match(id) return "##{id}" if id =~ /^\d+$/
id id
end end
......
...@@ -15,8 +15,6 @@ class Issue < ActiveRecord::Base ...@@ -15,8 +15,6 @@ class Issue < ActiveRecord::Base
DueThisWeek = DueDateStruct.new('Due This Week', 'week').freeze DueThisWeek = DueDateStruct.new('Due This Week', 'week').freeze
DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze
ActsAsTaggableOn.strict_case_match = true
belongs_to :project belongs_to :project
belongs_to :moved_to, class_name: 'Issue' belongs_to :moved_to, class_name: 'Issue'
......
...@@ -11,7 +11,7 @@ class Label < ActiveRecord::Base ...@@ -11,7 +11,7 @@ class Label < ActiveRecord::Base
cache_markdown_field :description, pipeline: :single_line cache_markdown_field :description, pipeline: :single_line
DEFAULT_COLOR = '#428BCA' DEFAULT_COLOR = '#428BCA'.freeze
default_value_for :color, DEFAULT_COLOR default_value_for :color, DEFAULT_COLOR
......
...@@ -10,6 +10,8 @@ class Member < ActiveRecord::Base ...@@ -10,6 +10,8 @@ class Member < ActiveRecord::Base
belongs_to :user belongs_to :user
belongs_to :source, polymorphic: true belongs_to :source, polymorphic: true
delegate :name, :username, :email, to: :user, prefix: true
validates :user, presence: true, unless: :invite? validates :user, presence: true, unless: :invite?
validates :source, presence: true validates :source, presence: true
validates :user_id, uniqueness: { scope: [:source_type, :source_id], validates :user_id, uniqueness: { scope: [:source_type, :source_id],
...@@ -73,8 +75,6 @@ class Member < ActiveRecord::Base ...@@ -73,8 +75,6 @@ class Member < ActiveRecord::Base
after_destroy :post_destroy_hook, unless: :pending? after_destroy :post_destroy_hook, unless: :pending?
after_commit :refresh_member_authorized_projects after_commit :refresh_member_authorized_projects
delegate :name, :username, :email, to: :user, prefix: true
default_value_for :notification_level, NotificationSetting.levels[:global] default_value_for :notification_level, NotificationSetting.levels[:global]
class << self class << self
......
class GroupMember < Member class GroupMember < Member
SOURCE_TYPE = 'Namespace' SOURCE_TYPE = 'Namespace'.freeze
belongs_to :group, foreign_key: 'source_id' belongs_to :group, foreign_key: 'source_id'
# Make sure group member points only to group as it source # Make sure group member points only to group as it source
default_value_for :source_type, SOURCE_TYPE 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) } default_scope { where(source_type: SOURCE_TYPE) }
def self.access_level_roles def self.access_level_roles
......
class ProjectMember < Member class ProjectMember < Member
SOURCE_TYPE = 'Project' SOURCE_TYPE = 'Project'.freeze
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
...@@ -7,7 +7,7 @@ class ProjectMember < Member ...@@ -7,7 +7,7 @@ class ProjectMember < Member
# Make sure project member points only to project as it source # Make sure project member points only to project as it source
default_value_for :source_type, SOURCE_TYPE 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 } validates :access_level, inclusion: { in: Gitlab::Access.values }
default_scope { where(source_type: SOURCE_TYPE) } default_scope { where(source_type: SOURCE_TYPE) }
......
...@@ -203,7 +203,11 @@ class MergeRequest < ActiveRecord::Base ...@@ -203,7 +203,11 @@ class MergeRequest < ActiveRecord::Base
end end
def diff_size 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 raw_diffs(opts).size
end end
...@@ -527,7 +531,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -527,7 +531,7 @@ class MergeRequest < ActiveRecord::Base
} }
if diff_head_commit if diff_head_commit
attrs.merge!(last_commit: diff_head_commit.hook_attrs) attrs[:last_commit] = diff_head_commit.hook_attrs
end end
attributes.merge!(attrs) attributes.merge!(attrs)
......
...@@ -7,7 +7,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -7,7 +7,7 @@ class MergeRequestDiff < ActiveRecord::Base
COMMITS_SAFE_SIZE = 100 COMMITS_SAFE_SIZE = 100
# Valid types of serialized diffs allowed by Gitlab::Git::Diff # 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 belongs_to :merge_request
......
...@@ -98,14 +98,8 @@ class Namespace < ActiveRecord::Base ...@@ -98,14 +98,8 @@ class Namespace < ActiveRecord::Base
# Work around that by setting their username to "blank", followed by a counter. # Work around that by setting their username to "blank", followed by a counter.
path = "blank" if path.blank? path = "blank" if path.blank?
counter = 0 uniquify = Uniquify.new
base = path uniquify.string(path) { |s| Namespace.find_by_path_or_name(s) }
while Namespace.find_by_path_or_name(path)
counter += 1
path = "#{base}#{counter}"
end
path
end end
end end
......
...@@ -188,11 +188,12 @@ module Network ...@@ -188,11 +188,12 @@ module Network
end end
# and mark it as reserved # and mark it as reserved
if parent_time.nil? min_time =
min_time = leaves.first.time if parent_time.nil?
else leaves.first.time
min_time = parent_time + 1 else
end parent_time + 1
end
max_time = leaves.last.time max_time = leaves.last.time
leaves.last.parents(@map).each do |parent| leaves.last.parents(@map).each do |parent|
......
...@@ -72,7 +72,7 @@ class Note < ActiveRecord::Base ...@@ -72,7 +72,7 @@ class Note < ActiveRecord::Base
scope :inc_author, ->{ includes(:author) } scope :inc_author, ->{ includes(:author) }
scope :inc_relations_for_view, ->{ includes(:project, :author, :updated_by, :resolved_by, :award_emoji) } 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 :non_diff_notes, ->{ where(type: ['Note', nil]) }
scope :with_associations, -> do scope :with_associations, -> do
......
...@@ -35,11 +35,11 @@ class NotificationSetting < ActiveRecord::Base ...@@ -35,11 +35,11 @@ class NotificationSetting < ActiveRecord::Base
:merge_merge_request, :merge_merge_request,
:failed_pipeline, :failed_pipeline,
:success_pipeline :success_pipeline
] ].freeze
EXCLUDED_WATCHER_EVENTS = [ EXCLUDED_WATCHER_EVENTS = [
:success_pipeline :success_pipeline
] ].freeze
store :events, accessors: EMAIL_EVENTS, coder: JSON 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