summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/issue_templates/Research Proposal.md17
-rw-r--r--CONTRIBUTING.md18
-rwxr-xr-x[-rw-r--r--]app/assets/images/favicon-blue.icobin5430 -> 5430 bytes
-rw-r--r--app/assets/javascripts/dispatcher.js.es61
-rw-r--r--app/assets/javascripts/environments/components/environment.js.es65
-rw-r--r--app/assets/javascripts/environments/environments_bundle.js.es61
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_bundle.js.es61
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_view.js.es65
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.es64
-rw-r--r--app/assets/javascripts/issue.js43
-rw-r--r--app/assets/javascripts/merge_request.js43
-rw-r--r--app/assets/javascripts/notes.js32
-rw-r--r--app/assets/javascripts/task_list.js40
-rw-r--r--app/assets/stylesheets/pages/tree.scss2
-rw-r--r--app/controllers/admin/runners_controller.rb6
-rw-r--r--app/controllers/projects/application_controller.rb1
-rw-r--r--app/controllers/projects/merge_requests_controller.rb3
-rw-r--r--app/controllers/projects/runners_controller.rb6
-rw-r--r--app/finders/issuable_finder.rb15
-rw-r--r--app/finders/issues_finder.rb4
-rw-r--r--app/finders/merge_requests_finder.rb10
-rw-r--r--app/models/ci/build.rb33
-rw-r--r--app/models/ci/pipeline.rb14
-rw-r--r--app/models/ci/runner.rb16
-rw-r--r--app/models/commit_status.rb6
-rw-r--r--app/models/project_services/chat_message/base_message.rb4
-rw-r--r--app/models/project_services/chat_message/build_message.rb28
-rw-r--r--app/models/project_services/chat_message/issue_message.rb4
-rw-r--r--app/models/project_services/chat_message/merge_message.rb4
-rw-r--r--app/models/project_services/chat_message/note_message.rb9
-rw-r--r--app/services/base_service.rb5
-rw-r--r--app/services/ci/retry_build_service.rb42
-rw-r--r--app/services/ci/retry_pipeline_service.rb22
-rw-r--r--app/services/ci/update_runner_service.rb15
-rw-r--r--app/services/merge_requests/add_todo_when_build_fails_service.rb6
-rw-r--r--app/views/admin/application_settings/_form.html.haml4
-rw-r--r--app/views/projects/notes/_note.html.haml2
-rw-r--r--app/views/projects/pipelines/_info.html.haml4
-rw-r--r--changelogs/unreleased/22018-api-milestone-merge-requests.yml4
-rw-r--r--changelogs/unreleased/26379-iid-param.yml4
-rw-r--r--changelogs/unreleased/26500-informative-slack-notifications.yml4
-rw-r--r--changelogs/unreleased/27920-both-wip-messages-showing.yml4
-rw-r--r--changelogs/unreleased/28236-browse-button-dropping.yml4
-rw-r--r--changelogs/unreleased/28303-change-development-tanuki-favicon-colors-to-match-logo.yml4
-rw-r--r--changelogs/unreleased/dynamic-project-title-fixture.yml4
-rw-r--r--changelogs/unreleased/fix-gb-pipeline-retry-builds-started.yml4
-rw-r--r--changelogs/unreleased/fix-gb-pipeline-retry-cancel-buttons-consistency.yml4
-rw-r--r--changelogs/unreleased/gfm-autocomplete-fixes.yml4
-rw-r--r--changelogs/unreleased/paginate-all-the-things.yml4
-rw-r--r--changelogs/unreleased/task_list_refactor.yml4
-rw-r--r--config/initializers/4_ci_app.rb8
-rw-r--r--doc/api/milestones.md13
-rw-r--r--doc/api/v3_to_v4.md1
-rw-r--r--doc/development/limit_ee_conflicts.md25
-rw-r--r--doc/workflow/gitlab_flow.md2
-rw-r--r--features/dashboard/issues.feature21
-rw-r--r--features/steps/dashboard/issues.rb91
-rw-r--r--features/steps/shared/builds.rb2
-rw-r--r--lib/api/api.rb9
-rw-r--r--lib/api/award_emoji.rb4
-rw-r--r--lib/api/boards.rb13
-rw-r--r--lib/api/branches.rb10
-rw-r--r--lib/api/deploy_keys.rb14
-rw-r--r--lib/api/files.rb1
-rw-r--r--lib/api/helpers/pagination.rb2
-rw-r--r--lib/api/labels.rb8
-rw-r--r--lib/api/merge_request_diffs.rb6
-rw-r--r--lib/api/merge_requests.rb3
-rw-r--r--lib/api/milestones.rb22
-rw-r--r--lib/api/pagination_params.rb4
-rw-r--r--lib/api/project_hooks.rb4
-rw-r--r--lib/api/repositories.rb14
-rw-r--r--lib/api/runners.rb3
-rw-r--r--lib/api/system_hooks.rb10
-rw-r--r--lib/api/tags.rb10
-rw-r--r--lib/api/templates.rb12
-rw-r--r--lib/api/users.rb16
-rw-r--r--lib/api/v3/boards.rb51
-rw-r--r--lib/api/v3/branches.rb24
-rw-r--r--lib/api/v3/issues.rb3
-rw-r--r--lib/api/v3/labels.rb19
-rw-r--r--lib/api/v3/repositories.rb55
-rw-r--r--lib/api/v3/system_hooks.rb19
-rw-r--r--lib/api/v3/tags.rb20
-rw-r--r--lib/api/v3/users.rb64
-rw-r--r--lib/gitlab/data_builder/build.rb10
-rw-r--r--package.json28
-rw-r--r--spec/controllers/admin/runners_controller_spec.rb85
-rw-r--r--spec/controllers/projects/runners_controller_spec.rb75
-rw-r--r--spec/factories/ci/builds.rb25
-rw-r--r--spec/factories/commits.rb10
-rw-r--r--spec/features/boards/sidebar_spec.rb139
-rw-r--r--spec/features/dashboard/issues_spec.rb48
-rw-r--r--spec/features/issues_spec.rb6
-rw-r--r--spec/finders/issues_finder_spec.rb6
-rw-r--r--spec/finders/merge_requests_finder_spec.rb8
-rw-r--r--spec/javascripts/fixtures/project_title.html.haml20
-rw-r--r--spec/javascripts/gfm_auto_complete_spec.js.es657
-rw-r--r--spec/javascripts/merge_request_spec.js6
-rw-r--r--spec/javascripts/notes_spec.js12
-rw-r--r--spec/javascripts/project_title_spec.js19
-rw-r--r--spec/lib/gitlab/data_builder/build_spec.rb26
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb6
-rw-r--r--spec/models/ci/build_spec.rb48
-rw-r--r--spec/models/ci/pipeline_spec.rb57
-rw-r--r--spec/models/ci/runner_spec.rb21
-rw-r--r--spec/models/commit_status_spec.rb22
-rw-r--r--spec/models/environment_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/build_message_spec.rb28
-rw-r--r--spec/requests/api/access_requests_spec.rb1
-rw-r--r--spec/requests/api/award_emoji_spec.rb1
-rw-r--r--spec/requests/api/boards_spec.rb2
-rw-r--r--spec/requests/api/branches_spec.rb4
-rw-r--r--spec/requests/api/broadcast_messages_spec.rb1
-rw-r--r--spec/requests/api/builds_spec.rb2
-rw-r--r--spec/requests/api/commit_statuses_spec.rb11
-rw-r--r--spec/requests/api/commits_spec.rb1
-rw-r--r--spec/requests/api/deploy_keys_spec.rb2
-rw-r--r--spec/requests/api/deployments_spec.rb1
-rw-r--r--spec/requests/api/environments_spec.rb1
-rw-r--r--spec/requests/api/groups_spec.rb17
-rw-r--r--spec/requests/api/issues_spec.rb102
-rw-r--r--spec/requests/api/labels_spec.rb1
-rw-r--r--spec/requests/api/members_spec.rb7
-rw-r--r--spec/requests/api/merge_request_diffs_spec.rb2
-rw-r--r--spec/requests/api/merge_requests_spec.rb28
-rw-r--r--spec/requests/api/milestones_spec.rb75
-rw-r--r--spec/requests/api/namespaces_spec.rb12
-rw-r--r--spec/requests/api/notes_spec.rb5
-rw-r--r--spec/requests/api/project_hooks_spec.rb1
-rw-r--r--spec/requests/api/project_snippets_spec.rb7
-rw-r--r--spec/requests/api/projects_spec.rb21
-rw-r--r--spec/requests/api/repositories_spec.rb7
-rw-r--r--spec/requests/api/runners_spec.rb22
-rw-r--r--spec/requests/api/snippets_spec.rb7
-rw-r--r--spec/requests/api/system_hooks_spec.rb1
-rw-r--r--spec/requests/api/tags_spec.rb10
-rw-r--r--spec/requests/api/templates_spec.rb4
-rw-r--r--spec/requests/api/todos_spec.rb6
-rw-r--r--spec/requests/api/triggers_spec.rb1
-rw-r--r--spec/requests/api/users_spec.rb20
-rw-r--r--spec/requests/api/v3/boards_spec.rb79
-rw-r--r--spec/requests/api/v3/branches_spec.rb23
-rw-r--r--spec/requests/api/v3/labels_spec.rb70
-rw-r--r--spec/requests/api/v3/repositories_spec.rb144
-rw-r--r--spec/requests/api/v3/system_hooks_spec.rb41
-rw-r--r--spec/requests/api/v3/tags_spec.rb67
-rw-r--r--spec/requests/api/v3/users_spec.rb120
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb14
-rw-r--r--spec/services/ci/retry_build_service_spec.rb117
-rw-r--r--spec/services/ci/retry_pipeline_service_spec.rb175
-rw-r--r--spec/services/ci/update_runner_service_spec.rb41
-rw-r--r--spec/services/create_deployment_service_spec.rb6
-rw-r--r--spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb25
-rw-r--r--spec/support/db_cleaner.rb4
-rw-r--r--spec/support/matchers/pagination_matcher.rb5
-rw-r--r--yarn.lock22
157 files changed, 2567 insertions, 639 deletions
diff --git a/.gitlab/issue_templates/Research Proposal.md b/.gitlab/issue_templates/Research Proposal.md
new file mode 100644
index 00000000000..5676656793d
--- /dev/null
+++ b/.gitlab/issue_templates/Research Proposal.md
@@ -0,0 +1,17 @@
+### Background:
+
+(Include problem, use cases, benefits, and/or goals)
+
+**What questions are you trying to answer?**
+
+**Are you looking to verify an existing hypothesis or uncover new issues you should be exploring?**
+
+**What is the backstory of this project and how does it impact the approach?**
+
+**What do you already know about the areas you are exploring?**
+
+**What does success look like at the end of the project?**
+
+### Links / references:
+
+/label ~"UX research"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 72cd57ad7ff..de32a953f63 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -93,18 +93,20 @@ Please see the [UX Guide for GitLab].
### Retrospective
-After each release (usually on the 22nd of each month), we have a retrospective
-call where we discuss what went well, what went wrong, and what we can improve
-for the next release. The [retrospective notes] are public and you are invited
-to comment them.
-If you're interested, you can even join the [retrospective call][retro-kickoff-call].
+After each release, we have a retrospective call where we discuss what went well,
+what went wrong, and what we can improve for the next release. The
+[retrospective notes] are public and you are invited to comment on them.
+If you're interested, you can even join the
+[retrospective call][retro-kickoff-call], on the first working day after the
+22nd at 6pm CET / 9am PST.
### Kickoff
-Before working on the next release (usually on the 8th of each month), we have a
+Before working on the next release, we have a
kickoff call to explain what we expect to ship in the next release. The
-[kickoff notes] are public and you are invited to comment them.
-If you're interested, you can even join the [kickoff call][retro-kickoff-call].
+[kickoff notes] are public and you are invited to comment on them.
+If you're interested, you can even join the [kickoff call][retro-kickoff-call],
+on the first working day after the 7th at 6pm CET / 9am PST..
[retrospective notes]: https://docs.google.com/document/d/1nEkM_7Dj4bT21GJy0Ut3By76FZqCfLBmFQNVThmW2TY/edit?usp=sharing
[kickoff notes]: https://docs.google.com/document/d/1ElPkZ90A8ey_iOkTvUs_ByMlwKK6NAB2VOK5835wYK0/edit?usp=sharing
diff --git a/app/assets/images/favicon-blue.ico b/app/assets/images/favicon-blue.ico
index 71acdf670ab..156fcf07588 100644..100755
--- a/app/assets/images/favicon-blue.ico
+++ b/app/assets/images/favicon-blue.ico
Binary files differ
diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6
index 7eec2d39a9c..f06a8848021 100644
--- a/app/assets/javascripts/dispatcher.js.es6
+++ b/app/assets/javascripts/dispatcher.js.es6
@@ -118,6 +118,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
new gl.IssuableTemplateSelectors();
break;
case 'projects:merge_requests:new':
+ case 'projects:merge_requests:new_diffs':
case 'projects:merge_requests:edit':
new gl.Diff();
shortcut_handler = new ShortcutsNavigation();
diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6
index 0cbf952ea5c..4b700a39d44 100644
--- a/app/assets/javascripts/environments/components/environment.js.es6
+++ b/app/assets/javascripts/environments/components/environment.js.es6
@@ -1,13 +1,14 @@
/* eslint-disable no-param-reassign, no-new */
/* global Flash */
-const Vue = require('vue');
-Vue.use(require('vue-resource'));
+const Vue = window.Vue = require('vue');
+window.Vue.use(require('vue-resource'));
const EnvironmentsService = require('../services/environments_service');
const EnvironmentTable = require('./environments_table');
const EnvironmentsStore = require('../stores/environments_store');
require('../../vue_shared/components/table_pagination');
require('../../lib/utils/common_utils');
+require('../../vue_shared/vue_resource_interceptor');
module.exports = Vue.component('environment-component', {
diff --git a/app/assets/javascripts/environments/environments_bundle.js.es6 b/app/assets/javascripts/environments/environments_bundle.js.es6
index 867eba1d384..7bbba91bc10 100644
--- a/app/assets/javascripts/environments/environments_bundle.js.es6
+++ b/app/assets/javascripts/environments/environments_bundle.js.es6
@@ -1,5 +1,4 @@
const EnvironmentsComponent = require('./components/environment');
-require('../vue_shared/vue_resource_interceptor');
$(() => {
window.gl = window.gl || {};
diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6 b/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6
index 29f704c1a37..d2ca465351a 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6
+++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6
@@ -1,5 +1,4 @@
const EnvironmentsFolderComponent = require('./environments_folder_view');
-require('../../vue_shared/vue_resource_interceptor');
$(() => {
window.gl = window.gl || {};
diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.js.es6 b/app/assets/javascripts/environments/folder/environments_folder_view.js.es6
index 0b1204559da..53d52965758 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_view.js.es6
+++ b/app/assets/javascripts/environments/folder/environments_folder_view.js.es6
@@ -1,13 +1,14 @@
/* eslint-disable no-param-reassign, no-new */
/* global Flash */
-const Vue = require('vue');
-Vue.use(require('vue-resource'));
+const Vue = window.Vue = require('vue');
+window.Vue.use(require('vue-resource'));
const EnvironmentsService = require('../services/environments_service');
const EnvironmentTable = require('../components/environments_table');
const EnvironmentsStore = require('../stores/environments_store');
require('../../vue_shared/components/table_pagination');
require('../../lib/utils/common_utils');
+require('../../vue_shared/vue_resource_interceptor');
module.exports = Vue.component('environment-folder-view', {
diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6
index 0f6994dd2d1..c295860c334 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.es6
+++ b/app/assets/javascripts/gfm_auto_complete.js.es6
@@ -83,12 +83,12 @@
_a = decodeURI("%C3%80");
_y = decodeURI("%C3%BF");
- regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?![" + atSymbolsWithBar + "])(([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]|[^\\x00-\\x7a])*)$", 'gi');
+ regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?!" + atSymbolsWithBar + ")((?:[A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]|[^\\x00-\\x7a])*)$", 'gi');
match = regexp.exec(subtext);
if (match) {
- return (match[1] || match[1] === "") ? match[1] : match[2];
+ return match[1];
} else {
return null;
}
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 1776b3d61f6..00633e812e1 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -3,7 +3,7 @@
require('./flash');
require('vendor/jquery.waitforimages');
-require('vendor/task_list');
+require('./task_list');
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
@@ -11,10 +11,16 @@ require('vendor/task_list');
this.Issue = (function() {
function Issue() {
this.submitNoteForm = bind(this.submitNoteForm, this);
- // Prevent duplicate event bindings
- this.disableTaskList();
if ($('a.btn-close').length) {
- this.initTaskList();
+ this.taskList = new gl.TaskList({
+ dataType: 'issue',
+ fieldName: 'description',
+ selector: '.detail-page-description',
+ onSuccess: (result) => {
+ document.querySelector('#task_status').innerText = result.task_status;
+ document.querySelector('#task_status_short').innerText = result.task_status_short;
+ }
+ });
this.initIssueBtnEventListeners();
}
this.initMergeRequests();
@@ -22,11 +28,6 @@ require('vendor/task_list');
this.initCanCreateBranch();
}
- Issue.prototype.initTaskList = function() {
- $('.detail-page-description .js-task-list-container').taskList('enable');
- return $(document).on('tasklist:changed', '.detail-page-description .js-task-list-container', this.updateTaskList);
- };
-
Issue.prototype.initIssueBtnEventListeners = function() {
var _this, issueFailMessage;
_this = this;
@@ -85,30 +86,6 @@ require('vendor/task_list');
}
};
- Issue.prototype.disableTaskList = function() {
- $('.detail-page-description .js-task-list-container').taskList('disable');
- return $(document).off('tasklist:changed', '.detail-page-description .js-task-list-container');
- };
-
- Issue.prototype.updateTaskList = function() {
- var patchData;
- patchData = {};
- patchData['issue'] = {
- 'description': $('.js-task-list-field', this).val()
- };
- return $.ajax({
- type: 'PATCH',
- url: $('form.js-issuable-update').attr('action'),
- data: patchData,
- success: function(issue) {
- document.querySelector('#task_status').innerText = issue.task_status;
- document.querySelector('#task_status_short').innerText = issue.task_status_short;
- }
- });
- // TODO (rspeicher): Make the issue description inline-editable like a note so
- // that we can re-use its form here
- };
-
Issue.prototype.initMergeRequests = function() {
var $container;
$container = $('#merge-requests');
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index e65378cd610..be12d925040 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -2,7 +2,7 @@
/* global MergeRequestTabs */
require('vendor/jquery.waitforimages');
-require('vendor/task_list');
+require('./task_list');
require('./merge_request_tabs');
(function() {
@@ -24,12 +24,18 @@ require('./merge_request_tabs');
};
})(this));
this.initTabs();
- // Prevent duplicate event bindings
- this.disableTaskList();
this.initMRBtnListeners();
this.initCommitMessageListeners();
if ($("a.btn-close").length) {
- this.initTaskList();
+ this.taskList = new gl.TaskList({
+ dataType: 'merge_request',
+ fieldName: 'description',
+ selector: '.detail-page-description',
+ onSuccess: (result) => {
+ document.querySelector('#task_status').innerText = result.task_status;
+ document.querySelector('#task_status_short').innerText = result.task_status_short;
+ }
+ });
}
}
@@ -50,11 +56,6 @@ require('./merge_request_tabs');
return this.$('.all-commits').removeClass('hide');
};
- MergeRequest.prototype.initTaskList = function() {
- $('.detail-page-description .js-task-list-container').taskList('enable');
- return $(document).on('tasklist:changed', '.detail-page-description .js-task-list-container', this.updateTaskList);
- };
-
MergeRequest.prototype.initMRBtnListeners = function() {
var _this;
_this = this;
@@ -85,30 +86,6 @@ require('./merge_request_tabs');
}
};
- MergeRequest.prototype.disableTaskList = function() {
- $('.detail-page-description .js-task-list-container').taskList('disable');
- return $(document).off('tasklist:changed', '.detail-page-description .js-task-list-container');
- };
-
- MergeRequest.prototype.updateTaskList = function() {
- var patchData;
- patchData = {};
- patchData['merge_request'] = {
- 'description': $('.js-task-list-field', this).val()
- };
- return $.ajax({
- type: 'PATCH',
- url: $('form.js-issuable-update').attr('action'),
- data: patchData,
- success: function(mergeRequest) {
- document.querySelector('#task_status').innerText = mergeRequest.task_status;
- document.querySelector('#task_status_short').innerText = mergeRequest.task_status_short;
- }
- });
- // TODO (rspeicher): Make the merge request description inline-editable like a
- // note so that we can re-use its form here
- };
-
MergeRequest.prototype.initCommitMessageListeners = function() {
$(document).on('click', 'a.js-with-description-link', function(e) {
var textarea = $('textarea.js-commit-message');
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 0464b895d6d..553ced4fa55 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -11,7 +11,7 @@ require('./dropzone_input');
require('./gfm_auto_complete');
require('vendor/jquery.caret'); // required by jquery.atwho
require('vendor/jquery.atwho');
-require('vendor/task_list');
+require('./task_list');
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
@@ -51,7 +51,11 @@ require('vendor/task_list');
this.addBinding();
this.setPollingInterval();
this.setupMainTargetNoteForm();
- this.initTaskList();
+ this.taskList = new gl.TaskList({
+ dataType: 'note',
+ fieldName: 'note',
+ selector: '.notes'
+ });
this.collapseLongCommitList();
// We are in the Merge Requests page so we need another edit form for Changes tab
@@ -125,8 +129,6 @@ require('vendor/task_list');
$(document).off("keydown", ".js-note-text");
$(document).off('click', '.js-comment-resolve-button');
$(document).off("click", '.system-note-commit-list-toggler');
- $('.note .js-task-list-container').taskList('disable');
- return $(document).off('tasklist:changed', '.note .js-task-list-container');
};
Notes.prototype.keydownNoteText = function(e) {
@@ -286,7 +288,7 @@ require('vendor/task_list');
// Update datetime format on the recent note
gl.utils.localTimeAgo($notesList.find("#note_" + note.id + " .js-timeago"), false);
this.collapseLongCommitList();
- this.initTaskList();
+ this.taskList.init();
this.refresh();
return this.updateNotesCount(1);
}
@@ -863,15 +865,6 @@ require('vendor/task_list');
}
};
- Notes.prototype.initTaskList = function() {
- this.enableTaskList();
- return $(document).on('tasklist:changed', '.note .js-task-list-container', this.updateTaskList.bind(this));
- };
-
- Notes.prototype.enableTaskList = function() {
- return $('.note .js-task-list-container').taskList('enable');
- };
-
Notes.prototype.putEditFormInPlace = function($el) {
var $editForm = $(this.getEditFormSelector($el));
var $note = $el.closest('.note');
@@ -896,17 +889,6 @@ require('vendor/task_list');
$editForm.find('.referenced-users').hide();
};
- Notes.prototype.updateTaskList = function(e) {
- var $target = $(e.target);
- var $list = $target.closest('.js-task-list-container');
- var $editForm = $(this.getEditFormSelector($target));
- var $note = $list.closest('.note');
-
- this.putEditFormInPlace($list);
- $editForm.find('#note_note').val($note.find('.original-task-list').val());
- $('form', $list).submit();
- };
-
Notes.prototype.updateNotesCount = function(updateCount) {
return this.notesCountBadge.text(parseInt(this.notesCountBadge.text(), 10) + updateCount);
};
diff --git a/app/assets/javascripts/task_list.js b/app/assets/javascripts/task_list.js
new file mode 100644
index 00000000000..dfe24d1fb33
--- /dev/null
+++ b/app/assets/javascripts/task_list.js
@@ -0,0 +1,40 @@
+require('vendor/task_list');
+
+class TaskList {
+ constructor(options = {}) {
+ this.selector = options.selector;
+ this.dataType = options.dataType;
+ this.fieldName = options.fieldName;
+ this.onSuccess = options.onSuccess || (() => {});
+ this.init();
+ }
+
+ init() {
+ // Prevent duplicate event bindings
+ this.disable();
+ $(`${this.selector} .js-task-list-container`).taskList('enable');
+ $(document).on('tasklist:changed', `${this.selector} .js-task-list-container`, this.update.bind(this));
+ }
+
+ disable() {
+ $(`${this.selector} .js-task-list-container`).taskList('disable');
+ $(document).off('tasklist:changed', `${this.selector} .js-task-list-container`);
+ }
+
+ update(e) {
+ const $target = $(e.target);
+ const patchData = {};
+ patchData[this.dataType] = {
+ [this.fieldName]: $target.val(),
+ };
+ return $.ajax({
+ type: 'PATCH',
+ url: $target.data('update-url') || $('form.js-issuable-update').attr('action'),
+ data: patchData,
+ success: this.onSuccess,
+ });
+ }
+}
+
+window.gl = window.gl || {};
+window.gl.TaskList = TaskList;
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 948921efc0b..e4487dbcb87 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -149,7 +149,7 @@
}
.commit-actions {
- width: 200px;
+ width: 260px;
}
}
diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb
index 7345c91f67d..348641e5ecb 100644
--- a/app/controllers/admin/runners_controller.rb
+++ b/app/controllers/admin/runners_controller.rb
@@ -13,7 +13,7 @@ class Admin::RunnersController < Admin::ApplicationController
end
def update
- if @runner.update_attributes(runner_params)
+ if Ci::UpdateRunnerService.new(@runner).update(runner_params)
respond_to do |format|
format.js
format.html { redirect_to admin_runner_path(@runner) }
@@ -31,7 +31,7 @@ class Admin::RunnersController < Admin::ApplicationController
end
def resume
- if @runner.update_attributes(active: true)
+ if Ci::UpdateRunnerService.new(@runner).update(active: true)
redirect_to admin_runners_path, notice: 'Runner was successfully updated.'
else
redirect_to admin_runners_path, alert: 'Runner was not updated.'
@@ -39,7 +39,7 @@ class Admin::RunnersController < Admin::ApplicationController
end
def pause
- if @runner.update_attributes(active: false)
+ if Ci::UpdateRunnerService.new(@runner).update(active: false)
redirect_to admin_runners_path, notice: 'Runner was successfully updated.'
else
redirect_to admin_runners_path, alert: 'Runner was not updated.'
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index db33b60b229..e2f81b09adc 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -83,7 +83,6 @@ class Projects::ApplicationController < ApplicationController
end
def apply_diff_view_cookie!
- @show_changes_tab = params[:view].present?
cookies.permanent[:diff_view] = params.delete(:view) if params[:view].present?
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 63b5bcbb586..2bf3542d089 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -245,6 +245,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
respond_to do |format|
format.html do
define_new_vars
+ @show_changes_tab = true
render "new"
end
format.json do
@@ -616,6 +617,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@labels = LabelsFinder.new(current_user, project_id: @project.id).execute
+ @show_changes_tab = params[:show_changes].present?
+
define_pipelines_vars
end
diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
index 74c54037ba9..8b50ea207a5 100644
--- a/app/controllers/projects/runners_controller.rb
+++ b/app/controllers/projects/runners_controller.rb
@@ -12,7 +12,7 @@ class Projects::RunnersController < Projects::ApplicationController
end
def update
- if @runner.update_attributes(runner_params)
+ if Ci::UpdateRunnerService.new(@runner).update(runner_params)
redirect_to runner_path(@runner), notice: 'Runner was successfully updated.'
else
render 'edit'
@@ -28,7 +28,7 @@ class Projects::RunnersController < Projects::ApplicationController
end
def resume
- if @runner.update_attributes(active: true)
+ if Ci::UpdateRunnerService.new(@runner).update(active: true)
redirect_to runner_path(@runner), notice: 'Runner was successfully updated.'
else
redirect_to runner_path(@runner), alert: 'Runner was not updated.'
@@ -36,7 +36,7 @@ class Projects::RunnersController < Projects::ApplicationController
end
def pause
- if @runner.update_attributes(active: false)
+ if Ci::UpdateRunnerService.new(@runner).update(active: false)
redirect_to runner_path(@runner), notice: 'Runner was successfully updated.'
else
redirect_to runner_path(@runner), alert: 'Runner was not updated.'
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 1576fc80a6b..206c92fe82a 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -16,6 +16,7 @@
# label_name: string
# sort: string
# non_archived: boolean
+# iids: integer[]
#
class IssuableFinder
NONE = '0'
@@ -40,6 +41,7 @@ class IssuableFinder
items = by_label(items)
items = by_due_date(items)
items = by_non_archived(items)
+ items = by_iids(items)
sort(items)
end
@@ -266,16 +268,11 @@ class IssuableFinder
end
def by_search(items)
- if search
- items =
- if search =~ iid_pattern
- items.where(iid: $~[:iid])
- else
- items.full_search(search)
- end
- end
+ search ? items.full_search(search) : items
+ end
- items
+ def by_iids(items)
+ params[:iids].present? ? items.where(iid: params[:iids]) : items
end
def sort(items)
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index 707eddd4d29..f542f72a386 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -26,10 +26,6 @@ class IssuesFinder < IssuableFinder
IssuesFinder.not_restricted_by_confidentiality(current_user)
end
- def iid_pattern
- @iid_pattern ||= %r{\A#{Regexp.escape(Issue.reference_prefix)}(?<iid>\d+)\z}
- end
-
def self.not_restricted_by_confidentiality(user)
return Issue.where('issues.confidential IS NULL OR issues.confidential IS FALSE') if user.blank?
diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb
index 8b82255445e..b76ca389f38 100644
--- a/app/finders/merge_requests_finder.rb
+++ b/app/finders/merge_requests_finder.rb
@@ -20,14 +20,4 @@ class MergeRequestsFinder < IssuableFinder
def klass
MergeRequest
end
-
- private
-
- def iid_pattern
- @iid_pattern ||= %r{\A[
- #{Regexp.escape(MergeRequest.reference_prefix)}
- #{Regexp.escape(Issue.reference_prefix)}
- ](?<iid>\d+)\z
- }x
- end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 8c1b076c2d7..e018f8e7c4e 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -62,33 +62,10 @@ module Ci
new_build.save
end
- def retry(build, user = nil)
- new_build = Ci::Build.create(
- ref: build.ref,
- tag: build.tag,
- options: build.options,
- commands: build.commands,
- tag_list: build.tag_list,
- project: build.project,
- pipeline: build.pipeline,
- name: build.name,
- allow_failure: build.allow_failure,
- stage: build.stage,
- stage_idx: build.stage_idx,
- trigger_request: build.trigger_request,
- yaml_variables: build.yaml_variables,
- when: build.when,
- user: user,
- environment: build.environment,
- status_event: 'enqueue'
- )
-
- MergeRequests::AddTodoWhenBuildFailsService
- .new(build.project, nil)
- .close(new_build)
-
- build.pipeline.mark_as_processable_after_stage(build.stage_idx)
- new_build
+ def retry(build, current_user)
+ Ci::RetryBuildService
+ .new(build.project, current_user)
+ .execute(build)
end
end
@@ -136,7 +113,7 @@ module Ci
project.builds_enabled? && commands.present? && manual? && skipped?
end
- def play(current_user = nil)
+ def play(current_user)
# Try to queue a current build
if self.enqueue
self.update(user: current_user)
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index bbc358adb83..dc4590a9923 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -214,21 +214,17 @@ module Ci
def cancel_running
Gitlab::OptimisticLocking.retry_lock(
statuses.cancelable) do |cancelable|
- cancelable.each(&:cancel)
+ cancelable.find_each(&:cancel)
end
end
- def retry_failed(user)
- Gitlab::OptimisticLocking.retry_lock(
- builds.latest.failed_or_canceled) do |failed_or_canceled|
- failed_or_canceled.select(&:retryable?).each do |build|
- Ci::Build.retry(build, user)
- end
- end
+ def retry_failed(current_user)
+ Ci::RetryPipelineService.new(project, current_user)
+ .execute(self)
end
def mark_as_processable_after_stage(stage_idx)
- builds.skipped.where('stage_idx > ?', stage_idx).find_each(&:process)
+ builds.skipped.after_stage(stage_idx).find_each(&:process)
end
def latest?
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index ed1843ba005..07a086b0aca 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -22,8 +22,6 @@ module Ci
scope :online, ->() { where('contacted_at > ?', LAST_CONTACT_TIME) }
scope :ordered, ->() { order(id: :desc) }
- after_save :tick_runner_queue, if: :form_editable_changed?
-
scope :owned_or_shared, ->(project_id) do
joins('LEFT JOIN ci_runner_projects ON ci_runner_projects.runner_id = ci_runners.id')
.where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id)
@@ -40,6 +38,8 @@ module Ci
acts_as_taggable
+ after_destroy :cleanup_runner_queue
+
# Searches for runners matching the given query.
#
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
@@ -147,14 +147,14 @@ module Ci
private
- def runner_queue_key
- "runner:build_queue:#{self.token}"
+ def cleanup_runner_queue
+ Gitlab::Redis.with do |redis|
+ redis.del(runner_queue_key)
+ end
end
- def form_editable_changed?
- FORM_EDITABLE.any? do |editable|
- public_send("#{editable}_changed?")
- end
+ def runner_queue_key
+ "runner:build_queue:#{self.token}"
end
def tag_constraints
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 9547c57b2ae..99a6326309d 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -23,9 +23,6 @@ class CommitStatus < ActiveRecord::Base
where(id: max_id.group(:name, :commit_id))
end
- scope :retried, -> { where.not(id: latest) }
- scope :ordered, -> { order(:name) }
-
scope :failed_but_allowed, -> do
where(allow_failure: true, status: [:failed, :canceled])
end
@@ -36,8 +33,11 @@ class CommitStatus < ActiveRecord::Base
false, all_state_names - [:failed, :canceled])
end
+ scope :retried, -> { where.not(id: latest) }
+ scope :ordered, -> { order(:name) }
scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) }
scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) }
+ scope :after_stage, -> (index) { where('stage_idx > ?', index) }
state_machine :status do
event :enqueue do
diff --git a/app/models/project_services/chat_message/base_message.rb b/app/models/project_services/chat_message/base_message.rb
index a03605d01fb..86d271a3f69 100644
--- a/app/models/project_services/chat_message/base_message.rb
+++ b/app/models/project_services/chat_message/base_message.rb
@@ -30,5 +30,9 @@ module ChatMessage
def attachment_color
'#345'
end
+
+ def link(text, url)
+ "[#{text}](#{url})"
+ end
end
end
diff --git a/app/models/project_services/chat_message/build_message.rb b/app/models/project_services/chat_message/build_message.rb
index 53e35cb21bf..c776e0a20c4 100644
--- a/app/models/project_services/chat_message/build_message.rb
+++ b/app/models/project_services/chat_message/build_message.rb
@@ -7,7 +7,11 @@ module ChatMessage
attr_reader :project_name
attr_reader :project_url
attr_reader :user_name
+ attr_reader :user_url
attr_reader :duration
+ attr_reader :stage
+ attr_reader :build_id
+ attr_reader :build_name
def initialize(params)
@sha = params[:sha]
@@ -17,7 +21,11 @@ module ChatMessage
@project_url = params[:project_url]
@status = params[:commit][:status]
@user_name = params[:commit][:author_name]
+ @user_url = params[:commit][:author_url]
@duration = params[:commit][:duration]
+ @stage = params[:build_stage]
+ @build_name = params[:build_name]
+ @build_id = params[:build_id]
end
def pretext
@@ -35,7 +43,19 @@ module ChatMessage
private
def message
- "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} #{'second'.pluralize(duration)}"
+ "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_link} #{humanized_status} on build #{build_link} of stage #{stage} in #{duration} #{'second'.pluralize(duration)}"
+ end
+
+ def build_url
+ "#{project_url}/builds/#{build_id}"
+ end
+
+ def build_link
+ link(build_name, build_url)
+ end
+
+ def user_link
+ link(user_name, user_url)
end
def format(string)
@@ -64,11 +84,11 @@ module ChatMessage
end
def branch_link
- "[#{ref}](#{branch_url})"
+ link(ref, branch_url)
end
def project_link
- "[#{project_name}](#{project_url})"
+ link(project_name, project_url)
end
def commit_url
@@ -76,7 +96,7 @@ module ChatMessage
end
def commit_link
- "[#{Commit.truncate_sha(sha)}](#{commit_url})"
+ link(Commit.truncate_sha(sha), commit_url)
end
end
end
diff --git a/app/models/project_services/chat_message/issue_message.rb b/app/models/project_services/chat_message/issue_message.rb
index 14fd64e5332..b96aca47e65 100644
--- a/app/models/project_services/chat_message/issue_message.rb
+++ b/app/models/project_services/chat_message/issue_message.rb
@@ -55,11 +55,11 @@ module ChatMessage
end
def project_link
- "[#{project_name}](#{project_url})"
+ link(project_name, project_url)
end
def issue_link
- "[#{issue_title}](#{issue_url})"
+ link(issue_title, issue_url)
end
def issue_title
diff --git a/app/models/project_services/chat_message/merge_message.rb b/app/models/project_services/chat_message/merge_message.rb
index ab5e8b24167..5e5efca7bec 100644
--- a/app/models/project_services/chat_message/merge_message.rb
+++ b/app/models/project_services/chat_message/merge_message.rb
@@ -42,7 +42,7 @@ module ChatMessage
end
def project_link
- "[#{project_name}](#{project_url})"
+ link(project_name, project_url)
end
def merge_request_message
@@ -50,7 +50,7 @@ module ChatMessage
end
def merge_request_link
- "[merge request !#{merge_request_id}](#{merge_request_url})"
+ link("merge request !#{merge_request_id}", merge_request_url)
end
def merge_request_url
diff --git a/app/models/project_services/chat_message/note_message.rb b/app/models/project_services/chat_message/note_message.rb
index ca1d7207034..552113bac29 100644
--- a/app/models/project_services/chat_message/note_message.rb
+++ b/app/models/project_services/chat_message/note_message.rb
@@ -3,10 +3,9 @@ module ChatMessage
attr_reader :message
attr_reader :user_name
attr_reader :project_name
- attr_reader :project_link
+ attr_reader :project_url
attr_reader :note
attr_reader :note_url
- attr_reader :title
def initialize(params)
params = HashWithIndifferentAccess.new(params)
@@ -69,15 +68,15 @@ module ChatMessage
end
def description_message
- [{ text: format(@note), color: attachment_color }]
+ [{ text: format(note), color: attachment_color }]
end
def project_link
- "[#{@project_name}](#{@project_url})"
+ link(project_name, project_url)
end
def commented_on_message(target, title)
- @message = "#{@user_name} [commented on #{target}](#{@note_url}) in #{project_link}: *#{title}*"
+ @message = "#{user_name} #{link('commented on ' + target, note_url)} in #{project_link}: *#{title}*"
end
end
end
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index 1a2bad77a02..fa45506317e 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -1,4 +1,5 @@
class BaseService
+ include Gitlab::Allowable
include Gitlab::CurrentSettings
attr_accessor :project, :current_user, :params
@@ -7,10 +8,6 @@ class BaseService
@project, @current_user, @params = project, user, params.dup
end
- def can?(object, action, subject)
- Ability.allowed?(object, action, subject)
- end
-
def notification_service
NotificationService.new
end
diff --git a/app/services/ci/retry_build_service.rb b/app/services/ci/retry_build_service.rb
new file mode 100644
index 00000000000..4b47ee489cf
--- /dev/null
+++ b/app/services/ci/retry_build_service.rb
@@ -0,0 +1,42 @@
+module Ci
+ class RetryBuildService < ::BaseService
+ CLONE_ATTRIBUTES = %i[pipeline ref tag options commands tag_list name
+ allow_failure stage stage_idx trigger_request
+ yaml_variables when environment coverage_regex]
+ .freeze
+
+ REJECT_ATTRIBUTES = %i[id status user token coverage trace runner
+ artifacts_file artifacts_metadata artifacts_size
+ created_at updated_at started_at finished_at
+ queued_at erased_by erased_at].freeze
+
+ IGNORE_ATTRIBUTES = %i[trace type lock_version project target_url
+ deploy job_id description].freeze
+
+ def execute(build)
+ reprocess(build).tap do |new_build|
+ build.pipeline.mark_as_processable_after_stage(build.stage_idx)
+
+ new_build.enqueue!
+
+ MergeRequests::AddTodoWhenBuildFailsService
+ .new(project, current_user)
+ .close(new_build)
+ end
+ end
+
+ def reprocess(build)
+ unless can?(current_user, :update_build, build)
+ raise Gitlab::Access::AccessDeniedError
+ end
+
+ attributes = CLONE_ATTRIBUTES.map do |attribute|
+ [attribute, build.send(attribute)]
+ end
+
+ attributes.push([:user, current_user])
+
+ project.builds.create(Hash[attributes])
+ end
+ end
+end
diff --git a/app/services/ci/retry_pipeline_service.rb b/app/services/ci/retry_pipeline_service.rb
new file mode 100644
index 00000000000..2c5e130e5aa
--- /dev/null
+++ b/app/services/ci/retry_pipeline_service.rb
@@ -0,0 +1,22 @@
+module Ci
+ class RetryPipelineService < ::BaseService
+ def execute(pipeline)
+ unless can?(current_user, :update_pipeline, pipeline)
+ raise Gitlab::Access::AccessDeniedError
+ end
+
+ pipeline.builds.failed_or_canceled.find_each do |build|
+ next unless build.retryable?
+
+ Ci::RetryBuildService.new(project, current_user)
+ .reprocess(build)
+ end
+
+ MergeRequests::AddTodoWhenBuildFailsService
+ .new(project, current_user)
+ .close_all(pipeline)
+
+ pipeline.process!
+ end
+ end
+end
diff --git a/app/services/ci/update_runner_service.rb b/app/services/ci/update_runner_service.rb
new file mode 100644
index 00000000000..450ee7da1c9
--- /dev/null
+++ b/app/services/ci/update_runner_service.rb
@@ -0,0 +1,15 @@
+module Ci
+ class UpdateRunnerService
+ attr_reader :runner
+
+ def initialize(runner)
+ @runner = runner
+ end
+
+ def update(params)
+ runner.update(params).tap do |updated|
+ runner.tick_runner_queue if updated
+ end
+ end
+ end
+end
diff --git a/app/services/merge_requests/add_todo_when_build_fails_service.rb b/app/services/merge_requests/add_todo_when_build_fails_service.rb
index 12a8415d9a5..727768b1a39 100644
--- a/app/services/merge_requests/add_todo_when_build_fails_service.rb
+++ b/app/services/merge_requests/add_todo_when_build_fails_service.rb
@@ -18,5 +18,11 @@ module MergeRequests
todo_service.merge_request_build_retried(merge_request)
end
end
+
+ def close_all(pipeline)
+ pipeline_merge_requests(pipeline) do |merge_request|
+ todo_service.merge_request_build_retried(merge_request)
+ end
+ end
end
end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 816035ec442..749c74b8110 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -192,7 +192,7 @@
= f.label :max_pages_size, 'Maximum size of pages (MB)', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :max_pages_size, class: 'form-control'
- .help-block Zero for unlimited
+ .help-block 0 for unlimited
%fieldset
%legend Continuous Integration
@@ -525,7 +525,7 @@
= f.number_field :terminal_max_session_time, class: 'form-control'
.help-block
Maximum time for web terminal websocket connection (in seconds).
- Set to 0 for unlimited time.
+ 0 for unlimited.
.form-actions
= f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 1b08165c14c..a73e8f345e0 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -71,7 +71,7 @@
- if note_editable
.original-note-content.hidden{ data: { post_url: namespace_project_note_path(@project.namespace, @project, note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } }
#{note.note}
- %textarea.hidden.js-task-list-field.original-task-list= note.note
+ %textarea.hidden.js-task-list-field.original-task-list{ data: {update_url: namespace_project_note_path(@project.namespace, @project, note) } }= note.note
.note-awards
= render 'award_emoji/awards_block', awardable: note, inline: false
- if note.system
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index a6cd2d83bd5..e0c972aa2fb 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -7,9 +7,9 @@
= commit_author_link(@commit)
.header-action-buttons
- if can?(current_user, :update_pipeline, @pipeline.project)
- - if @pipeline.builds.latest.failed.any?(&:retryable?)
+ - if @pipeline.retryable?
= link_to "Retry failed", retry_namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'btn btn-inverted-secondary', method: :post
- - if @pipeline.builds.running_or_pending.any?
+ - if @pipeline.cancelable?
= link_to "Cancel running", cancel_namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
- if @commit
diff --git a/changelogs/unreleased/22018-api-milestone-merge-requests.yml b/changelogs/unreleased/22018-api-milestone-merge-requests.yml
new file mode 100644
index 00000000000..ccad2ec838c
--- /dev/null
+++ b/changelogs/unreleased/22018-api-milestone-merge-requests.yml
@@ -0,0 +1,4 @@
+---
+title: Adds API endpoint to fetch all merge request for a single milestone
+merge_request:
+author: Joren De Groof
diff --git a/changelogs/unreleased/26379-iid-param.yml b/changelogs/unreleased/26379-iid-param.yml
new file mode 100644
index 00000000000..ac743e68d6f
--- /dev/null
+++ b/changelogs/unreleased/26379-iid-param.yml
@@ -0,0 +1,4 @@
+---
+title: add :iids param to IssuableFinder (resolve technical dept)
+merge_request: 9222
+author: mhasbini
diff --git a/changelogs/unreleased/26500-informative-slack-notifications.yml b/changelogs/unreleased/26500-informative-slack-notifications.yml
new file mode 100644
index 00000000000..342235424f4
--- /dev/null
+++ b/changelogs/unreleased/26500-informative-slack-notifications.yml
@@ -0,0 +1,4 @@
+---
+title: Add user & build links in Slack Notifications
+merge_request: 8641
+author: Poornima M
diff --git a/changelogs/unreleased/27920-both-wip-messages-showing.yml b/changelogs/unreleased/27920-both-wip-messages-showing.yml
new file mode 100644
index 00000000000..497fda8c8ba
--- /dev/null
+++ b/changelogs/unreleased/27920-both-wip-messages-showing.yml
@@ -0,0 +1,4 @@
+---
+title: Dispatch needed JS when creating a new MR in diff view
+merge_request:
+author:
diff --git a/changelogs/unreleased/28236-browse-button-dropping.yml b/changelogs/unreleased/28236-browse-button-dropping.yml
new file mode 100644
index 00000000000..3a3d755f40c
--- /dev/null
+++ b/changelogs/unreleased/28236-browse-button-dropping.yml
@@ -0,0 +1,4 @@
+---
+title: Increase right side of file header to button stays on same line
+merge_request:
+author:
diff --git a/changelogs/unreleased/28303-change-development-tanuki-favicon-colors-to-match-logo.yml b/changelogs/unreleased/28303-change-development-tanuki-favicon-colors-to-match-logo.yml
new file mode 100644
index 00000000000..b97e9a59b2a
--- /dev/null
+++ b/changelogs/unreleased/28303-change-development-tanuki-favicon-colors-to-match-logo.yml
@@ -0,0 +1,4 @@
+---
+title: Change development tanuki favicon colors to match logo color order
+merge_request:
+author:
diff --git a/changelogs/unreleased/dynamic-project-title-fixture.yml b/changelogs/unreleased/dynamic-project-title-fixture.yml
new file mode 100644
index 00000000000..2404cbb891c
--- /dev/null
+++ b/changelogs/unreleased/dynamic-project-title-fixture.yml
@@ -0,0 +1,4 @@
+---
+title: Replace static fixture for project_title_spec.js
+merge_request: 9175
+author: winniehell
diff --git a/changelogs/unreleased/fix-gb-pipeline-retry-builds-started.yml b/changelogs/unreleased/fix-gb-pipeline-retry-builds-started.yml
new file mode 100644
index 00000000000..49e243ca6bb
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-pipeline-retry-builds-started.yml
@@ -0,0 +1,4 @@
+---
+title: Fix CI/CD pipeline retry and take stages order into account
+merge_request: 9021
+author:
diff --git a/changelogs/unreleased/fix-gb-pipeline-retry-cancel-buttons-consistency.yml b/changelogs/unreleased/fix-gb-pipeline-retry-cancel-buttons-consistency.yml
new file mode 100644
index 00000000000..d747e0e63a3
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-pipeline-retry-cancel-buttons-consistency.yml
@@ -0,0 +1,4 @@
+---
+title: Fix pipeline retry and cancel buttons on pipeline details page
+merge_request: 9225
+author:
diff --git a/changelogs/unreleased/gfm-autocomplete-fixes.yml b/changelogs/unreleased/gfm-autocomplete-fixes.yml
new file mode 100644
index 00000000000..737e2ad5234
--- /dev/null
+++ b/changelogs/unreleased/gfm-autocomplete-fixes.yml
@@ -0,0 +1,4 @@
+---
+title: Fix errors in slash commands matcher, add simple test coverage
+merge_request:
+author: YarNayar
diff --git a/changelogs/unreleased/paginate-all-the-things.yml b/changelogs/unreleased/paginate-all-the-things.yml
new file mode 100644
index 00000000000..52f23ba52a9
--- /dev/null
+++ b/changelogs/unreleased/paginate-all-the-things.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: Paginate all endpoints that return an array'
+merge_request: 8606
+author: Robert Schilling
diff --git a/changelogs/unreleased/task_list_refactor.yml b/changelogs/unreleased/task_list_refactor.yml
new file mode 100644
index 00000000000..68942dadaa8
--- /dev/null
+++ b/changelogs/unreleased/task_list_refactor.yml
@@ -0,0 +1,4 @@
+---
+title: Deduplicate markdown task lists
+merge_request:
+author:
diff --git a/config/initializers/4_ci_app.rb b/config/initializers/4_ci_app.rb
deleted file mode 100644
index d252e403102..00000000000
--- a/config/initializers/4_ci_app.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-module GitlabCi
- VERSION = Gitlab::VERSION
- REVISION = Gitlab::REVISION
-
- def self.config
- Settings
- end
-end
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
index 12497acff98..bf7dcc008e9 100644
--- a/doc/api/milestones.md
+++ b/doc/api/milestones.md
@@ -103,3 +103,16 @@ Parameters:
- `id` (required) - The ID of a project
- `milestone_id` (required) - The ID of a project milestone
+
+## Get all merge requests assigned to a single milestone
+
+Gets all merge requests assigned to a single project milestone.
+
+```
+GET /projects/:id/milestones/:milestone_id/merge_requests
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `milestone_id` (required) - The ID of a project milestone \ No newline at end of file
diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md
index 84ff72bc36c..5c1fa6b47a0 100644
--- a/doc/api/v3_to_v4.md
+++ b/doc/api/v3_to_v4.md
@@ -24,3 +24,4 @@ changes are in V4:
- `/dockerfiles/:key`
- Moved `/projects/fork/:id` to `/projects/:id/fork`
- Endpoints `/projects/owned`, `/projects/visible`, `/projects/starred` & `/projects/all` are consolidated into `/projects` using query parameters
+- Return pagination headers for all endpoints that return an array
diff --git a/doc/development/limit_ee_conflicts.md b/doc/development/limit_ee_conflicts.md
index 568dedf1669..2d82b09f301 100644
--- a/doc/development/limit_ee_conflicts.md
+++ b/doc/development/limit_ee_conflicts.md
@@ -2,19 +2,26 @@
This guide contains best-practices for avoiding conflicts between CE and EE.
-## Context
+## Daily CE Upstream merge
-Usually, GitLab Community Edition is merged into the Enterprise Edition once a
-week. During these merges, it's very common to get conflicts when some changes
-in CE do not apply cleanly to EE.
+GitLab Community Edition is merged daily into the Enterprise Edition (look for
+the [`CE Upstream` merge requests]). The daily merge is currently done manually
+by four individuals.
-There are a few things that can help you as a developer to:
+**If a developer pings you in a `CE Upstream` merge request for help with
+resolving conflicts, please help them because it means that you didn't do your
+job to reduce the conflicts nor to ease their resolution in the first place!**
-- know when your merge request to CE will conflict when merged to EE
-- avoid such conflicts in the first place
-- ease future conflict resolutions if conflict is inevitable
+To avoid the conflicts beforehand when working on CE, there are a few tools and
+techniques that can help you:
-## Check the `rake ee_compat_check` in your merge requests
+- know what are the usual types of conflicts and how to prevent them
+- the CI `rake ee_compat_check` job tells you if you need to open an EE-version
+ of your CE merge request
+
+[`CE Upstream` merge requests]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests?label_name%5B%5D=CE+upstream
+
+## Check the status of the CI `rake ee_compat_check` job
For each commit (except on `master`), the `rake ee_compat_check` CI job tries to
detect if the current branch's changes will conflict during the CE->EE merge.
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index c228ea72f22..4889e3ec50c 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -67,7 +67,7 @@ With GitLab flow we offer additional guidance for these questions.
![Master branch and production branch with arrow that indicate deployments](production_branch.png)
GitHub flow does assume you are able to deploy to production every time you merge a feature branch.
-This is possible for SaaS applications but are many cases where this is not possible.
+This is possible for SaaS applications but there are many cases where this is not possible.
One would be a situation where you are not in control of the exact release moment, for example an iOS application that needs to pass App Store validation.
Another example is when you have deployment windows (workdays from 10am to 4pm when the operations team is at full capacity) but you also merge code at other times.
In these cases you can make a production branch that reflects the deployed code.
diff --git a/features/dashboard/issues.feature b/features/dashboard/issues.feature
deleted file mode 100644
index 99dad88a402..00000000000
--- a/features/dashboard/issues.feature
+++ /dev/null
@@ -1,21 +0,0 @@
-@dashboard
-Feature: Dashboard Issues
- Background:
- Given I sign in as a user
- And I have authored issues
- And I have assigned issues
- And I have other issues
- And I visit dashboard issues page
-
- Scenario: I should see assigned issues
- Then I should see issues assigned to me
-
- @javascript
- Scenario: I should see authored issues
- When I click "Authored by me" link
- Then I should see issues authored by me
-
- @javascript
- Scenario: I should see all issues
- When I click "All" link
- Then I should see all issues
diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb
deleted file mode 100644
index 4e15d79ae74..00000000000
--- a/features/steps/dashboard/issues.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include Select2Helper
-
- step 'I should see issues assigned to me' do
- should_see(assigned_issue)
- should_not_see(authored_issue)
- should_not_see(other_issue)
- end
-
- step 'I should see issues authored by me' do
- should_see(authored_issue)
- should_see(authored_issue_on_public_project)
- should_not_see(assigned_issue)
- should_not_see(other_issue)
- end
-
- step 'I should see all issues' do
- should_see(authored_issue)
- should_see(assigned_issue)
- should_see(other_issue)
- end
-
- step 'I have authored issues' do
- authored_issue
- authored_issue_on_public_project
- end
-
- step 'I have assigned issues' do
- assigned_issue
- end
-
- step 'I have other issues' do
- other_issue
- end
-
- step 'I click "Authored by me" link' do
- find("#assignee_id").set("")
- find(".js-author-search", match: :first).click
- find(".dropdown-menu-author li a", match: :first, text: current_user.to_reference).click
- end
-
- step 'I click "All" link' do
- find(".js-author-search").click
- expect(page).to have_selector(".dropdown-menu-author li a")
- find(".dropdown-menu-author li a", match: :first).click
- expect(page).not_to have_selector(".dropdown-menu-author li a")
-
- find(".js-assignee-search").click
- expect(page).to have_selector(".dropdown-menu-assignee li a")
- find(".dropdown-menu-assignee li a", match: :first).click
- expect(page).not_to have_selector(".dropdown-menu-assignee li a")
- end
-
- def should_see(issue)
- expect(page).to have_content(issue.title[0..10])
- end
-
- def should_not_see(issue)
- expect(page).not_to have_content(issue.title[0..10])
- end
-
- def assigned_issue
- @assigned_issue ||= create :issue, assignee: current_user, project: project
- end
-
- def authored_issue
- @authored_issue ||= create :issue, author: current_user, project: project
- end
-
- def other_issue
- @other_issue ||= create :issue, project: project
- end
-
- def authored_issue_on_public_project
- @authored_issue_on_public_project ||= create :issue, author: current_user, project: public_project
- end
-
- def project
- @project ||= begin
- project = create(:empty_project)
- project.team << [current_user, :master]
- project
- end
- end
-
- def public_project
- @public_project ||= create(:empty_project, :public)
- end
-end
diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb
index d008a8a26af..5bc3a1f5ac4 100644
--- a/features/steps/shared/builds.rb
+++ b/features/steps/shared/builds.rb
@@ -11,7 +11,7 @@ module SharedBuilds
step 'project has a recent build' do
@pipeline = create(:ci_empty_pipeline, project: @project, sha: @project.commit.sha, ref: 'master')
- @build = create(:ci_build_with_coverage, pipeline: @pipeline)
+ @build = create(:ci_build, :coverage, pipeline: @pipeline)
end
step 'recent build is successful' do
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 06346ae822a..dbb7271ccbd 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -5,13 +5,22 @@ module API
version %w(v3 v4), using: :path
version 'v3', using: :path do
+ mount ::API::V3::Boards
+ mount ::API::V3::Branches
mount ::API::V3::DeployKeys
mount ::API::V3::Issues
+ mount ::API::V3::Labels
mount ::API::V3::Members
+ mount ::API::V3::MergeRequestDiffs
mount ::API::V3::MergeRequests
+ mount ::API::V3::ProjectHooks
mount ::API::V3::Projects
mount ::API::V3::ProjectSnippets
+ mount ::API::V3::Repositories
+ mount ::API::V3::SystemHooks
+ mount ::API::V3::Tags
mount ::API::V3::Templates
+ mount ::API::V3::Users
end
before { allow_access_with_scope :api }
diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb
index 58a4df54bea..2ef327217ea 100644
--- a/lib/api/award_emoji.rb
+++ b/lib/api/award_emoji.rb
@@ -28,8 +28,8 @@ module API
end
get endpoint do
if can_read_awardable?
- awards = paginate(awardable.award_emoji)
- present awards, with: Entities::AwardEmoji
+ awards = awardable.award_emoji
+ present paginate(awards), with: Entities::AwardEmoji
else
not_found!("Award Emoji")
end
diff --git a/lib/api/boards.rb b/lib/api/boards.rb
index 13752eb4947..f4226e5a89d 100644
--- a/lib/api/boards.rb
+++ b/lib/api/boards.rb
@@ -1,6 +1,7 @@
module API
- # Boards API
class Boards < Grape::API
+ include PaginationParams
+
before { authenticate! }
params do
@@ -11,9 +12,12 @@ module API
detail 'This feature was introduced in 8.13'
success Entities::Board
end
+ params do
+ use :pagination
+ end
get ':id/boards' do
authorize!(:read_board, user_project)
- present user_project.boards, with: Entities::Board
+ present paginate(user_project.boards), with: Entities::Board
end
params do
@@ -40,9 +44,12 @@ module API
detail 'Does not include `done` list. This feature was introduced in 8.13'
success Entities::List
end
+ params do
+ use :pagination
+ end
get '/lists' do
authorize!(:read_board, user_project)
- present board_lists, with: Entities::List
+ present paginate(board_lists), with: Entities::List
end
desc 'Get a list of a project board' do
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 9331be1f7de..9d1f5a28ef6 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -1,8 +1,9 @@
require 'mime/types'
module API
- # Projects API
class Branches < Grape::API
+ include PaginationParams
+
before { authenticate! }
before { authorize! :download_code, user_project }
@@ -13,10 +14,13 @@ module API
desc 'Get a project repository branches' do
success Entities::RepoBranch
end
+ params do
+ use :pagination
+ end
get ":id/repository/branches" do
- branches = user_project.repository.branches.sort_by(&:name)
+ branches = ::Kaminari.paginate_array(user_project.repository.branches.sort_by(&:name))
- present branches, with: Entities::RepoBranch, project: user_project
+ present paginate(branches), with: Entities::RepoBranch, project: user_project
end
desc 'Get a single branch' do
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index 3f5183d46a2..982645c2f64 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -1,12 +1,17 @@
module API
class DeployKeys < Grape::API
+ include PaginationParams
+
before { authenticate! }
+ desc 'Return all deploy keys'
+ params do
+ use :pagination
+ end
get "deploy_keys" do
authenticated_as_admin!
- keys = DeployKey.all
- present keys, with: Entities::SSHKey
+ present paginate(DeployKey.all), with: Entities::SSHKey
end
params do
@@ -18,8 +23,11 @@ module API
desc "Get a specific project's deploy keys" do
success Entities::SSHKey
end
+ params do
+ use :pagination
+ end
get ":id/deploy_keys" do
- present user_project.deploy_keys, with: Entities::SSHKey
+ present paginate(user_project.deploy_keys), with: Entities::SSHKey
end
desc 'Get single deploy key' do
diff --git a/lib/api/files.rb b/lib/api/files.rb
index 2ecdd747c8e..6e16ccd2fd8 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -1,5 +1,4 @@
module API
- # Projects API
class Files < Grape::API
helpers do
def commit_params(attrs)
diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb
index 2199eea7e5f..0764b58fb4c 100644
--- a/lib/api/helpers/pagination.rb
+++ b/lib/api/helpers/pagination.rb
@@ -2,7 +2,7 @@ module API
module Helpers
module Pagination
def paginate(relation)
- relation.page(params[:page]).per(params[:per_page].to_i).tap do |data|
+ relation.page(params[:page]).per(params[:per_page]).tap do |data|
add_pagination_headers(data)
end
end
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index 652786d4e3e..d2955af3f95 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -1,6 +1,7 @@
module API
- # Labels API
class Labels < Grape::API
+ include PaginationParams
+
before { authenticate! }
params do
@@ -10,8 +11,11 @@ module API
desc 'Get all labels of the project' do
success Entities::Label
end
+ params do
+ use :pagination
+ end
get ':id/labels' do
- present available_labels, with: Entities::Label, current_user: current_user, project: user_project
+ present paginate(available_labels), with: Entities::Label, current_user: current_user, project: user_project
end
desc 'Create a new label' do
diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb
index bc3d69f6904..4901a7cfea6 100644
--- a/lib/api/merge_request_diffs.rb
+++ b/lib/api/merge_request_diffs.rb
@@ -1,6 +1,8 @@
module API
# MergeRequestDiff API
class MergeRequestDiffs < Grape::API
+ include PaginationParams
+
before { authenticate! }
resource :projects do
@@ -12,12 +14,12 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
+ use :pagination
end
-
get ":id/merge_requests/:merge_request_id/versions" do
merge_request = find_merge_request_with_access(params[:merge_request_id])
- present merge_request.merge_request_diffs, with: Entities::MergeRequestDiff
+ present paginate(merge_request.merge_request_diffs), with: Entities::MergeRequestDiff
end
desc 'Get a single merge request diff version' do
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 8e09a6f7354..bdd764abfeb 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -119,8 +119,9 @@ module API
end
get ':id/merge_requests/:merge_request_id/commits' do
merge_request = find_merge_request_with_access(params[:merge_request_id])
+ commits = ::Kaminari.paginate_array(merge_request.commits)
- present merge_request.commits, with: Entities::RepoCommit
+ present paginate(commits), with: Entities::RepoCommit
end
desc 'Show the merge request changes' do
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
index 3c373a84ec5..0b4ed76b35c 100644
--- a/lib/api/milestones.rb
+++ b/lib/api/milestones.rb
@@ -120,6 +120,28 @@ module API
issues = IssuesFinder.new(current_user, finder_params).execute
present paginate(issues), with: Entities::Issue, current_user: current_user, project: user_project
end
+
+ desc 'Get all merge requests for a single project milestone' do
+ detail 'This feature was introduced in GitLab 9.'
+ success Entities::MergeRequest
+ end
+ params do
+ requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
+ use :pagination
+ end
+ get ':id/milestones/:milestone_id/merge_requests' do
+ authorize! :read_milestone, user_project
+
+ milestone = user_project.milestones.find(params[:milestone_id])
+
+ finder_params = {
+ project_id: user_project.id,
+ milestone_id: milestone.id
+ }
+
+ merge_requests = MergeRequestsFinder.new(current_user, finder_params).execute
+ present paginate(merge_requests), with: Entities::MergeRequest, current_user: current_user, project: user_project
+ end
end
end
end
diff --git a/lib/api/pagination_params.rb b/lib/api/pagination_params.rb
index 8c1e4381a74..f566eb3ed2b 100644
--- a/lib/api/pagination_params.rb
+++ b/lib/api/pagination_params.rb
@@ -15,8 +15,8 @@ module API
included do
helpers do
params :pagination do
- optional :page, type: Integer, desc: 'Current page number'
- optional :per_page, type: Integer, desc: 'Number of items per page'
+ optional :page, type: Integer, default: 1, desc: 'Current page number'
+ optional :per_page, type: Integer, default: 20, desc: 'Number of items per page'
end
end
end
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index cb679e6658a..f7a28d7ad10 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -32,9 +32,7 @@ module API
use :pagination
end
get ":id/hooks" do
- hooks = paginate user_project.hooks
-
- present hooks, with: Entities::ProjectHook
+ present paginate(user_project.hooks), with: Entities::ProjectHook
end
desc 'Get a project hook' do
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 4ca6646a6f1..bfda6f45b0a 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -2,6 +2,8 @@ require 'mime/types'
module API
class Repositories < Grape::API
+ include PaginationParams
+
before { authorize! :download_code, user_project }
params do
@@ -24,6 +26,7 @@ module API
optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used'
optional :path, type: String, desc: 'The path of the tree'
optional :recursive, type: Boolean, default: false, desc: 'Used to get a recursive tree'
+ use :pagination
end
get ':id/repository/tree' do
ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
@@ -33,8 +36,8 @@ module API
not_found!('Tree') unless commit
tree = user_project.repository.tree(commit.id, path, recursive: params[:recursive])
-
- present tree.sorted_entries, with: Entities::RepoTreeObject
+ entries = ::Kaminari.paginate_array(tree.sorted_entries)
+ present paginate(entries), with: Entities::RepoTreeObject
end
desc 'Get a raw file contents'
@@ -100,10 +103,13 @@ module API
desc 'Get repository contributors' do
success Entities::Contributor
end
+ params do
+ use :pagination
+ end
get ':id/repository/contributors' do
begin
- present user_project.repository.contributors,
- with: Entities::Contributor
+ contributors = ::Kaminari.paginate_array(user_project.repository.contributors)
+ present paginate(contributors), with: Entities::Contributor
rescue
not_found!
end
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index 4816b5ed1b7..4fbd4096533 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -60,8 +60,9 @@ module API
put ':id' do
runner = get_runner(params.delete(:id))
authenticate_update_runner!(runner)
+ update_service = Ci::UpdateRunnerService.new(runner)
- if runner.update(declared_params(include_missing: false))
+ if update_service.update(declared_params(include_missing: false))
present runner, with: Entities::RunnerDetails, current_user: current_user
else
render_validation_error!(runner)
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index 708ec8cfe70..d038a3fa828 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -1,6 +1,7 @@
module API
- # Hooks API
class SystemHooks < Grape::API
+ include PaginationParams
+
before do
authenticate!
authenticated_as_admin!
@@ -10,10 +11,11 @@ module API
desc 'Get the list of system hooks' do
success Entities::Hook
end
+ params do
+ use :pagination
+ end
get do
- hooks = SystemHook.all
-
- present hooks, with: Entities::Hook
+ present paginate(SystemHook.all), with: Entities::Hook
end
desc 'Create a new system hook' do
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index b6fd8f569a9..86759ab882f 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -1,6 +1,7 @@
module API
- # Git Tags API
class Tags < Grape::API
+ include PaginationParams
+
before { authorize! :download_code, user_project }
params do
@@ -10,9 +11,12 @@ module API
desc 'Get a project repository tags' do
success Entities::RepoTag
end
+ params do
+ use :pagination
+ end
get ":id/repository/tags" do
- present user_project.repository.tags.sort_by(&:name).reverse,
- with: Entities::RepoTag, project: user_project
+ tags = ::Kaminari.paginate_array(user_project.repository.tags.sort_by(&:name).reverse)
+ present paginate(tags), with: Entities::RepoTag, project: user_project
end
desc 'Get a single repository tag' do
diff --git a/lib/api/templates.rb b/lib/api/templates.rb
index 8a2d66efd89..0fc13b35d5b 100644
--- a/lib/api/templates.rb
+++ b/lib/api/templates.rb
@@ -1,5 +1,7 @@
module API
class Templates < Grape::API
+ include PaginationParams
+
GLOBAL_TEMPLATE_TYPES = {
gitignores: {
klass: Gitlab::Template::GitignoreTemplate,
@@ -51,12 +53,14 @@ module API
end
params do
optional :popular, type: Boolean, desc: 'If passed, returns only popular licenses'
+ use :pagination
end
get "templates/licenses" do
options = {
featured: declared(params).popular.present? ? true : nil
}
- present Licensee::License.all(options), with: ::API::Entities::RepoLicense
+ licences = ::Kaminari.paginate_array(Licensee::License.all(options))
+ present paginate(licences), with: Entities::RepoLicense
end
desc 'Get the text for a specific license' do
@@ -82,8 +86,12 @@ module API
detail "This feature was introduced in GitLab #{gitlab_version}."
success Entities::TemplatesList
end
+ params do
+ use :pagination
+ end
get "templates/#{template_type}" do
- present klass.all, with: Entities::TemplatesList
+ templates = ::Kaminari.paginate_array(klass.all)
+ present paginate(templates), with: Entities::TemplatesList
end
desc 'Get the text for a specific template present in local filesystem' do
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 82ac3886ac3..05538f5a42f 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -209,6 +209,7 @@ module API
end
params do
requires :id, type: Integer, desc: 'The ID of the user'
+ use :pagination
end
get ':id/keys' do
authenticated_as_admin!
@@ -216,7 +217,7 @@ module API
user = User.find_by(id: params[:id])
not_found!('User') unless user
- present user.keys, with: Entities::SSHKey
+ present paginate(user.keys), with: Entities::SSHKey
end
desc 'Delete an existing SSH key from a specified user. Available only for admins.' do
@@ -266,13 +267,14 @@ module API
end
params do
requires :id, type: Integer, desc: 'The ID of the user'
+ use :pagination
end
get ':id/emails' do
authenticated_as_admin!
user = User.find_by(id: params[:id])
not_found!('User') unless user
- present user.emails, with: Entities::Email
+ present paginate(user.emails), with: Entities::Email
end
desc 'Delete an email address of a specified user. Available only for admins.' do
@@ -373,8 +375,11 @@ module API
desc "Get the currently authenticated user's SSH keys" do
success Entities::SSHKey
end
+ params do
+ use :pagination
+ end
get "keys" do
- present current_user.keys, with: Entities::SSHKey
+ present paginate(current_user.keys), with: Entities::SSHKey
end
desc 'Get a single key owned by currently authenticated user' do
@@ -423,8 +428,11 @@ module API
desc "Get the currently authenticated user's email addresses" do
success Entities::Email
end
+ params do
+ use :pagination
+ end
get "emails" do
- present current_user.emails, with: Entities::Email
+ present paginate(current_user.emails), with: Entities::Email
end
desc 'Get a single email address owned by the currently authenticated user' do
diff --git a/lib/api/v3/boards.rb b/lib/api/v3/boards.rb
new file mode 100644
index 00000000000..31d708bc2c8
--- /dev/null
+++ b/lib/api/v3/boards.rb
@@ -0,0 +1,51 @@
+module API
+ module V3
+ class Boards < Grape::API
+ before { authenticate! }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ desc 'Get all project boards' do
+ detail 'This feature was introduced in 8.13'
+ success ::API::Entities::Board
+ end
+ get ':id/boards' do
+ authorize!(:read_board, user_project)
+ present user_project.boards, with: ::API::Entities::Board
+ end
+
+ params do
+ requires :board_id, type: Integer, desc: 'The ID of a board'
+ end
+ segment ':id/boards/:board_id' do
+ helpers do
+ def project_board
+ board = user_project.boards.first
+
+ if params[:board_id] == board.id
+ board
+ else
+ not_found!('Board')
+ end
+ end
+
+ def board_lists
+ project_board.lists.destroyable
+ end
+ end
+
+ desc 'Get the lists of a project board' do
+ detail 'Does not include `done` list. This feature was introduced in 8.13'
+ success ::API::Entities::List
+ end
+ get '/lists' do
+ authorize!(:read_board, user_project)
+ present board_lists, with: ::API::Entities::List
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/branches.rb b/lib/api/v3/branches.rb
new file mode 100644
index 00000000000..733c6b21be5
--- /dev/null
+++ b/lib/api/v3/branches.rb
@@ -0,0 +1,24 @@
+require 'mime/types'
+
+module API
+ module V3
+ class Branches < Grape::API
+ before { authenticate! }
+ before { authorize! :download_code, user_project }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ desc 'Get a project repository branches' do
+ success ::API::Entities::RepoBranch
+ end
+ get ":id/repository/branches" do
+ branches = user_project.repository.branches.sort_by(&:name)
+
+ present branches, with: ::API::Entities::RepoBranch, project: user_project
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/issues.rb b/lib/api/v3/issues.rb
index 081d45165e8..ba5b6fdbe52 100644
--- a/lib/api/v3/issues.rb
+++ b/lib/api/v3/issues.rb
@@ -16,7 +16,8 @@ module API
labels = args.delete(:labels)
args[:label_name] = labels if match_all_labels
- args[:search] = "#{Issue.reference_prefix}#{args.delete(:iid)}" if args.key?(:iid)
+ # IssuesFinder expects iids
+ args[:iids] = args.delete(:iid) if args.key?(:iid)
issues = IssuesFinder.new(current_user, args).execute.inc_notes_with_associations
diff --git a/lib/api/v3/labels.rb b/lib/api/v3/labels.rb
new file mode 100644
index 00000000000..5c3261311bf
--- /dev/null
+++ b/lib/api/v3/labels.rb
@@ -0,0 +1,19 @@
+module API
+ module V3
+ class Labels < Grape::API
+ before { authenticate! }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ desc 'Get all labels of the project' do
+ success ::API::Entities::Label
+ end
+ get ':id/labels' do
+ present available_labels, with: ::API::Entities::Label, current_user: current_user, project: user_project
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/repositories.rb b/lib/api/v3/repositories.rb
new file mode 100644
index 00000000000..3549ea225ef
--- /dev/null
+++ b/lib/api/v3/repositories.rb
@@ -0,0 +1,55 @@
+require 'mime/types'
+
+module API
+ module V3
+ class Repositories < Grape::API
+ before { authorize! :download_code, user_project }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ helpers do
+ def handle_project_member_errors(errors)
+ if errors[:project_access].any?
+ error!(errors[:project_access], 422)
+ end
+ not_found!
+ end
+ end
+
+ desc 'Get a project repository tree' do
+ success ::API::Entities::RepoTreeObject
+ end
+ params do
+ optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used'
+ optional :path, type: String, desc: 'The path of the tree'
+ optional :recursive, type: Boolean, default: false, desc: 'Used to get a recursive tree'
+ end
+ get ':id/repository/tree' do
+ ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
+ path = params[:path] || nil
+
+ commit = user_project.commit(ref)
+ not_found!('Tree') unless commit
+
+ tree = user_project.repository.tree(commit.id, path, recursive: params[:recursive])
+
+ present tree.sorted_entries, with: ::API::Entities::RepoTreeObject
+ end
+
+ desc 'Get repository contributors' do
+ success ::API::Entities::Contributor
+ end
+ get ':id/repository/contributors' do
+ begin
+ present user_project.repository.contributors,
+ with: ::API::Entities::Contributor
+ rescue
+ not_found!
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/system_hooks.rb b/lib/api/v3/system_hooks.rb
new file mode 100644
index 00000000000..391510b9ee0
--- /dev/null
+++ b/lib/api/v3/system_hooks.rb
@@ -0,0 +1,19 @@
+module API
+ module V3
+ class SystemHooks < Grape::API
+ before do
+ authenticate!
+ authenticated_as_admin!
+ end
+
+ resource :hooks do
+ desc 'Get the list of system hooks' do
+ success ::API::Entities::Hook
+ end
+ get do
+ present SystemHook.all, with: ::API::Entities::Hook
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/tags.rb b/lib/api/v3/tags.rb
new file mode 100644
index 00000000000..016e3d86932
--- /dev/null
+++ b/lib/api/v3/tags.rb
@@ -0,0 +1,20 @@
+module API
+ module V3
+ class Tags < Grape::API
+ before { authorize! :download_code, user_project }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ desc 'Get a project repository tags' do
+ success ::API::Entities::RepoTag
+ end
+ get ":id/repository/tags" do
+ tags = user_project.repository.tags.sort_by(&:name).reverse
+ present tags, with: ::API::Entities::RepoTag, project: user_project
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/users.rb b/lib/api/v3/users.rb
new file mode 100644
index 00000000000..ceb139d11b8
--- /dev/null
+++ b/lib/api/v3/users.rb
@@ -0,0 +1,64 @@
+module API
+ module V3
+ class Users < Grape::API
+ include PaginationParams
+
+ before do
+ allow_access_with_scope :read_user if request.get?
+ authenticate!
+ end
+
+ resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
+ desc 'Get the SSH keys of a specified user. Available only for admins.' do
+ success ::API::Entities::SSHKey
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ use :pagination
+ end
+ get ':id/keys' do
+ authenticated_as_admin!
+
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ present paginate(user.keys), with: ::API::Entities::SSHKey
+ end
+
+ desc 'Get the emails addresses of a specified user. Available only for admins.' do
+ success ::API::Entities::Email
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ use :pagination
+ end
+ get ':id/emails' do
+ authenticated_as_admin!
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ present user.emails, with: ::API::Entities::Email
+ end
+ end
+
+ resource :user do
+ desc "Get the currently authenticated user's SSH keys" do
+ success ::API::Entities::SSHKey
+ end
+ params do
+ use :pagination
+ end
+ get "keys" do
+ present current_user.keys, with: ::API::Entities::SSHKey
+ end
+
+ desc "Get the currently authenticated user's email addresses" do
+ success ::API::Entities::Email
+ end
+ get "emails" do
+ present current_user.emails, with: ::API::Entities::Email
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/data_builder/build.rb b/lib/gitlab/data_builder/build.rb
index 6548e6475c6..f78106f5b10 100644
--- a/lib/gitlab/data_builder/build.rb
+++ b/lib/gitlab/data_builder/build.rb
@@ -8,6 +8,8 @@ module Gitlab
commit = build.pipeline
user = build.user
+ author_url = build_author_url(build.commit, commit)
+
data = {
object_kind: 'build',
@@ -43,6 +45,7 @@ module Gitlab
message: commit.git_commit_message,
author_name: commit.git_author_name,
author_email: commit.git_author_email,
+ author_url: author_url,
status: commit.status,
duration: commit.duration,
started_at: commit.started_at,
@@ -62,6 +65,13 @@ module Gitlab
data
end
+
+ private
+
+ def build_author_url(commit, pipeline)
+ author = commit.try(:author)
+ author ? Gitlab::Routing.url_helpers.user_url(author) : "mailto:#{pipeline.git_author_email}"
+ end
end
end
end
diff --git a/package.json b/package.json
index 08bde1bc313..c696de439bd 100644
--- a/package.json
+++ b/package.json
@@ -15,27 +15,25 @@
"babel-loader": "^6.2.10",
"babel-preset-es2015": "^6.22.0",
"babel-preset-stage-2": "^6.22.0",
- "bootstrap-sass": "3.3.6",
+ "bootstrap-sass": "^3.3.6",
"compression-webpack-plugin": "^0.3.2",
- "d3": "3.5.11",
- "dropzone": "4.2.0",
+ "d3": "^3.5.11",
+ "dropzone": "^4.2.0",
"es6-promise": "^4.0.5",
"imports-loader": "^0.6.5",
- "jquery": "2.2.1",
- "jquery-ui": "github:jquery/jquery-ui#1.11.4",
- "jquery-ujs": "1.2.1",
+ "jquery": "^2.2.1",
+ "jquery-ui": "git+https://github.com/jquery/jquery-ui#1.11.4",
+ "jquery-ujs": "^1.2.1",
"js-cookie": "^2.1.3",
- "karma-mocha-reporter": "^2.2.2",
- "mousetrap": "1.4.6",
+ "mousetrap": "^1.4.6",
"pikaday": "^1.5.1",
"select2": "3.5.2-browserify",
"stats-webpack-plugin": "^0.4.3",
"timeago.js": "^2.0.5",
- "underscore": "1.8.3",
- "vue": "2.0.3",
- "vue-resource": "0.9.3",
- "webpack": "^2.2.1",
- "webpack-dev-server": "^2.3.0"
+ "underscore": "^1.8.3",
+ "vue": "^2.0.3",
+ "vue-resource": "^0.9.3",
+ "webpack": "^2.2.1"
},
"devDependencies": {
"babel-plugin-istanbul": "^4.0.0",
@@ -51,9 +49,11 @@
"karma": "^1.4.1",
"karma-coverage-istanbul-reporter": "^0.2.0",
"karma-jasmine": "^1.1.0",
+ "karma-mocha-reporter": "^2.2.2",
"karma-phantomjs-launcher": "^1.0.2",
"karma-sourcemap-loader": "^0.3.7",
- "karma-webpack": "^2.0.2"
+ "karma-webpack": "^2.0.2",
+ "webpack-dev-server": "^2.3.0"
},
"nyc": {
"exclude": [
diff --git a/spec/controllers/admin/runners_controller_spec.rb b/spec/controllers/admin/runners_controller_spec.rb
new file mode 100644
index 00000000000..b5fe40d0510
--- /dev/null
+++ b/spec/controllers/admin/runners_controller_spec.rb
@@ -0,0 +1,85 @@
+require 'spec_helper'
+
+describe Admin::RunnersController do
+ let(:runner) { create(:ci_runner) }
+
+ before do
+ sign_in(create(:admin))
+ end
+
+ describe '#index' do
+ it 'lists all runners' do
+ get :index
+
+ expect(response).to have_http_status(200)
+ end
+ end
+
+ describe '#show' do
+ it 'shows a particular runner' do
+ get :show, id: runner.id
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'shows 404 for unknown runner' do
+ get :show, id: 0
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ describe '#update' do
+ it 'updates the runner and ticks the queue' do
+ new_desc = runner.description.swapcase
+
+ expect do
+ post :update, id: runner.id, runner: { description: new_desc }
+ end.to change { runner.ensure_runner_queue_value }
+
+ runner.reload
+
+ expect(response).to have_http_status(302)
+ expect(runner.description).to eq(new_desc)
+ end
+ end
+
+ describe '#destroy' do
+ it 'destroys the runner' do
+ delete :destroy, id: runner.id
+
+ expect(response).to have_http_status(302)
+ expect(Ci::Runner.find_by(id: runner.id)).to be_nil
+ end
+ end
+
+ describe '#resume' do
+ it 'marks the runner as active and ticks the queue' do
+ runner.update(active: false)
+
+ expect do
+ post :resume, id: runner.id
+ end.to change { runner.ensure_runner_queue_value }
+
+ runner.reload
+
+ expect(response).to have_http_status(302)
+ expect(runner.active).to eq(true)
+ end
+ end
+
+ describe '#pause' do
+ it 'marks the runner as inactive and ticks the queue' do
+ runner.update(active: true)
+
+ expect do
+ post :pause, id: runner.id
+ end.to change { runner.ensure_runner_queue_value }
+
+ runner.reload
+
+ expect(response).to have_http_status(302)
+ expect(runner.active).to eq(false)
+ end
+ end
+end
diff --git a/spec/controllers/projects/runners_controller_spec.rb b/spec/controllers/projects/runners_controller_spec.rb
new file mode 100644
index 00000000000..0fa249e4405
--- /dev/null
+++ b/spec/controllers/projects/runners_controller_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+describe Projects::RunnersController do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+ let(:runner) { create(:ci_runner) }
+
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: runner
+ }
+ end
+
+ before do
+ sign_in(user)
+ project.add_master(user)
+ project.runners << runner
+ end
+
+ describe '#update' do
+ it 'updates the runner and ticks the queue' do
+ new_desc = runner.description.swapcase
+
+ expect do
+ post :update, params.merge(runner: { description: new_desc } )
+ end.to change { runner.ensure_runner_queue_value }
+
+ runner.reload
+
+ expect(response).to have_http_status(302)
+ expect(runner.description).to eq(new_desc)
+ end
+ end
+
+ describe '#destroy' do
+ it 'destroys the runner' do
+ delete :destroy, params
+
+ expect(response).to have_http_status(302)
+ expect(Ci::Runner.find_by(id: runner.id)).to be_nil
+ end
+ end
+
+ describe '#resume' do
+ it 'marks the runner as active and ticks the queue' do
+ runner.update(active: false)
+
+ expect do
+ post :resume, params
+ end.to change { runner.ensure_runner_queue_value }
+
+ runner.reload
+
+ expect(response).to have_http_status(302)
+ expect(runner.active).to eq(true)
+ end
+ end
+
+ describe '#pause' do
+ it 'marks the runner as inactive and ticks the queue' do
+ runner.update(active: true)
+
+ expect do
+ post :pause, params
+ end.to change { runner.ensure_runner_queue_value }
+
+ runner.reload
+
+ expect(response).to have_http_status(302)
+ expect(runner.active).to eq(false)
+ end
+ end
+end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 0397d5d4001..a90534d10ba 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -89,8 +89,9 @@ FactoryGirl.define do
tag true
end
- factory :ci_build_with_coverage do
+ trait :coverage do
coverage 99.9
+ coverage_regex '/(d+)/'
end
trait :trace do
@@ -99,6 +100,16 @@ FactoryGirl.define do
end
end
+ trait :erased do
+ erased_at Time.now
+ erased_by factory: :user
+ end
+
+ trait :queued do
+ queued_at Time.now
+ runner factory: :ci_runner
+ end
+
trait :artifacts do
after(:create) do |build, _|
build.artifacts_file =
@@ -128,5 +139,17 @@ FactoryGirl.define do
build.save!
end
end
+
+ trait :with_commit do
+ after(:build) do |build|
+ allow(build).to receive(:commit).and_return build(:commit, :without_author)
+ end
+ end
+
+ trait :with_commit_and_author do
+ after(:build) do |build|
+ allow(build).to receive(:commit).and_return build(:commit)
+ end
+ end
end
end
diff --git a/spec/factories/commits.rb b/spec/factories/commits.rb
index ac6eb0a7897..89e260cf65b 100644
--- a/spec/factories/commits.rb
+++ b/spec/factories/commits.rb
@@ -8,5 +8,15 @@ FactoryGirl.define do
initialize_with do
new(git_commit, project)
end
+
+ after(:build) do |commit|
+ allow(commit).to receive(:author).and_return build(:author)
+ end
+
+ trait :without_author do
+ after(:build) do |commit|
+ allow(commit).to receive(:author).and_return nil
+ end
+ end
end
end
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 7651364703e..59e87b3f69c 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -15,8 +15,11 @@ describe 'Issue Boards', feature: true, js: true do
let!(:issue2) { create(:labeled_issue, project: project, labels: [development, stretch]) }
let(:board) { create(:board, project: project) }
let!(:list) { create(:list, board: board, label: development, position: 0) }
+ let(:card) { first('.board').first('.card') }
before do
+ Timecop.freeze
+
project.team << [user, :master]
login_as(user)
@@ -25,32 +28,28 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_vue_resource
end
+ after do
+ Timecop.return
+ end
+
it 'shows sidebar when clicking issue' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
expect(page).to have_selector('.issue-boards-sidebar')
end
it 'closes sidebar when clicking issue' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
expect(page).to have_selector('.issue-boards-sidebar')
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
expect(page).not_to have_selector('.issue-boards-sidebar')
end
it 'closes sidebar when clicking close button' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
expect(page).to have_selector('.issue-boards-sidebar')
@@ -60,9 +59,7 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'shows issue details when sidebar is open' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
page.within('.issue-boards-sidebar') do
expect(page).to have_content(issue2.title)
@@ -70,15 +67,15 @@ describe 'Issue Boards', feature: true, js: true do
end
end
- it 'removes card from board when clicking remove button' do
- page.within(first('.board')) do
- first('.card').click
- end
+ it 'removes card from board when clicking ' do
+ click_card(card)
page.within('.issue-boards-sidebar') do
click_button 'Remove from board'
end
+ wait_for_vue_resource
+
page.within(first('.board')) do
expect(page).to have_selector('.card', count: 1)
end
@@ -86,9 +83,7 @@ describe 'Issue Boards', feature: true, js: true do
context 'assignee' do
it 'updates the issues assignee' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
page.within('.assignee') do
click_link 'Edit'
@@ -104,17 +99,12 @@ describe 'Issue Boards', feature: true, js: true do
expect(page).to have_content(user.name)
end
- page.within(first('.board')) do
- page.within(first('.card')) do
- expect(page).to have_selector('.avatar')
- end
- end
+ expect(card).to have_selector('.avatar')
end
it 'removes the assignee' do
- page.within(first('.board')) do
- find('.card:nth-child(2)').click
- end
+ card_two = first('.board').find('.card:nth-child(2)')
+ click_card(card_two)
page.within('.assignee') do
click_link 'Edit'
@@ -130,17 +120,11 @@ describe 'Issue Boards', feature: true, js: true do
expect(page).to have_content('No assignee')
end
- page.within(first('.board')) do
- page.within(find('.card:nth-child(2)')) do
- expect(page).not_to have_selector('.avatar')
- end
- end
+ expect(card_two).not_to have_selector('.avatar')
end
it 'assignees to current user' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
page.within(find('.assignee')) do
expect(page).to have_content('No assignee')
@@ -152,17 +136,11 @@ describe 'Issue Boards', feature: true, js: true do
expect(page).to have_content(user.name)
end
- page.within(first('.board')) do
- page.within(first('.card')) do
- expect(page).to have_selector('.avatar')
- end
- end
+ expect(card).to have_selector('.avatar')
end
it 'resets assignee dropdown' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
page.within('.assignee') do
click_link 'Edit'
@@ -192,9 +170,7 @@ describe 'Issue Boards', feature: true, js: true do
context 'milestone' do
it 'adds a milestone' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
page.within('.milestone') do
click_link 'Edit'
@@ -212,9 +188,7 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'removes a milestone' do
- page.within(first('.board')) do
- find('.card:nth-child(2)').click
- end
+ click_card(card)
page.within('.milestone') do
click_link 'Edit'
@@ -234,9 +208,7 @@ describe 'Issue Boards', feature: true, js: true do
context 'due date' do
it 'updates due date' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
page.within('.due_date') do
click_link 'Edit'
@@ -252,9 +224,7 @@ describe 'Issue Boards', feature: true, js: true do
context 'labels' do
it 'adds a single label' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
page.within('.labels') do
click_link 'Edit'
@@ -273,18 +243,12 @@ describe 'Issue Boards', feature: true, js: true do
end
end
- page.within(first('.board')) do
- page.within(first('.card')) do
- expect(page).to have_selector('.label', count: 2)
- expect(page).to have_content(bug.title)
- end
- end
+ expect(card).to have_selector('.label', count: 2)
+ expect(card).to have_content(bug.title)
end
it 'adds a multiple labels' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
page.within('.labels') do
click_link 'Edit'
@@ -305,19 +269,13 @@ describe 'Issue Boards', feature: true, js: true do
end
end
- page.within(first('.board')) do
- page.within(first('.card')) do
- expect(page).to have_selector('.label', count: 3)
- expect(page).to have_content(bug.title)
- expect(page).to have_content(regression.title)
- end
- end
+ expect(card).to have_selector('.label', count: 3)
+ expect(card).to have_content(bug.title)
+ expect(card).to have_content(regression.title)
end
it 'removes a label' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
page.within('.labels') do
click_link 'Edit'
@@ -336,20 +294,14 @@ describe 'Issue Boards', feature: true, js: true do
end
end
- page.within(first('.board')) do
- page.within(first('.card')) do
- expect(page).not_to have_selector('.label')
- expect(page).not_to have_content(stretch.title)
- end
- end
+ expect(card).not_to have_selector('.label')
+ expect(card).not_to have_content(stretch.title)
end
end
context 'subscription' do
it 'changes issue subscription' do
- page.within(first('.board')) do
- first('.card').click
- end
+ click_card(card)
page.within('.subscription') do
click_button 'Subscribe'
@@ -358,4 +310,19 @@ describe 'Issue Boards', feature: true, js: true do
end
end
end
+
+ def click_card(card)
+ page.within(card) do
+ first('.card-number').click
+ end
+
+ wait_for_sidebar
+ end
+
+ def wait_for_sidebar
+ # loop until the CSS transition is complete
+ Timeout.timeout(0.5) do
+ loop until evaluate_script('$(".right-sidebar").outerWidth()') == 290
+ end
+ end
end
diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb
new file mode 100644
index 00000000000..2db1cf71209
--- /dev/null
+++ b/spec/features/dashboard/issues_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+RSpec.describe 'Dashboard Issues', feature: true do
+ let(:current_user) { create :user }
+ let(:public_project) { create(:empty_project, :public) }
+ let(:project) do
+ create(:empty_project) do |project|
+ project.team << [current_user, :master]
+ end
+ end
+
+ let!(:authored_issue) { create :issue, author: current_user, project: project }
+ let!(:authored_issue_on_public_project) { create :issue, author: current_user, project: public_project }
+ let!(:assigned_issue) { create :issue, assignee: current_user, project: project }
+ let!(:other_issue) { create :issue, project: project }
+
+ before do
+ login_as(current_user)
+
+ visit issues_dashboard_path(assignee_id: current_user.id)
+ end
+
+ it 'shows issues assigned to current user' do
+ expect(page).to have_content(assigned_issue.title)
+ expect(page).not_to have_content(authored_issue.title)
+ expect(page).not_to have_content(other_issue.title)
+ end
+
+ it 'shows issues when current user is author', js: true do
+ find('#assignee_id', visible: false).set('')
+ find('.js-author-search', match: :first).click
+ find('.dropdown-menu-author li a', match: :first, text: current_user.to_reference).click
+
+ expect(page).to have_content(authored_issue.title)
+ expect(page).to have_content(authored_issue_on_public_project.title)
+ expect(page).not_to have_content(assigned_issue.title)
+ expect(page).not_to have_content(other_issue.title)
+ end
+
+ it 'shows all issues' do
+ click_link('Reset filters')
+
+ expect(page).to have_content(authored_issue.title)
+ expect(page).to have_content(authored_issue_on_public_project.title)
+ expect(page).to have_content(assigned_issue.title)
+ expect(page).to have_content(other_issue.title)
+ end
+end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 755162a1eb5..094f645a077 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -382,7 +382,9 @@ describe 'Issues', feature: true do
it 'changes incoming email address token', js: true do
find('.issue-email-modal-btn').click
previous_token = find('input#issue_email').value
- find('.incoming-email-token-reset').click
+ find('.incoming-email-token-reset').trigger('click')
+
+ wait_for_ajax
expect(page).to have_no_field('issue_email', with: previous_token)
new_token = project1.new_issue_address(@user.reload)
@@ -636,7 +638,7 @@ describe 'Issues', feature: true do
it 'removes due date from issue' do
date = Date.today.at_beginning_of_month + 2.days
-
+
page.within '.due_date' do
click_link 'Edit'
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 12ab1d6dde8..2a008427478 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -136,10 +136,10 @@ describe IssuesFinder do
end
end
- context 'filtering by issue iid' do
- let(:params) { { search: issue3.to_reference } }
+ context 'filtering by issues iids' do
+ let(:params) { { iids: issue3.iid } }
- it 'returns issue with iid match' do
+ it 'returns issues with iids match' do
expect(issues).to contain_exactly(issue3)
end
end
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 3dcd7781e5b..21ef94ac5d1 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -38,5 +38,13 @@ describe MergeRequestsFinder do
merge_requests = MergeRequestsFinder.new(user, params).execute
expect(merge_requests.size).to eq(3)
end
+
+ it 'filters by iid' do
+ params = { project_id: project1.id, iids: merge_request1.iid }
+
+ merge_requests = MergeRequestsFinder.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request1)
+ end
end
end
diff --git a/spec/javascripts/fixtures/project_title.html.haml b/spec/javascripts/fixtures/project_title.html.haml
deleted file mode 100644
index 9d1f7877116..00000000000
--- a/spec/javascripts/fixtures/project_title.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-.header-content
- %h1.title
- %a
- GitLab Org
- %a.project-item-select-holder{href: "/gitlab-org/gitlab-test"}
- GitLab Test
- %i.fa.chevron-down.dropdown-toggle-caret.js-projects-dropdown-toggle{ "data-toggle" => "dropdown", "data-target" => ".header-content", "data-order-by" => "last_activity_at" }
- .js-dropdown-menu-projects
- .dropdown-menu.dropdown-select.dropdown-menu-projects
- .dropdown-title
- %span Go to a project
- %button.dropdown-title-button.dropdown-menu-close{"aria-label" => "Close", type: "button"}
- %i.fa.fa-times.dropdown-menu-close-icon
- .dropdown-input
- %input.dropdown-input-field{id: "", placeholder: "Search your projects", type: "search", value: ""}
- %i.fa.fa-search.dropdown-input-search
- %i.fa.fa-times.dropdown-input-clear.js-dropdown-input-clear{role: "button"}
- .dropdown-content
- .dropdown-loading
- %i.fa.fa-spinner.fa-spin
diff --git a/spec/javascripts/gfm_auto_complete_spec.js.es6 b/spec/javascripts/gfm_auto_complete_spec.js.es6
index c61c32f8a13..5dfa4008fbd 100644
--- a/spec/javascripts/gfm_auto_complete_spec.js.es6
+++ b/spec/javascripts/gfm_auto_complete_spec.js.es6
@@ -1,3 +1,5 @@
+/* eslint no-param-reassign: "off" */
+
require('~/gfm_auto_complete');
require('vendor/jquery.caret');
require('vendor/jquery.atwho');
@@ -63,6 +65,61 @@ describe('GfmAutoComplete', function () {
});
});
+ describe('DefaultOptions.matcher', function () {
+ const defaultMatcher = (context, flag, subtext) => (
+ GfmAutoComplete.DefaultOptions.matcher.call(context, flag, subtext)
+ );
+
+ const flagsUseDefaultMatcher = ['@', '#', '!', '~', '%'];
+ const otherFlags = ['/', ':'];
+ const flags = flagsUseDefaultMatcher.concat(otherFlags);
+
+ const flagsHash = flags.reduce((hash, el) => { hash[el] = null; return hash; }, {});
+ const atwhoInstance = { setting: {}, app: { controllers: flagsHash } };
+
+ const minLen = 1;
+ const maxLen = 20;
+ const argumentSize = [minLen, maxLen / 2, maxLen];
+
+ const allowedSymbols = ['', 'a', 'n', 'z', 'A', 'Z', 'N', '0', '5', '9', 'А', 'а', 'Я', 'я', '.', '\'', '+', '-', '_'];
+ const jointAllowedSymbols = allowedSymbols.join('');
+
+ describe('should match regular symbols', () => {
+ flagsUseDefaultMatcher.forEach((flag) => {
+ allowedSymbols.forEach((symbol) => {
+ argumentSize.forEach((size) => {
+ const query = new Array(size + 1).join(symbol);
+ const subtext = flag + query;
+
+ it(`matches argument "${flag}" with query "${subtext}"`, () => {
+ expect(defaultMatcher(atwhoInstance, flag, subtext)).toBe(query);
+ });
+ });
+ });
+
+ it(`matches combination of allowed symbols for flag "${flag}"`, () => {
+ const subtext = flag + jointAllowedSymbols;
+
+ expect(defaultMatcher(atwhoInstance, flag, subtext)).toBe(jointAllowedSymbols);
+ });
+ });
+ });
+
+ describe('should not match special sequences', () => {
+ const ShouldNotBeFollowedBy = flags.concat(['\x00', '\x10', '\x3f', '\n', ' ']);
+
+ flagsUseDefaultMatcher.forEach((atSign) => {
+ ShouldNotBeFollowedBy.forEach((followedSymbol) => {
+ const seq = atSign + followedSymbol;
+
+ it(`should not match "${seq}"`, () => {
+ expect(defaultMatcher(atwhoInstance, atSign, seq)).toBe(null);
+ });
+ });
+ });
+ });
+ });
+
describe('isLoading', function () {
it('should be true with loading data object item', function () {
expect(GfmAutoComplete.isLoading({ name: 'loading' })).toBe(true);
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index 25cfa9e9479..34c98f5176a 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -6,9 +6,9 @@ require('~/merge_request');
(function() {
describe('MergeRequest', function() {
return describe('task lists', function() {
- preloadFixtures('static/merge_requests_show.html.raw');
+ preloadFixtures('merge_requests/merge_request_with_task_list.html.raw');
beforeEach(function() {
- loadFixtures('static/merge_requests_show.html.raw');
+ loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
return this.merge = new MergeRequest();
});
it('modifies the Markdown field', function() {
@@ -19,7 +19,7 @@ require('~/merge_request');
return it('submits an ajax request on tasklist:changed', function() {
spyOn(jQuery, 'ajax').and.callFake(function(req) {
expect(req.type).toBe('PATCH');
- expect(req.url).toBe('/foo');
+ expect(req.url).toBe(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`);
return expect(req.data.merge_request.description).not.toBe(null);
});
return $('.js-task-list-field').trigger('tasklist:changed');
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index af495787c54..b5b4f61e5d5 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -35,15 +35,13 @@ require('~/lib/utils/text_utility');
expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
});
- it('submits the form on tasklist:changed', function() {
- var submitted = false;
- $('form').on('submit', function(e) {
- submitted = true;
- e.preventDefault();
+ it('submits an ajax request on tasklist:changed', function() {
+ spyOn(jQuery, 'ajax').and.callFake(function(req) {
+ expect(req.type).toBe('PATCH');
+ expect(req.url).toBe('http://test.host/frontend-fixtures/issues-project/notes/1');
+ return expect(req.data.note).not.toBe(null);
});
-
$('.js-task-list-field').trigger('tasklist:changed');
- expect(submitted).toBe(true);
});
});
diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js
index bfe3d2df79d..6e72c3f8310 100644
--- a/spec/javascripts/project_title_spec.js
+++ b/spec/javascripts/project_title_spec.js
@@ -10,11 +10,11 @@ require('~/project');
(function() {
describe('Project Title', function() {
- preloadFixtures('static/project_title.html.raw');
+ preloadFixtures('issues/open-issue.html.raw');
loadJSONFixtures('projects.json');
beforeEach(function() {
- loadFixtures('static/project_title.html.raw');
+ loadFixtures('issues/open-issue.html.raw');
window.gon = {};
window.gon.api_version = 'v3';
@@ -38,15 +38,12 @@ require('~/project');
return spyOn(jQuery, 'ajax').and.callFake(fakeAjaxResponse.bind(_this));
};
})(this));
- it('to show on toggle click', (function(_this) {
- return function() {
- $('.js-projects-dropdown-toggle').click();
- return expect($('.header-content').hasClass('open')).toBe(true);
- };
- })(this));
- return it('hide dropdown', function() {
- $(".dropdown-menu-close-icon").click();
- return expect($('.header-content').hasClass('open')).toBe(false);
+ it('toggles dropdown', function() {
+ var menu = $('.js-dropdown-menu-projects');
+ $('.js-projects-dropdown-toggle').click();
+ expect(menu).toHaveClass('open');
+ menu.find('.dropdown-menu-close-icon').click();
+ expect(menu).not.toHaveClass('open');
});
});
diff --git a/spec/lib/gitlab/data_builder/build_spec.rb b/spec/lib/gitlab/data_builder/build_spec.rb
index 6c71e98066b..91c43f2bdc0 100644
--- a/spec/lib/gitlab/data_builder/build_spec.rb
+++ b/spec/lib/gitlab/data_builder/build_spec.rb
@@ -17,5 +17,31 @@ describe Gitlab::DataBuilder::Build do
it { expect(data[:build_allow_failure]).to eq(false) }
it { expect(data[:project_id]).to eq(build.project.id) }
it { expect(data[:project_name]).to eq(build.project.name_with_namespace) }
+
+ context 'commit author_url' do
+ context 'when no commit present' do
+ let(:build) { create(:ci_build) }
+
+ it 'sets to mailing address of git_author_email' do
+ expect(data[:commit][:author_url]).to eq("mailto:#{build.pipeline.git_author_email}")
+ end
+ end
+
+ context 'when commit present but has no author' do
+ let(:build) { create(:ci_build, :with_commit) }
+
+ it 'sets to mailing address of git_author_email' do
+ expect(data[:commit][:author_url]).to eq("mailto:#{build.pipeline.git_author_email}")
+ end
+ end
+
+ context 'when commit and author are present' do
+ let(:build) { create(:ci_build, :with_commit_and_author) }
+
+ it 'sets to GitLab user url' do
+ expect(data[:commit][:author_url]).to eq(Gitlab::Routing.url_helpers.user_url(username: build.commit.author.username))
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index 94a3b0fbba9..f4a21c24fa1 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -3,7 +3,7 @@ include ImportExport::CommonUtil
describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
describe 'restore project tree' do
- before(:all) do
+ before(:context) do
@user = create(:user)
RSpec::Mocks.with_temporary_scope do
@@ -15,10 +15,6 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
end
end
- after(:all) do
- @user.destroy!
- end
-
context 'JSON' do
it 'restores models based on JSON' do
expect(@restored_project_json).to be true
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 4080092405d..2dfca8bcfce 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1,6 +1,7 @@
require 'spec_helper'
describe Ci::Build, :models do
+ let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:test_trace) { 'This is a test' }
@@ -207,14 +208,16 @@ describe Ci::Build, :models do
end
it 'expects to have retried builds instead the original ones' do
- retried_rspec = Ci::Build.retry(rspec_test)
- expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, retried_rspec.id, rubocop_test.id)
+ project.add_developer(user)
+
+ retried_rspec = Ci::Build.retry(rspec_test, user)
+
+ expect(staging.depends_on_builds.map(&:id))
+ .to contain_exactly(build.id, retried_rspec.id, rubocop_test.id)
end
end
describe '#detailed_status' do
- let(:user) { create(:user) }
-
it 'returns a detailed status' do
expect(build.detailed_status(user))
.to be_a Gitlab::Ci::Status::Build::Cancelable
@@ -813,12 +816,16 @@ describe Ci::Build, :models do
subject { build.other_actions }
+ before do
+ project.add_developer(user)
+ end
+
it 'returns other actions' do
is_expected.to contain_exactly(other_build)
end
context 'when build is retried' do
- let!(:new_build) { Ci::Build.retry(build) }
+ let!(:new_build) { Ci::Build.retry(build, user) }
it 'does not return any of them' do
is_expected.not_to include(build, new_build)
@@ -826,7 +833,7 @@ describe Ci::Build, :models do
end
context 'when other build is retried' do
- let!(:retried_build) { Ci::Build.retry(other_build) }
+ let!(:retried_build) { Ci::Build.retry(other_build, user) }
it 'returns a retried build' do
is_expected.to contain_exactly(retried_build)
@@ -857,21 +864,29 @@ describe Ci::Build, :models do
describe '#play' do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
- subject { build.play }
+ before do
+ project.add_developer(user)
+ end
+
+ context 'when build is manual' do
+ it 'enqueues a build' do
+ new_build = build.play(user)
- it 'enqueues a build' do
- is_expected.to be_pending
- is_expected.to eq(build)
+ expect(new_build).to be_pending
+ expect(new_build).to eq(build)
+ end
end
- context 'for successful build' do
+ context 'when build is passed' do
before do
build.update(status: 'success')
end
it 'creates a new build' do
- is_expected.to be_pending
- is_expected.not_to eq(build)
+ new_build = build.play(user)
+
+ expect(new_build).to be_pending
+ expect(new_build).not_to eq(build)
end
end
end
@@ -1246,12 +1261,9 @@ describe Ci::Build, :models do
end
context 'when build has user' do
- let(:user) { create(:user, username: 'starter') }
let(:user_variables) do
- [
- { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true },
- { key: 'GITLAB_USER_EMAIL', value: user.email, public: true }
- ]
+ [ { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true },
+ { key: 'GITLAB_USER_EMAIL', value: user.email, public: true } ]
end
before do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 426be74cd02..10c2bfbb400 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -3,8 +3,12 @@ require 'spec_helper'
describe Ci::Pipeline, models: true do
include EmailHelpers
- let(:project) { FactoryGirl.create :empty_project }
- let(:pipeline) { FactoryGirl.create :ci_empty_pipeline, status: 'created', project: project }
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+
+ let(:pipeline) do
+ create(:ci_empty_pipeline, status: :created, project: project)
+ end
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:user) }
@@ -503,7 +507,9 @@ describe Ci::Pipeline, models: true do
end
describe '#status' do
- let!(:build) { create(:ci_build, :created, pipeline: pipeline, name: 'test') }
+ let(:build) do
+ create(:ci_build, :created, pipeline: pipeline, name: 'test')
+ end
subject { pipeline.reload.status }
@@ -545,13 +551,21 @@ describe Ci::Pipeline, models: true do
build.cancel
end
- it { is_expected.to eq('canceled') }
+ context 'when build is pending' do
+ let(:build) do
+ create(:ci_build, :pending, pipeline: pipeline)
+ end
+
+ it { is_expected.to eq('canceled') }
+ end
end
context 'on failure and build retry' do
before do
build.drop
- Ci::Build.retry(build)
+ project.add_developer(user)
+
+ Ci::Build.retry(build, user)
end
# We are changing a state: created > failed > running
@@ -563,8 +577,6 @@ describe Ci::Pipeline, models: true do
end
describe '#detailed_status' do
- let(:user) { create(:user) }
-
subject { pipeline.detailed_status(user) }
context 'when pipeline is created' do
@@ -720,7 +732,7 @@ describe Ci::Pipeline, models: true do
describe '#cancel_running' do
let(:latest_status) { pipeline.statuses.pluck(:status) }
- context 'when there is a running external job and created build' do
+ context 'when there is a running external job and a regular job' do
before do
create(:ci_build, :running, pipeline: pipeline)
create(:generic_commit_status, :running, pipeline: pipeline)
@@ -733,7 +745,7 @@ describe Ci::Pipeline, models: true do
end
end
- context 'when builds are in different stages' do
+ context 'when jobs are in different stages' do
before do
create(:ci_build, :running, stage_idx: 0, pipeline: pipeline)
create(:ci_build, :running, stage_idx: 1, pipeline: pipeline)
@@ -745,17 +757,34 @@ describe Ci::Pipeline, models: true do
expect(latest_status).to contain_exactly('canceled', 'canceled')
end
end
+
+ context 'when there are created builds present in the pipeline' do
+ before do
+ create(:ci_build, :running, stage_idx: 0, pipeline: pipeline)
+ create(:ci_build, :created, stage_idx: 1, pipeline: pipeline)
+
+ pipeline.cancel_running
+ end
+
+ it 'cancels created builds' do
+ expect(latest_status).to eq ['canceled', 'canceled']
+ end
+ end
end
describe '#retry_failed' do
let(:latest_status) { pipeline.statuses.latest.pluck(:status) }
+ before do
+ project.add_developer(user)
+ end
+
context 'when there is a failed build and failed external status' do
before do
create(:ci_build, :failed, name: 'build', pipeline: pipeline)
create(:generic_commit_status, :failed, name: 'jenkins', pipeline: pipeline)
- pipeline.retry_failed(create(:user))
+ pipeline.retry_failed(user)
end
it 'retries only build' do
@@ -768,11 +797,11 @@ describe Ci::Pipeline, models: true do
create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline)
create(:ci_build, :failed, name: 'jenkins', stage_idx: 1, pipeline: pipeline)
- pipeline.retry_failed(create(:user))
+ pipeline.retry_failed(user)
end
it 'retries both builds' do
- expect(latest_status).to contain_exactly('pending', 'pending')
+ expect(latest_status).to contain_exactly('pending', 'created')
end
end
@@ -781,11 +810,11 @@ describe Ci::Pipeline, models: true do
create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline)
create(:ci_build, :canceled, name: 'jenkins', stage_idx: 1, pipeline: pipeline)
- pipeline.retry_failed(create(:user))
+ pipeline.retry_failed(user)
end
it 'retries both builds' do
- expect(latest_status).to contain_exactly('pending', 'pending')
+ expect(latest_status).to contain_exactly('pending', 'created')
end
end
end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 3f32248e52b..f8513ac8b1c 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -290,7 +290,7 @@ describe Ci::Runner, models: true do
let!(:last_update) { runner.ensure_runner_queue_value }
before do
- runner.update(description: 'new runner')
+ Ci::UpdateRunnerService.new(runner).update(description: 'new runner')
end
it 'sets a new last_update value' do
@@ -318,6 +318,25 @@ describe Ci::Runner, models: true do
end
end
+ describe '#destroy' do
+ let(:runner) { create(:ci_runner) }
+
+ context 'when there is a tick in the queue' do
+ let!(:queue_key) { runner.send(:runner_queue_key) }
+
+ before do
+ runner.tick_runner_queue
+ runner.destroy
+ end
+
+ it 'cleans up the queue' do
+ Gitlab::Redis.with do |redis|
+ expect(redis.get(queue_key)).to be_nil
+ end
+ end
+ end
+ end
+
describe '.assignable_for' do
let(:runner) { create(:ci_runner) }
let(:project) { create(:empty_project) }
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index bf4394f7d5b..36533bdd11e 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe CommitStatus, models: true do
+describe CommitStatus, :models do
let(:project) { create(:project, :repository) }
let(:pipeline) do
@@ -127,7 +127,7 @@ describe CommitStatus, models: true do
end
describe '.latest' do
- subject { CommitStatus.latest.order(:id) }
+ subject { described_class.latest.order(:id) }
let(:statuses) do
[create_status(name: 'aa', ref: 'bb', status: 'running'),
@@ -143,7 +143,7 @@ describe CommitStatus, models: true do
end
describe '.running_or_pending' do
- subject { CommitStatus.running_or_pending.order(:id) }
+ subject { described_class.running_or_pending.order(:id) }
let(:statuses) do
[create_status(name: 'aa', ref: 'bb', status: 'running'),
@@ -159,7 +159,21 @@ describe CommitStatus, models: true do
end
describe '.exclude_ignored' do
- subject { CommitStatus.exclude_ignored.order(:id) }
+ subject { described_class.after_stage(0) }
+
+ let(:statuses) do
+ [create_status(name: 'aa', stage_idx: 0),
+ create_status(name: 'cc', stage_idx: 1),
+ create_status(name: 'aa', stage_idx: 2)]
+ end
+
+ it 'returns statuses from second and third stage' do
+ is_expected.to eq(statuses.values_at(1, 2))
+ end
+ end
+
+ describe '.exclude_ignored' do
+ subject { described_class.exclude_ignored.order(:id) }
let(:statuses) do
[create_status(when: 'manual', status: 'skipped'),
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 960f29f3805..f0ed0c679d5 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -155,7 +155,7 @@ describe Environment, models: true do
end
describe '#stop_with_action!' do
- let(:user) { create(:user) }
+ let(:user) { create(:admin) }
subject { environment.stop_with_action!(user) }
diff --git a/spec/models/project_services/chat_message/build_message_spec.rb b/spec/models/project_services/chat_message/build_message_spec.rb
index 50ad5013df9..3bd7ec18ae0 100644
--- a/spec/models/project_services/chat_message/build_message_spec.rb
+++ b/spec/models/project_services/chat_message/build_message_spec.rb
@@ -11,21 +11,28 @@ describe ChatMessage::BuildMessage do
project_name: 'project_name',
project_url: 'http://example.gitlab.com',
+ build_id: 1,
+ build_name: build_name,
+ build_stage: stage,
commit: {
status: status,
author_name: 'hacker',
+ author_url: 'http://example.gitlab.com/hacker',
duration: duration,
},
}
end
let(:message) { build_message }
+ let(:stage) { 'test' }
+ let(:status) { 'success' }
+ let(:build_name) { 'rspec' }
+ let(:duration) { 10 }
context 'build succeeded' do
let(:status) { 'success' }
let(:color) { 'good' }
- let(:duration) { 10 }
let(:message) { build_message('passed') }
it 'returns a message with information about succeeded build' do
@@ -38,7 +45,6 @@ describe ChatMessage::BuildMessage do
context 'build failed' do
let(:status) { 'failed' }
let(:color) { 'danger' }
- let(:duration) { 10 }
it 'returns a message with information about failed build' do
expect(subject.pretext).to be_empty
@@ -47,11 +53,25 @@ describe ChatMessage::BuildMessage do
end
end
- def build_message(status_text = status)
+ it 'returns a message with information on build' do
+ expect(subject.fallback).to include("on build <http://example.gitlab.com/builds/1|#{build_name}>")
+ end
+
+ it 'returns a message with stage name' do
+ expect(subject.fallback).to include("of stage #{stage}")
+ end
+
+ it 'returns a message with link to author' do
+ expect(subject.fallback).to include("by <http://example.gitlab.com/hacker|hacker>")
+ end
+
+ def build_message(status_text = status, stage_text = stage, build_text = build_name)
"<http://example.gitlab.com|project_name>:" \
" Commit <http://example.gitlab.com/commit/" \
"97de212e80737a608d939f648d959671fb0a0142/builds|97de212e>" \
" of <http://example.gitlab.com/commits/develop|develop> branch" \
- " by hacker #{status_text} in #{duration} #{'second'.pluralize(duration)}"
+ " by <http://example.gitlab.com/hacker|hacker> #{status_text}" \
+ " on build <http://example.gitlab.com/builds/1|#{build_text}>" \
+ " of stage #{stage_text} in #{duration} #{'second'.pluralize(duration)}"
end
end
diff --git a/spec/requests/api/access_requests_spec.rb b/spec/requests/api/access_requests_spec.rb
index e487297748b..919c98d6437 100644
--- a/spec/requests/api/access_requests_spec.rb
+++ b/spec/requests/api/access_requests_spec.rb
@@ -48,6 +48,7 @@ describe API::AccessRequests, api: true do
get api("/#{source_type.pluralize}/#{source.id}/access_requests", master)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
end
diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb
index c8e8f31cc1f..6cc1ef315db 100644
--- a/spec/requests/api/award_emoji_spec.rb
+++ b/spec/requests/api/award_emoji_spec.rb
@@ -34,6 +34,7 @@ describe API::AwardEmoji, api: true do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(downvote.name)
end
diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb
index c14c3cb1ce7..71df534ebe1 100644
--- a/spec/requests/api/boards_spec.rb
+++ b/spec/requests/api/boards_spec.rb
@@ -55,6 +55,7 @@ describe API::Boards, api: true do
get api(base_url, user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(board.id)
@@ -72,6 +73,7 @@ describe API::Boards, api: true do
get api(base_url, user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['label']['name']).to eq(dev_label.title)
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index 3e66236f6ae..2e6db0f43c6 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -17,8 +17,10 @@ describe API::Branches, api: true do
it "returns an array of project branches" do
project.repository.expire_all_method_caches
- get api("/projects/#{project.id}/repository/branches", user)
+ get api("/projects/#{project.id}/repository/branches", user), per_page: 100
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
branch_names = json_response.map { |x| x['name'] }
expect(branch_names).to match_array(project.repository.branch_names)
diff --git a/spec/requests/api/broadcast_messages_spec.rb b/spec/requests/api/broadcast_messages_spec.rb
index 7c9078b2864..921d8714173 100644
--- a/spec/requests/api/broadcast_messages_spec.rb
+++ b/spec/requests/api/broadcast_messages_spec.rb
@@ -25,6 +25,7 @@ describe API::BroadcastMessages, api: true do
get api('/broadcast_messages', admin)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_kind_of(Array)
expect(json_response.first.keys)
.to match_array(%w(id message starts_at ends_at color font active))
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index 834c4e52693..38aef7f2767 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -22,6 +22,7 @@ describe API::Builds, api: true do
context 'authorized user' do
it 'returns project builds' do
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
end
@@ -97,6 +98,7 @@ describe API::Builds, api: true do
it 'returns project jobs for specific commit' do
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq 2
end
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index eb53fd71872..5cba546bee2 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -5,12 +5,15 @@ describe API::CommitStatuses, api: true do
let!(:project) { create(:project, :repository) }
let(:commit) { project.repository.commit }
- let(:commit_status) { create(:commit_status, pipeline: pipeline) }
let(:guest) { create_user(:guest) }
let(:reporter) { create_user(:reporter) }
let(:developer) { create_user(:developer) }
let(:sha) { commit.id }
+ let(:commit_status) do
+ create(:commit_status, status: :pending, pipeline: pipeline)
+ end
+
describe "GET /projects/:id/repository/commits/:sha/statuses" do
let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" }
@@ -54,7 +57,7 @@ describe API::CommitStatuses, api: true do
it 'returns all commit statuses' do
expect(response).to have_http_status(200)
-
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(statuses_id).to contain_exactly(status1.id, status2.id,
status3.id, status4.id,
@@ -67,7 +70,7 @@ describe API::CommitStatuses, api: true do
it 'returns latest commit statuses for specific ref' do
expect(response).to have_http_status(200)
-
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(statuses_id).to contain_exactly(status3.id, status5.id)
end
@@ -78,7 +81,7 @@ describe API::CommitStatuses, api: true do
it 'return latest commit statuses for specific name' do
expect(response).to have_http_status(200)
-
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(statuses_id).to contain_exactly(status4.id, status5.id)
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 3eef10c0698..9fa007332f0 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -456,6 +456,7 @@ describe API::Commits, api: true do
it 'returns merge_request comments' do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['note']).to eq('a comment on a commit')
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index 766234d7104..67039bb037e 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -35,6 +35,7 @@ describe API::DeployKeys, api: true do
get api('/deploy_keys', admin)
expect(response.status).to eq(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['id']).to eq(deploy_keys_project.deploy_key.id)
end
@@ -48,6 +49,7 @@ describe API::DeployKeys, api: true do
get api("/projects/#{project.id}/deploy_keys", admin)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(deploy_key.title)
end
diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb
index 31e3cfa1b2f..5c4ce39f70c 100644
--- a/spec/requests/api/deployments_spec.rb
+++ b/spec/requests/api/deployments_spec.rb
@@ -22,6 +22,7 @@ describe API::Deployments, api: true do
get api("/projects/#{project.id}/deployments", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
expect(json_response.first['iid']).to eq(deployment.iid)
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
index 8168b613766..b0ee196666e 100644
--- a/spec/requests/api/environments_spec.rb
+++ b/spec/requests/api/environments_spec.rb
@@ -22,6 +22,7 @@ describe API::Environments, api: true do
get api("/projects/#{project.id}/environments", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
expect(json_response.first['name']).to eq(environment.name)
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index ccd7898586c..a59112579e5 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -33,6 +33,7 @@ describe API::Groups, api: true do
get api("/groups", user1)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response)
@@ -43,6 +44,7 @@ describe API::Groups, api: true do
get api("/groups", user1), statistics: true
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first).not_to include 'statistics'
end
@@ -53,6 +55,7 @@ describe API::Groups, api: true do
get api("/groups", admin)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
end
@@ -61,6 +64,7 @@ describe API::Groups, api: true do
get api("/groups", admin)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first).not_to include('statistics')
end
@@ -78,6 +82,7 @@ describe API::Groups, api: true do
get api("/groups", admin), statistics: true
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response)
.to satisfy_one { |group| group['statistics'] == attributes }
@@ -89,6 +94,7 @@ describe API::Groups, api: true do
get api("/groups", admin), skip_groups: [group2.id]
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
end
@@ -103,6 +109,7 @@ describe API::Groups, api: true do
get api("/groups", user1), all_available: true
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_groups).to contain_exactly(public_group.name, group1.name)
end
@@ -120,6 +127,7 @@ describe API::Groups, api: true do
get api("/groups", user1)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_groups).to eq([group3.name, group1.name])
end
@@ -128,6 +136,7 @@ describe API::Groups, api: true do
get api("/groups", user1), sort: "desc"
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_groups).to eq([group1.name, group3.name])
end
@@ -136,6 +145,7 @@ describe API::Groups, api: true do
get api("/groups", user1), order_by: "path"
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_groups).to eq([group1.name, group3.name])
end
@@ -156,6 +166,7 @@ describe API::Groups, api: true do
get api('/groups/owned', user2)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(group2.name)
end
@@ -290,6 +301,7 @@ describe API::Groups, api: true do
get api("/groups/#{group1.id}/projects", user1)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response.length).to eq(2)
project_names = json_response.map { |proj| proj['name' ] }
expect(project_names).to match_array([project1.name, project3.name])
@@ -300,6 +312,7 @@ describe API::Groups, api: true do
get api("/groups/#{group1.id}/projects", user1), simple: true
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response.length).to eq(2)
project_names = json_response.map { |proj| proj['name' ] }
expect(project_names).to match_array([project1.name, project3.name])
@@ -312,6 +325,7 @@ describe API::Groups, api: true do
get api("/groups/#{group1.id}/projects", user1), visibility: 'public'
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an(Array)
expect(json_response.length).to eq(1)
expect(json_response.first['name']).to eq(public_project.name)
@@ -335,6 +349,7 @@ describe API::Groups, api: true do
get api("/groups/#{group1.id}/projects", user3)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response.length).to eq(1)
expect(json_response.first['name']).to eq(project3.name)
end
@@ -365,6 +380,7 @@ describe API::Groups, api: true do
get api("/groups/#{group2.id}/projects", admin)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response.length).to eq(1)
expect(json_response.first['name']).to eq(project2.name)
end
@@ -381,6 +397,7 @@ describe API::Groups, api: true do
get api("/groups/#{group1.path}/projects", admin)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
project_names = json_response.map { |proj| proj['name' ] }
expect(project_names).to match_array([project1.name, project3.name])
end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index cca00df9591..74ac7955cb8 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -68,7 +68,9 @@ describe API::Issues, api: true do
context "when authenticated" do
it "returns an array of issues" do
get api("/issues", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(issue.title)
expect(json_response.last).to have_key('web_url')
@@ -76,7 +78,9 @@ describe API::Issues, api: true do
it 'returns an array of closed issues' do
get api('/issues?state=closed', user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(closed_issue.id)
@@ -84,7 +88,9 @@ describe API::Issues, api: true do
it 'returns an array of opened issues' do
get api('/issues?state=opened', user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(issue.id)
@@ -92,7 +98,9 @@ describe API::Issues, api: true do
it 'returns an array of all issues' do
get api('/issues?state=all', user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['id']).to eq(issue.id)
@@ -101,7 +109,9 @@ describe API::Issues, api: true do
it 'returns an array of labeled issues' do
get api("/issues?labels=#{label.title}", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['labels']).to eq([label.title])
@@ -111,6 +121,7 @@ describe API::Issues, api: true do
get api("/issues?labels=#{label.title},foo,bar", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['labels']).to eq([label.title])
@@ -118,14 +129,18 @@ describe API::Issues, api: true do
it 'returns an empty array if no issue matches labels' do
get api('/issues?labels=foo,bar', user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
it 'returns an array of labeled issues matching given state' do
get api("/issues?labels=#{label.title}&state=opened", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['labels']).to eq([label.title])
@@ -134,7 +149,9 @@ describe API::Issues, api: true do
it 'returns an empty array if no issue matches labels and state filters' do
get api("/issues?labels=#{label.title}&state=closed", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -143,6 +160,7 @@ describe API::Issues, api: true do
get api("/issues?milestone=#{empty_milestone.title}", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -151,6 +169,7 @@ describe API::Issues, api: true do
get api("/issues?milestone=foo", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -159,6 +178,7 @@ describe API::Issues, api: true do
get api("/issues?milestone=#{milestone.title}", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['id']).to eq(issue.id)
@@ -170,6 +190,7 @@ describe API::Issues, api: true do
'&state=closed', user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(closed_issue.id)
@@ -179,6 +200,7 @@ describe API::Issues, api: true do
get api("/issues?milestone=#{no_milestone_title}", author)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(confidential_issue.id)
@@ -186,36 +208,40 @@ describe API::Issues, api: true do
it 'sorts by created_at descending by default' do
get api('/issues', user)
- response_dates = json_response.map { |issue| issue['created_at'] }
+ response_dates = json_response.map { |issue| issue['created_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort.reverse)
end
it 'sorts ascending when requested' do
get api('/issues?sort=asc', user)
- response_dates = json_response.map { |issue| issue['created_at'] }
+ response_dates = json_response.map { |issue| issue['created_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort)
end
it 'sorts by updated_at descending when requested' do
get api('/issues?order_by=updated_at', user)
- response_dates = json_response.map { |issue| issue['updated_at'] }
+ response_dates = json_response.map { |issue| issue['updated_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort.reverse)
end
it 'sorts by updated_at ascending when requested' do
get api('/issues?order_by=updated_at&sort=asc', user)
- response_dates = json_response.map { |issue| issue['updated_at'] }
+ response_dates = json_response.map { |issue| issue['updated_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort)
end
@@ -269,6 +295,7 @@ describe API::Issues, api: true do
get api(base_url, non_member)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['title']).to eq(group_issue.title)
@@ -278,6 +305,7 @@ describe API::Issues, api: true do
get api(base_url, author)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
end
@@ -286,6 +314,7 @@ describe API::Issues, api: true do
get api(base_url, assignee)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
end
@@ -294,6 +323,7 @@ describe API::Issues, api: true do
get api(base_url, user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
end
@@ -302,6 +332,7 @@ describe API::Issues, api: true do
get api(base_url, admin)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
end
@@ -310,6 +341,7 @@ describe API::Issues, api: true do
get api("#{base_url}?labels=#{group_label.title}", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['labels']).to eq([group_label.title])
@@ -319,6 +351,7 @@ describe API::Issues, api: true do
get api("#{base_url}?labels=#{group_label.title},foo,bar", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -327,6 +360,7 @@ describe API::Issues, api: true do
get api("#{base_url}?labels=foo,bar", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -335,6 +369,7 @@ describe API::Issues, api: true do
get api("#{base_url}?milestone=#{group_empty_milestone.title}", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -343,6 +378,7 @@ describe API::Issues, api: true do
get api("#{base_url}?milestone=foo", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -351,6 +387,7 @@ describe API::Issues, api: true do
get api("#{base_url}?milestone=#{group_milestone.title}", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(group_issue.id)
@@ -361,6 +398,7 @@ describe API::Issues, api: true do
'&state=closed', user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(group_closed_issue.id)
@@ -370,6 +408,7 @@ describe API::Issues, api: true do
get api("#{base_url}?milestone=#{no_milestone_title}", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(group_confidential_issue.id)
@@ -377,36 +416,40 @@ describe API::Issues, api: true do
it 'sorts by created_at descending by default' do
get api(base_url, user)
- response_dates = json_response.map { |issue| issue['created_at'] }
+ response_dates = json_response.map { |issue| issue['created_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort.reverse)
end
it 'sorts ascending when requested' do
get api("#{base_url}?sort=asc", user)
- response_dates = json_response.map { |issue| issue['created_at'] }
+ response_dates = json_response.map { |issue| issue['created_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort)
end
it 'sorts by updated_at descending when requested' do
get api("#{base_url}?order_by=updated_at", user)
- response_dates = json_response.map { |issue| issue['updated_at'] }
+ response_dates = json_response.map { |issue| issue['updated_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort.reverse)
end
it 'sorts by updated_at ascending when requested' do
get api("#{base_url}?order_by=updated_at&sort=asc", user)
- response_dates = json_response.map { |issue| issue['updated_at'] }
+ response_dates = json_response.map { |issue| issue['updated_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort)
end
@@ -430,12 +473,17 @@ describe API::Issues, api: true do
get api("/projects/#{restricted_project.id}/issues", non_member)
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response).to eq([])
end
it 'returns project issues without confidential issues for non project members' do
get api("#{base_url}/issues", non_member)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['title']).to eq(issue.title)
@@ -443,7 +491,9 @@ describe API::Issues, api: true do
it 'returns project issues without confidential issues for project members with guest role' do
get api("#{base_url}/issues", guest)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['title']).to eq(issue.title)
@@ -451,7 +501,9 @@ describe API::Issues, api: true do
it 'returns project confidential issues for author' do
get api("#{base_url}/issues", author)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
expect(json_response.first['title']).to eq(issue.title)
@@ -459,7 +511,9 @@ describe API::Issues, api: true do
it 'returns project confidential issues for assignee' do
get api("#{base_url}/issues", assignee)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
expect(json_response.first['title']).to eq(issue.title)
@@ -467,7 +521,9 @@ describe API::Issues, api: true do
it 'returns project issues with confidential issues for project members' do
get api("#{base_url}/issues", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
expect(json_response.first['title']).to eq(issue.title)
@@ -475,7 +531,9 @@ describe API::Issues, api: true do
it 'returns project confidential issues for admin' do
get api("#{base_url}/issues", admin)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
expect(json_response.first['title']).to eq(issue.title)
@@ -483,7 +541,9 @@ describe API::Issues, api: true do
it 'returns an array of labeled project issues' do
get api("#{base_url}/issues?labels=#{label.title}", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['labels']).to eq([label.title])
@@ -493,6 +553,7 @@ describe API::Issues, api: true do
get api("#{base_url}/issues?labels=#{label.title},foo,bar", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['labels']).to eq([label.title])
@@ -500,21 +561,27 @@ describe API::Issues, api: true do
it 'returns an empty array if no project issue matches labels' do
get api("#{base_url}/issues?labels=foo,bar", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
it 'returns an empty array if no issue matches milestone' do
get api("#{base_url}/issues?milestone=#{empty_milestone.title}", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
it 'returns an empty array if milestone does not exist' do
get api("#{base_url}/issues?milestone=foo", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -523,6 +590,7 @@ describe API::Issues, api: true do
get api("#{base_url}/issues?milestone=#{milestone.title}", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['id']).to eq(issue.id)
@@ -530,9 +598,10 @@ describe API::Issues, api: true do
end
it 'returns an array of issues matching state in milestone' do
- get api("#{base_url}/issues?milestone=#{milestone.title}"\
- '&state=closed', user)
+ get api("#{base_url}/issues?milestone=#{milestone.title}&state=closed", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(closed_issue.id)
@@ -542,6 +611,7 @@ describe API::Issues, api: true do
get api("#{base_url}/issues?milestone=#{no_milestone_title}", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(confidential_issue.id)
@@ -549,36 +619,40 @@ describe API::Issues, api: true do
it 'sorts by created_at descending by default' do
get api("#{base_url}/issues", user)
- response_dates = json_response.map { |issue| issue['created_at'] }
+ response_dates = json_response.map { |issue| issue['created_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort.reverse)
end
it 'sorts ascending when requested' do
get api("#{base_url}/issues?sort=asc", user)
- response_dates = json_response.map { |issue| issue['created_at'] }
+ response_dates = json_response.map { |issue| issue['created_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort)
end
it 'sorts by updated_at descending when requested' do
get api("#{base_url}/issues?order_by=updated_at", user)
- response_dates = json_response.map { |issue| issue['updated_at'] }
+ response_dates = json_response.map { |issue| issue['updated_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort.reverse)
end
it 'sorts by updated_at ascending when requested' do
get api("#{base_url}/issues?order_by=updated_at&sort=asc", user)
- response_dates = json_response.map { |issue| issue['updated_at'] }
+ response_dates = json_response.map { |issue| issue['updated_at'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort)
end
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index a8cd787f398..5d7a76cf3be 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -30,6 +30,7 @@ describe API::Labels, api: true do
get api("/projects/#{project.id}/labels", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(3)
expect(json_response.first.keys).to match_array expected_keys
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 3e9bcfd1a60..31166b50033 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -34,9 +34,12 @@ describe API::Members, api: true do
context "when authenticated as a #{type}" do
it 'returns 200' do
user = public_send(type)
+
get api("/#{source_type.pluralize}/#{source.id}/members", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response.size).to eq(2)
expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
end
@@ -49,6 +52,8 @@ describe API::Members, api: true do
get api("/#{source_type.pluralize}/#{source.id}/members", developer)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response.size).to eq(2)
expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
end
@@ -57,6 +62,8 @@ describe API::Members, api: true do
get api("/#{source_type.pluralize}/#{source.id}/members", developer), query: master.username
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response.count).to eq(1)
expect(json_response.first['username']).to eq(master.username)
end
diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb
index e1887138aab..1d02e827183 100644
--- a/spec/requests/api/merge_request_diffs_spec.rb
+++ b/spec/requests/api/merge_request_diffs_spec.rb
@@ -19,6 +19,8 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do
merge_request_diff = merge_request.merge_request_diffs.first
expect(response.status).to eq 200
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response.size).to eq(merge_request.merge_request_diffs.size)
expect(json_response.first['id']).to eq(merge_request_diff.id)
expect(json_response.first['head_commit_sha']).to eq(merge_request_diff.head_commit_sha)
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index ff10e79e417..f4dee4a4ca1 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -27,7 +27,9 @@ describe API::MergeRequests, api: true do
context "when authenticated" do
it "returns an array of all merge_requests" do
get api("/projects/#{project.id}/merge_requests", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
expect(json_response.last['title']).to eq(merge_request.title)
@@ -43,7 +45,9 @@ describe API::MergeRequests, api: true do
it "returns an array of all merge_requests" do
get api("/projects/#{project.id}/merge_requests?state", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
expect(json_response.last['title']).to eq(merge_request.title)
@@ -51,7 +55,9 @@ describe API::MergeRequests, api: true do
it "returns an array of open merge_requests" do
get api("/projects/#{project.id}/merge_requests?state=opened", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.last['title']).to eq(merge_request.title)
@@ -59,7 +65,9 @@ describe API::MergeRequests, api: true do
it "returns an array of closed merge_requests" do
get api("/projects/#{project.id}/merge_requests?state=closed", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['title']).to eq(merge_request_closed.title)
@@ -67,7 +75,9 @@ describe API::MergeRequests, api: true do
it "returns an array of merged merge_requests" do
get api("/projects/#{project.id}/merge_requests?state=merged", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['title']).to eq(merge_request_merged.title)
@@ -91,7 +101,9 @@ describe API::MergeRequests, api: true do
it "returns an array of merge_requests in ascending order" do
get api("/projects/#{project.id}/merge_requests?sort=asc", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
@@ -100,7 +112,9 @@ describe API::MergeRequests, api: true do
it "returns an array of merge_requests in descending order" do
get api("/projects/#{project.id}/merge_requests?sort=desc", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
@@ -109,7 +123,9 @@ describe API::MergeRequests, api: true do
it "returns an array of merge_requests ordered by updated_at" do
get api("/projects/#{project.id}/merge_requests?order_by=updated_at", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
response_dates = json_response.map{ |merge_request| merge_request['updated_at'] }
@@ -118,7 +134,9 @@ describe API::MergeRequests, api: true do
it "returns an array of merge_requests ordered by created_at" do
get api("/projects/#{project.id}/merge_requests?order_by=created_at&sort=asc", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
@@ -191,6 +209,8 @@ describe API::MergeRequests, api: true do
commit = merge_request.commits.first
expect(response.status).to eq 200
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response.size).to eq(merge_request.commits.size)
expect(json_response.first['id']).to eq(commit.id)
expect(json_response.first['title']).to eq(commit.title)
@@ -205,6 +225,7 @@ describe API::MergeRequests, api: true do
describe 'GET /projects/:id/merge_requests/:merge_request_id/changes' do
it 'returns the change information of the merge_request' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/changes", user)
+
expect(response.status).to eq 200
expect(json_response['changes'].size).to eq(merge_request.diffs.size)
end
@@ -572,7 +593,9 @@ describe API::MergeRequests, api: true do
it "returns merge_request comments ordered by created_at" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['note']).to eq("a comment on a MR")
@@ -594,7 +617,9 @@ describe API::MergeRequests, api: true do
end
get api("/projects/#{project.id}/merge_requests/#{mr.id}/closes_issues", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(issue.id)
@@ -602,7 +627,9 @@ describe API::MergeRequests, api: true do
it 'returns an empty array when there are no issues to be closed' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -616,6 +643,7 @@ describe API::MergeRequests, api: true do
get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.id}/closes_issues", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['title']).to eq(issue.title)
diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb
index 8beef821d6c..418bf5a507c 100644
--- a/spec/requests/api/milestones_spec.rb
+++ b/spec/requests/api/milestones_spec.rb
@@ -14,6 +14,7 @@ describe API::Milestones, api: true do
get api("/projects/#{project.id}/milestones", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(milestone.title)
end
@@ -28,6 +29,7 @@ describe API::Milestones, api: true do
get api("/projects/#{project.id}/milestones?state=active", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(milestone.id)
@@ -37,25 +39,18 @@ describe API::Milestones, api: true do
get api("/projects/#{project.id}/milestones?state=closed", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(closed_milestone.id)
end
- end
-
- describe 'GET /projects/:id/milestones/:milestone_id' do
- it 'returns a project milestone by id' do
- get api("/projects/#{project.id}/milestones/#{milestone.id}", user)
-
- expect(response).to have_http_status(200)
- expect(json_response['title']).to eq(milestone.title)
- expect(json_response['iid']).to eq(milestone.iid)
- end
it 'returns a project milestone by iid' do
get api("/projects/#{project.id}/milestones?iid=#{closed_milestone.iid}", user)
expect(response.status).to eq 200
+ expect(response).to include_pagination_headers
+ expect(json_response.size).to eq(1)
expect(json_response.size).to eq(1)
expect(json_response.first['title']).to eq closed_milestone.title
expect(json_response.first['id']).to eq closed_milestone.id
@@ -70,6 +65,26 @@ describe API::Milestones, api: true do
expect(json_response.first['id']).to eq milestone.id
end
+ it 'returns a project milestone by iid array' do
+ get api("/projects/#{project.id}/milestones", user), iid: [milestone.iid, closed_milestone.iid]
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response.size).to eq(2)
+ expect(json_response.first['title']).to eq milestone.title
+ expect(json_response.first['id']).to eq milestone.id
+ end
+ end
+
+ describe 'GET /projects/:id/milestones/:milestone_id' do
+ it 'returns a project milestone by id' do
+ get api("/projects/#{project.id}/milestones/#{milestone.id}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['title']).to eq(milestone.title)
+ expect(json_response['iid']).to eq(milestone.iid)
+ end
+
it 'returns 401 error if user not authenticated' do
get api("/projects/#{project.id}/milestones/#{milestone.id}")
@@ -177,6 +192,7 @@ describe API::Milestones, api: true do
get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['milestone']['title']).to eq(milestone.title)
end
@@ -202,6 +218,7 @@ describe API::Milestones, api: true do
get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(2)
expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id)
@@ -214,6 +231,7 @@ describe API::Milestones, api: true do
get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", member)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
expect(json_response.map { |issue| issue['id'] }).to include(issue.id)
@@ -223,10 +241,47 @@ describe API::Milestones, api: true do
get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user))
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
expect(json_response.map { |issue| issue['id'] }).to include(issue.id)
end
end
end
+
+ describe 'GET /projects/:id/milestones/:milestone_id/merge_requests' do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ before do
+ milestone.merge_requests << merge_request
+ end
+
+ it 'returns project merge_requests for a particular milestone' do
+ get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['title']).to eq(merge_request.title)
+ expect(json_response.first['milestone']['title']).to eq(milestone.title)
+ end
+
+ it 'returns a 404 error if milestone id not found' do
+ get api("/projects/#{project.id}/milestones/1234/merge_requests", user)
+
+ expect(response).to have_http_status(404)
+ end
+
+ it 'returns a 404 if the user has no access to the milestone' do
+ new_user = create :user
+ get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", new_user)
+
+ expect(response).to have_http_status(404)
+ end
+
+ it 'returns a 401 error if user not authenticated' do
+ get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests")
+
+ expect(response).to have_http_status(401)
+ end
+ end
end
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index a945d56f529..da8fa06d0af 100644
--- a/spec/requests/api/namespaces_spec.rb
+++ b/spec/requests/api/namespaces_spec.rb
@@ -18,17 +18,19 @@ describe API::Namespaces, api: true do
context "when authenticated as admin" do
it "admin: returns an array of all namespaces" do
get api("/namespaces", admin)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
-
expect(json_response.length).to eq(Namespace.count)
end
it "admin: returns an array of matched namespaces" do
get api("/namespaces?search=#{group2.name}", admin)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
-
expect(json_response.length).to eq(1)
expect(json_response.last['path']).to eq(group2.path)
expect(json_response.last['full_path']).to eq(group2.full_path)
@@ -38,17 +40,19 @@ describe API::Namespaces, api: true do
context "when authenticated as a regular user" do
it "user: returns an array of namespaces" do
get api("/namespaces", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
-
expect(json_response.length).to eq(1)
end
it "admin: returns an array of matched namespaces" do
get api("/namespaces?search=#{user.username}", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
-
expect(json_response.length).to eq(1)
end
end
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 0353ebea9e5..0d3040519bc 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -41,6 +41,7 @@ describe API::Notes, api: true do
get api("/projects/#{project.id}/issues/#{issue.id}/notes", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['body']).to eq(issue_note.note)
end
@@ -56,6 +57,7 @@ describe API::Notes, api: true do
get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response).to be_empty
end
@@ -75,6 +77,7 @@ describe API::Notes, api: true do
get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", private_user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['body']).to eq(cross_reference_note.note)
end
@@ -87,6 +90,7 @@ describe API::Notes, api: true do
get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['body']).to eq(snippet_note.note)
end
@@ -109,6 +113,7 @@ describe API::Notes, api: true do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/notes", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['body']).to eq(merge_request_note.note)
end
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index f4973d71088..20c76bd2c05 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -25,6 +25,7 @@ describe API::ProjectHooks, 'ProjectHooks', api: true do
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
+ expect(response).to include_pagination_headers
expect(json_response.count).to eq(1)
expect(json_response.first['url']).to eq("http://example.com")
expect(json_response.first['issues_events']).to eq(true)
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index eea76c7bb94..f56876bcf54 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -16,9 +16,11 @@ describe API::ProjectSnippets, api: true do
internal_snippet = create(:project_snippet, :internal, project: project)
private_snippet = create(:project_snippet, :private, project: project)
- get api("/projects/#{project.id}/snippets/", user)
+ get api("/projects/#{project.id}/snippets", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response.size).to eq(3)
expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id)
expect(json_response.last).to have_key('web_url')
@@ -28,7 +30,10 @@ describe API::ProjectSnippets, api: true do
create(:project_snippet, :private, project: project)
get api("/projects/#{project.id}/snippets/", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response.size).to eq(0)
end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 741815a780e..db70b63917e 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -76,6 +76,7 @@ describe API::Projects, api: true do
get api('/projects', user)
expect(response.status).to eq 200
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first.keys).to include('tag_list')
end
@@ -84,6 +85,7 @@ describe API::Projects, api: true do
get api('/projects', user)
expect(response.status).to eq 200
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first.keys).to include('open_issues_count')
end
@@ -94,6 +96,7 @@ describe API::Projects, api: true do
get api('/projects', user)
expect(response.status).to eq 200
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.find { |hash| hash['id'] == project.id }.keys).not_to include('open_issues_count')
end
@@ -102,6 +105,7 @@ describe API::Projects, api: true do
get api('/projects', user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first).not_to include('statistics')
end
@@ -110,6 +114,7 @@ describe API::Projects, api: true do
get api('/projects', user), statistics: true
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first).to include 'statistics'
end
@@ -121,6 +126,7 @@ describe API::Projects, api: true do
get api('/projects?simple=true', user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first.keys).to match_array expected_keys
end
@@ -131,6 +137,7 @@ describe API::Projects, api: true do
get api('/projects', user), { search: project.name }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
end
@@ -141,6 +148,7 @@ describe API::Projects, api: true do
get api('/projects', user), { visibility: 'private' }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.map { |p| p['id'] }).to contain_exactly(project.id, project2.id, project3.id)
end
@@ -151,6 +159,7 @@ describe API::Projects, api: true do
get api('/projects', user), { visibility: 'internal' }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.map { |p| p['id'] }).to contain_exactly(project2.id)
end
@@ -159,6 +168,7 @@ describe API::Projects, api: true do
get api('/projects', user), { visibility: 'public' }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.map { |p| p['id'] }).to contain_exactly(public_project.id)
end
@@ -169,6 +179,7 @@ describe API::Projects, api: true do
get api('/projects', user), { order_by: 'id', sort: 'desc' }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['id']).to eq(project3.id)
end
@@ -179,6 +190,7 @@ describe API::Projects, api: true do
get api('/projects', user4), owned: true
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(project4.name)
expect(json_response.first['owner']['username']).to eq(user4.username)
@@ -197,6 +209,7 @@ describe API::Projects, api: true do
get api('/projects', user3), starred: true
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id)
end
@@ -223,6 +236,7 @@ describe API::Projects, api: true do
get api('/projects', user), { visibility: 'public', owned: true, starred: true, search: 'gitlab' }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
expect(json_response.first['id']).to eq(project5.id)
@@ -644,9 +658,10 @@ describe API::Projects, api: true do
get api("/projects/#{project.id}/events", current_user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
first_event = json_response.first
-
expect(first_event['action_name']).to eq('commented on')
expect(first_event['note']['body']).to eq('What an awesome day!')
@@ -699,11 +714,11 @@ describe API::Projects, api: true do
get api("/projects/#{project.id}/users", current_user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
first_user = json_response.first
-
expect(first_user['username']).to eq(member.username)
expect(first_user['name']).to eq(member.name)
expect(first_user.keys).to contain_exactly(*%w[name username id state avatar_url web_url])
@@ -746,7 +761,9 @@ describe API::Projects, api: true do
it 'returns an array of project snippets' do
get api("/projects/#{project.id}/snippets", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(snippet.title)
end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index c61208e395c..7652606a491 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -19,10 +19,10 @@ describe API::Repositories, api: true do
get api(route, current_user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
first_commit = json_response.first
-
- expect(json_response).to be_an Array
expect(first_commit['name']).to eq('bar')
expect(first_commit['type']).to eq('tree')
expect(first_commit['mode']).to eq('040000')
@@ -49,6 +49,7 @@ describe API::Repositories, api: true do
expect(response.status).to eq(200)
expect(json_response).to be_an Array
+ expect(response).to include_pagination_headers
expect(json_response[4]['name']).to eq('html')
expect(json_response[4]['path']).to eq('files/html')
expect(json_response[4]['type']).to eq('tree')
@@ -380,10 +381,10 @@ describe API::Repositories, api: true do
get api(route, current_user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
first_contributor = json_response.first
-
expect(first_contributor['email']).to eq('tiagonbotelho@hotmail.com')
expect(first_contributor['name']).to eq('tiagonbotelho')
expect(first_contributor['commits']).to eq(1)
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
index f2d81a28cb8..103d6755888 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/runners_spec.rb
@@ -37,18 +37,20 @@ describe API::Runners, api: true do
context 'authorized user' do
it 'returns user available runners' do
get api('/runners', user)
- shared = json_response.any?{ |r| r['is_shared'] }
+ shared = json_response.any?{ |r| r['is_shared'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(shared).to be_falsey
end
it 'filters runners by scope' do
get api('/runners?scope=active', user)
- shared = json_response.any?{ |r| r['is_shared'] }
+ shared = json_response.any?{ |r| r['is_shared'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(shared).to be_falsey
end
@@ -73,9 +75,10 @@ describe API::Runners, api: true do
context 'with admin privileges' do
it 'returns all runners' do
get api('/runners/all', admin)
- shared = json_response.any?{ |r| r['is_shared'] }
+ shared = json_response.any?{ |r| r['is_shared'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(shared).to be_truthy
end
@@ -91,9 +94,10 @@ describe API::Runners, api: true do
it 'filters runners by scope' do
get api('/runners/all?scope=specific', admin)
- shared = json_response.any?{ |r| r['is_shared'] }
+ shared = json_response.any?{ |r| r['is_shared'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(shared).to be_falsey
end
@@ -183,6 +187,7 @@ describe API::Runners, api: true do
it 'updates runner' do
description = shared_runner.description
active = shared_runner.active
+ runner_queue_value = shared_runner.ensure_runner_queue_value
update_runner(shared_runner.id, admin, description: "#{description}_updated",
active: !active,
@@ -197,18 +202,24 @@ describe API::Runners, api: true do
expect(shared_runner.tag_list).to include('ruby2.1', 'pgsql', 'mysql')
expect(shared_runner.run_untagged?).to be(false)
expect(shared_runner.locked?).to be(true)
+ expect(shared_runner.ensure_runner_queue_value)
+ .not_to eq(runner_queue_value)
end
end
context 'when runner is not shared' do
it 'updates runner' do
description = specific_runner.description
+ runner_queue_value = specific_runner.ensure_runner_queue_value
+
update_runner(specific_runner.id, admin, description: 'test')
specific_runner.reload
expect(response).to have_http_status(200)
expect(specific_runner.description).to eq('test')
expect(specific_runner.description).not_to eq(description)
+ expect(specific_runner.ensure_runner_queue_value)
+ .not_to eq(runner_queue_value)
end
end
@@ -335,9 +346,10 @@ describe API::Runners, api: true do
context 'authorized user with master privileges' do
it "returns project's runners" do
get api("/projects/#{project.id}/runners", user)
- shared = json_response.any?{ |r| r['is_shared'] }
+ shared = json_response.any?{ |r| r['is_shared'] }
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(shared).to be_truthy
end
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index 6b9a739b439..1ef92930b3c 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -13,6 +13,8 @@ describe API::Snippets, api: true do
get api("/snippets/", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly(
public_snippet.id,
internal_snippet.id,
@@ -25,7 +27,10 @@ describe API::Snippets, api: true do
create(:personal_snippet, :private)
get api("/snippets/", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response.size).to eq(0)
end
end
@@ -43,6 +48,8 @@ describe API::Snippets, api: true do
get api("/snippets/public", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly(
public_snippet.id,
public_snippet_other.id)
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index b3e5afdadb1..b59da632c00 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -31,6 +31,7 @@ describe API::SystemHooks, api: true do
get api("/hooks", admin)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['url']).to eq(hook.url)
expect(json_response.first['push_events']).to be true
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index 898d2b27e5c..8a4f078182f 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -20,10 +20,9 @@ describe API::Tags, api: true do
get api("/projects/#{project.id}/repository/tags", current_user)
expect(response).to have_http_status(200)
-
- first_tag = json_response.first
-
- expect(first_tag['name']).to eq(tag_name)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(tag_name)
end
end
@@ -43,7 +42,9 @@ describe API::Tags, api: true do
context 'without releases' do
it "returns an array of project tags" do
get api("/projects/#{project.id}/repository/tags", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(tag_name)
end
@@ -59,6 +60,7 @@ describe API::Tags, api: true do
get api("/projects/#{project.id}/repository/tags", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(tag_name)
expect(json_response.first['message']).to eq('Version 1.1.0')
diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb
index c0a8c0832bb..8506e8fccde 100644
--- a/spec/requests/api/templates_spec.rb
+++ b/spec/requests/api/templates_spec.rb
@@ -22,6 +22,7 @@ describe API::Templates, api: true do
get api('/templates/gitignores')
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to be > 15
end
@@ -32,6 +33,7 @@ describe API::Templates, api: true do
get api('/templates/gitlab_ci_ymls')
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['name']).not_to be_nil
end
@@ -69,6 +71,7 @@ describe API::Templates, api: true do
get api('/templates/licenses')
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(15)
expect(json_response.map { |l| l['key'] }).to include('agpl-3.0')
@@ -80,6 +83,7 @@ describe API::Templates, api: true do
get api('/templates/licenses?popular=1')
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(3)
expect(json_response.map { |l| l['key'] }).to include('apache-2.0')
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index 56dc017ce54..2069d2a7c75 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -33,6 +33,7 @@ describe API::Todos, api: true do
get api('/todos', john_doe)
expect(response.status).to eq(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
expect(json_response[0]['id']).to eq(pending_3.id)
@@ -52,6 +53,7 @@ describe API::Todos, api: true do
get api('/todos', john_doe), { author_id: author_2.id }
expect(response.status).to eq(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
end
@@ -64,6 +66,7 @@ describe API::Todos, api: true do
get api('/todos', john_doe), { type: 'MergeRequest' }
expect(response.status).to eq(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
end
@@ -74,6 +77,7 @@ describe API::Todos, api: true do
get api('/todos', john_doe), { state: 'done' }
expect(response.status).to eq(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
end
@@ -84,6 +88,7 @@ describe API::Todos, api: true do
get api('/todos', john_doe), { project_id: project_2.id }
expect(response.status).to eq(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
end
@@ -94,6 +99,7 @@ describe API::Todos, api: true do
get api('/todos', john_doe), { action: 'mentioned' }
expect(response.status).to eq(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
end
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index 84104aa66ee..92dfc2aa277 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -100,6 +100,7 @@ describe API::Triggers do
get api("/projects/#{project.id}/triggers", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_a(Array)
expect(json_response[0]).to have_key('token')
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 5958012672e..7ece22f1934 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -40,7 +40,9 @@ describe API::Users, api: true do
it "returns an array of users" do
get api("/users", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
username = user.username
expect(json_response.detect do |user|
@@ -55,13 +57,16 @@ describe API::Users, api: true do
get api("/users?blocked=true", user)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response).to all(include('state' => /(blocked|ldap_blocked)/))
end
it "returns one user" do
get api("/users?username=#{omniauth_user.username}", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['username']).to eq(omniauth_user.username)
end
@@ -70,7 +75,9 @@ describe API::Users, api: true do
context "when admin" do
it "returns an array of users" do
get api("/users", admin)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first.keys).to include 'email'
expect(json_response.first.keys).to include 'organization'
@@ -87,6 +94,7 @@ describe API::Users, api: true do
get api("/users?external=true", admin)
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response).to all(include('external' => true))
end
@@ -507,8 +515,11 @@ describe API::Users, api: true do
it 'returns array of ssh keys' do
user.keys << key
user.save
+
get api("/users/#{user.id}/keys", admin)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(key.title)
end
@@ -595,8 +606,11 @@ describe API::Users, api: true do
it 'returns array of emails' do
user.emails << email
user.save
+
get api("/users/#{user.id}/emails", admin)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['email']).to eq(email.email)
end
@@ -774,8 +788,11 @@ describe API::Users, api: true do
it "returns array of ssh keys" do
user.keys << key
user.save
+
get api("/user/keys", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first["title"]).to eq(key.title)
end
@@ -891,8 +908,11 @@ describe API::Users, api: true do
it "returns array of emails" do
user.emails << email
user.save
+
get api("/user/emails", user)
+
expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first["email"]).to eq(email.email)
end
diff --git a/spec/requests/api/v3/boards_spec.rb b/spec/requests/api/v3/boards_spec.rb
new file mode 100644
index 00000000000..8aaf3be4f87
--- /dev/null
+++ b/spec/requests/api/v3/boards_spec.rb
@@ -0,0 +1,79 @@
+require 'spec_helper'
+
+describe API::V3::Boards, api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:guest) { create(:user) }
+ let!(:project) { create(:empty_project, :public, creator_id: user.id, namespace: user.namespace ) }
+
+ let!(:dev_label) do
+ create(:label, title: 'Development', color: '#FFAABB', project: project)
+ end
+
+ let!(:test_label) do
+ create(:label, title: 'Testing', color: '#FFAACC', project: project)
+ end
+
+ let!(:dev_list) do
+ create(:list, label: dev_label, position: 1)
+ end
+
+ let!(:test_list) do
+ create(:list, label: test_label, position: 2)
+ end
+
+ let!(:board) do
+ create(:board, project: project, lists: [dev_list, test_list])
+ end
+
+ before do
+ project.team << [user, :reporter]
+ project.team << [guest, :guest]
+ end
+
+ describe "GET /projects/:id/boards" do
+ let(:base_url) { "/projects/#{project.id}/boards" }
+
+ context "when unauthenticated" do
+ it "returns authentication error" do
+ get v3_api(base_url)
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context "when authenticated" do
+ it "returns the project issue board" do
+ get v3_api(base_url, user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(board.id)
+ expect(json_response.first['lists']).to be_an Array
+ expect(json_response.first['lists'].length).to eq(2)
+ expect(json_response.first['lists'].last).to have_key('position')
+ end
+ end
+ end
+
+ describe "GET /projects/:id/boards/:board_id/lists" do
+ let(:base_url) { "/projects/#{project.id}/boards/#{board.id}/lists" }
+
+ it 'returns issue board lists' do
+ get v3_api(base_url, user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(2)
+ expect(json_response.first['label']['name']).to eq(dev_label.title)
+ end
+
+ it 'returns 404 if board not found' do
+ get v3_api("/projects/#{project.id}/boards/22343/lists", user)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+end
diff --git a/spec/requests/api/v3/branches_spec.rb b/spec/requests/api/v3/branches_spec.rb
new file mode 100644
index 00000000000..0e4c6bc3bc6
--- /dev/null
+++ b/spec/requests/api/v3/branches_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+require 'mime/types'
+
+describe API::V3::Branches, api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let!(:project) { create(:project, :repository, creator: user) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+
+ describe "GET /projects/:id/repository/branches" do
+ it "returns an array of project branches" do
+ project.repository.expire_all_method_caches
+
+ get v3_api("/projects/#{project.id}/repository/branches", user), per_page: 100
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ branch_names = json_response.map { |x| x['name'] }
+ expect(branch_names).to match_array(project.repository.branch_names)
+ end
+ end
+end
diff --git a/spec/requests/api/v3/labels_spec.rb b/spec/requests/api/v3/labels_spec.rb
new file mode 100644
index 00000000000..18e2c0d40c8
--- /dev/null
+++ b/spec/requests/api/v3/labels_spec.rb
@@ -0,0 +1,70 @@
+require 'spec_helper'
+
+describe API::V3::Labels, api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) }
+ let!(:label1) { create(:label, title: 'label1', project: project) }
+ let!(:priority_label) { create(:label, title: 'bug', project: project, priority: 3) }
+
+ before do
+ project.team << [user, :master]
+ end
+
+ describe 'GET /projects/:id/labels' do
+ it 'returns all available labels to the project' do
+ group = create(:group)
+ group_label = create(:group_label, title: 'feature', group: group)
+ project.update(group: group)
+ create(:labeled_issue, project: project, labels: [group_label], author: user)
+ create(:labeled_issue, project: project, labels: [label1], author: user, state: :closed)
+ create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project )
+
+ expected_keys = [
+ 'id', 'name', 'color', 'description',
+ 'open_issues_count', 'closed_issues_count', 'open_merge_requests_count',
+ 'subscribed', 'priority'
+ ]
+
+ get v3_api("/projects/#{project.id}/labels", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(3)
+ expect(json_response.first.keys).to match_array expected_keys
+ expect(json_response.map { |l| l['name'] }).to match_array([group_label.name, priority_label.name, label1.name])
+
+ label1_response = json_response.find { |l| l['name'] == label1.title }
+ group_label_response = json_response.find { |l| l['name'] == group_label.title }
+ priority_label_response = json_response.find { |l| l['name'] == priority_label.title }
+
+ expect(label1_response['open_issues_count']).to eq(0)
+ expect(label1_response['closed_issues_count']).to eq(1)
+ expect(label1_response['open_merge_requests_count']).to eq(0)
+ expect(label1_response['name']).to eq(label1.name)
+ expect(label1_response['color']).to be_present
+ expect(label1_response['description']).to be_nil
+ expect(label1_response['priority']).to be_nil
+ expect(label1_response['subscribed']).to be_falsey
+
+ expect(group_label_response['open_issues_count']).to eq(1)
+ expect(group_label_response['closed_issues_count']).to eq(0)
+ expect(group_label_response['open_merge_requests_count']).to eq(0)
+ expect(group_label_response['name']).to eq(group_label.name)
+ expect(group_label_response['color']).to be_present
+ expect(group_label_response['description']).to be_nil
+ expect(group_label_response['priority']).to be_nil
+ expect(group_label_response['subscribed']).to be_falsey
+
+ expect(priority_label_response['open_issues_count']).to eq(0)
+ expect(priority_label_response['closed_issues_count']).to eq(0)
+ expect(priority_label_response['open_merge_requests_count']).to eq(1)
+ expect(priority_label_response['name']).to eq(priority_label.name)
+ expect(priority_label_response['color']).to be_present
+ expect(priority_label_response['description']).to be_nil
+ expect(priority_label_response['priority']).to eq(3)
+ expect(priority_label_response['subscribed']).to be_falsey
+ end
+ end
+end
diff --git a/spec/requests/api/v3/repositories_spec.rb b/spec/requests/api/v3/repositories_spec.rb
new file mode 100644
index 00000000000..c696721c1c9
--- /dev/null
+++ b/spec/requests/api/v3/repositories_spec.rb
@@ -0,0 +1,144 @@
+require 'spec_helper'
+require 'mime/types'
+
+describe API::V3::Repositories, api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:guest) { create(:user).tap { |u| create(:project_member, :guest, user: u, project: project) } }
+ let!(:project) { create(:project, :repository, creator: user) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+
+ describe "GET /projects/:id/repository/tree" do
+ let(:route) { "/projects/#{project.id}/repository/tree" }
+
+ shared_examples_for 'repository tree' do
+ it 'returns the repository tree' do
+ get v3_api(route, current_user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+
+ first_commit = json_response.first
+ expect(first_commit['name']).to eq('bar')
+ expect(first_commit['type']).to eq('tree')
+ expect(first_commit['mode']).to eq('040000')
+ end
+
+ context 'when ref does not exist' do
+ it_behaves_like '404 response' do
+ let(:request) { get v3_api("#{route}?ref_name=foo", current_user) }
+ let(:message) { '404 Tree Not Found' }
+ end
+ end
+
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+
+ it_behaves_like '403 response' do
+ let(:request) { get v3_api(route, current_user) }
+ end
+ end
+
+ context 'with recursive=1' do
+ it 'returns recursive project paths tree' do
+ get v3_api("#{route}?recursive=1", current_user)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response[4]['name']).to eq('html')
+ expect(json_response[4]['path']).to eq('files/html')
+ expect(json_response[4]['type']).to eq('tree')
+ expect(json_response[4]['mode']).to eq('040000')
+ end
+
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+
+ it_behaves_like '403 response' do
+ let(:request) { get v3_api(route, current_user) }
+ end
+ end
+
+ context 'when ref does not exist' do
+ it_behaves_like '404 response' do
+ let(:request) { get v3_api("#{route}?recursive=1&ref_name=foo", current_user) }
+ let(:message) { '404 Tree Not Found' }
+ end
+ end
+ end
+ end
+
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'repository tree' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:current_user) { nil }
+ end
+ end
+
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get v3_api(route) }
+ let(:message) { '404 Project Not Found' }
+ end
+ end
+
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'repository tree' do
+ let(:current_user) { user }
+ end
+ end
+
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get v3_api(route, guest) }
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/repository/contributors' do
+ let(:route) { "/projects/#{project.id}/repository/contributors" }
+
+ shared_examples_for 'repository contributors' do
+ it 'returns valid data' do
+ get v3_api(route, current_user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+
+ first_contributor = json_response.first
+ expect(first_contributor['email']).to eq('tiagonbotelho@hotmail.com')
+ expect(first_contributor['name']).to eq('tiagonbotelho')
+ expect(first_contributor['commits']).to eq(1)
+ expect(first_contributor['additions']).to eq(0)
+ expect(first_contributor['deletions']).to eq(0)
+ end
+ end
+
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'repository contributors' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:current_user) { nil }
+ end
+ end
+
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get v3_api(route) }
+ let(:message) { '404 Project Not Found' }
+ end
+ end
+
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'repository contributors' do
+ let(:current_user) { user }
+ end
+ end
+
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get v3_api(route, guest) }
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/v3/system_hooks_spec.rb b/spec/requests/api/v3/system_hooks_spec.rb
new file mode 100644
index 00000000000..da58efb6ebf
--- /dev/null
+++ b/spec/requests/api/v3/system_hooks_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe API::V3::SystemHooks, api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:admin) { create(:admin) }
+ let!(:hook) { create(:system_hook, url: "http://example.com") }
+
+ before { stub_request(:post, hook.url) }
+
+ describe "GET /hooks" do
+ context "when no user" do
+ it "returns authentication error" do
+ get v3_api("/hooks")
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context "when not an admin" do
+ it "returns forbidden error" do
+ get v3_api("/hooks", user)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context "when authenticated as admin" do
+ it "returns an array of hooks" do
+ get v3_api("/hooks", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['url']).to eq(hook.url)
+ expect(json_response.first['push_events']).to be true
+ expect(json_response.first['tag_push_events']).to be false
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/v3/tags_spec.rb b/spec/requests/api/v3/tags_spec.rb
new file mode 100644
index 00000000000..6722789d928
--- /dev/null
+++ b/spec/requests/api/v3/tags_spec.rb
@@ -0,0 +1,67 @@
+require 'spec_helper'
+require 'mime/types'
+
+describe API::V3::Tags, api: true do
+ include ApiHelpers
+ include RepoHelpers
+
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let!(:project) { create(:project, :repository, creator: user) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+
+ describe "GET /projects/:id/repository/tags" do
+ let(:tag_name) { project.repository.tag_names.sort.reverse.first }
+ let(:description) { 'Awesome release!' }
+
+ shared_examples_for 'repository tags' do
+ it 'returns the repository tags' do
+ get v3_api("/projects/#{project.id}/repository/tags", current_user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(tag_name)
+ end
+ end
+
+ context 'when unauthenticated' do
+ it_behaves_like 'repository tags' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:current_user) { nil }
+ end
+ end
+
+ context 'when authenticated' do
+ it_behaves_like 'repository tags' do
+ let(:current_user) { user }
+ end
+ end
+
+ context 'without releases' do
+ it "returns an array of project tags" do
+ get v3_api("/projects/#{project.id}/repository/tags", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(tag_name)
+ end
+ end
+
+ context 'with releases' do
+ before do
+ release = project.releases.find_or_initialize_by(tag: tag_name)
+ release.update_attributes(description: description)
+ end
+
+ it "returns an array of project tags with release info" do
+ get v3_api("/projects/#{project.id}/repository/tags", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(tag_name)
+ expect(json_response.first['message']).to eq('Version 1.1.0')
+ expect(json_response.first['release']['description']).to eq(description)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/v3/users_spec.rb b/spec/requests/api/v3/users_spec.rb
new file mode 100644
index 00000000000..7022f87bc51
--- /dev/null
+++ b/spec/requests/api/v3/users_spec.rb
@@ -0,0 +1,120 @@
+require 'spec_helper'
+
+describe API::V3::Users, api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:key) { create(:key, user: user) }
+ let(:email) { create(:email, user: user) }
+
+ describe 'GET /user/:id/keys' do
+ before { admin }
+
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ get v3_api("/users/#{user.id}/keys")
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'returns 404 for non-existing user' do
+ get v3_api('/users/999999/keys', admin)
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'returns array of ssh keys' do
+ user.keys << key
+ user.save
+
+ get v3_api("/users/#{user.id}/keys", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['title']).to eq(key.title)
+ end
+ end
+ end
+
+ describe 'GET /user/:id/emails' do
+ before { admin }
+
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ get v3_api("/users/#{user.id}/emails")
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'returns 404 for non-existing user' do
+ get v3_api('/users/999999/emails', admin)
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'returns array of emails' do
+ user.emails << email
+ user.save
+
+ get v3_api("/users/#{user.id}/emails", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['email']).to eq(email.email)
+ end
+
+ it "returns a 404 for invalid ID" do
+ put v3_api("/users/ASDF/emails", admin)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe "GET /user/keys" do
+ context "when unauthenticated" do
+ it "returns authentication error" do
+ get v3_api("/user/keys")
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context "when authenticated" do
+ it "returns array of ssh keys" do
+ user.keys << key
+ user.save
+
+ get v3_api("/user/keys", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first["title"]).to eq(key.title)
+ end
+ end
+ end
+
+ describe "GET /user/emails" do
+ context "when unauthenticated" do
+ it "returns authentication error" do
+ get v3_api("/user/emails")
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context "when authenticated" do
+ it "returns array of emails" do
+ user.emails << email
+ user.save
+
+ get v3_api("/user/emails", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first["email"]).to eq(email.email)
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index ebb11166964..ef2ddc4b1d7 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -1,8 +1,16 @@
require 'spec_helper'
-describe Ci::ProcessPipelineService, services: true do
- let(:pipeline) { create(:ci_empty_pipeline, ref: 'master') }
+describe Ci::ProcessPipelineService, :services do
let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+
+ let(:pipeline) do
+ create(:ci_empty_pipeline, ref: 'master', project: project)
+ end
+
+ before do
+ project.add_developer(user)
+ end
describe '#execute' do
context 'start queuing next builds' do
@@ -285,7 +293,7 @@ describe Ci::ProcessPipelineService, services: true do
expect(builds.pluck(:name))
.to contain_exactly('build:1', 'build:2', 'test:1', 'test:2')
- Ci::Build.retry(pipeline.builds.find_by(name: 'test:2')).success
+ Ci::Build.retry(pipeline.builds.find_by(name: 'test:2'), user).success
expect(builds.pluck(:name)).to contain_exactly(
'build:1', 'build:2', 'test:1', 'test:2', 'test:2', 'deploy:1', 'deploy:2')
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
new file mode 100644
index 00000000000..93147870afe
--- /dev/null
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -0,0 +1,117 @@
+require 'spec_helper'
+
+describe Ci::RetryBuildService, :services do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+
+ let(:service) do
+ described_class.new(project, user)
+ end
+
+ shared_examples 'build duplication' do
+ let(:build) do
+ create(:ci_build, :failed, :artifacts, :erased, :trace,
+ :queued, :coverage, pipeline: pipeline)
+ end
+
+ describe 'clone attributes' do
+ described_class::CLONE_ATTRIBUTES.each do |attribute|
+ it "clones #{attribute} build attribute" do
+ expect(new_build.send(attribute)).to eq build.send(attribute)
+ end
+ end
+ end
+
+ describe 'reject attributes' do
+ described_class::REJECT_ATTRIBUTES.each do |attribute|
+ it "does not clone #{attribute} build attribute" do
+ expect(new_build.send(attribute)).not_to eq build.send(attribute)
+ end
+ end
+ end
+
+ it 'has correct number of known attributes' do
+ attributes =
+ described_class::CLONE_ATTRIBUTES +
+ described_class::IGNORE_ATTRIBUTES +
+ described_class::REJECT_ATTRIBUTES
+
+ expect(attributes.size).to eq build.attributes.size
+ end
+ end
+
+ describe '#execute' do
+ let(:new_build) { service.execute(build) }
+
+ context 'when user has ability to execute build' do
+ before do
+ project.add_developer(user)
+ end
+
+ it_behaves_like 'build duplication'
+
+ it 'creates a new build that represents the old one' do
+ expect(new_build.name).to eq build.name
+ end
+
+ it 'enqueues the new build' do
+ expect(new_build).to be_pending
+ end
+
+ it 'resolves todos for old build that failed' do
+ expect(MergeRequests::AddTodoWhenBuildFailsService)
+ .to receive_message_chain(:new, :close)
+
+ service.execute(build)
+ end
+
+ context 'when there are subsequent builds that are skipped' do
+ let!(:subsequent_build) do
+ create(:ci_build, :skipped, stage_idx: 1, pipeline: pipeline)
+ end
+
+ it 'resumes pipeline processing in subsequent stages' do
+ service.execute(build)
+
+ expect(subsequent_build.reload).to be_created
+ end
+ end
+ end
+
+ context 'when user does not have ability to execute build' do
+ it 'raises an error' do
+ expect { service.execute(build) }
+ .to raise_error Gitlab::Access::AccessDeniedError
+ end
+ end
+ end
+
+ describe '#reprocess' do
+ let(:new_build) { service.reprocess(build) }
+
+ context 'when user has ability to execute build' do
+ before do
+ project.add_developer(user)
+ end
+
+ it_behaves_like 'build duplication'
+
+ it 'creates a new build that represents the old one' do
+ expect(new_build.name).to eq build.name
+ end
+
+ it 'does not enqueue the new build' do
+ expect(new_build).to be_created
+ end
+ end
+
+ context 'when user does not have ability to execute build' do
+ it 'raises an error' do
+ expect { service.reprocess(build) }
+ .to raise_error Gitlab::Access::AccessDeniedError
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb
new file mode 100644
index 00000000000..c0af8b8450a
--- /dev/null
+++ b/spec/services/ci/retry_pipeline_service_spec.rb
@@ -0,0 +1,175 @@
+require 'spec_helper'
+
+describe Ci::RetryPipelineService, '#execute', :services do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:service) { described_class.new(project, user) }
+
+ context 'when user has ability to modify pipeline' do
+ let(:user) { create(:admin) }
+
+ context 'when there are failed builds in the last stage' do
+ before do
+ create_build('rspec 1', :success, 0)
+ create_build('rspec 2', :failed, 1)
+ create_build('rspec 3', :canceled, 1)
+ end
+
+ it 'enqueues all builds in the last stage' do
+ service.execute(pipeline)
+
+ expect(build('rspec 2')).to be_pending
+ expect(build('rspec 3')).to be_pending
+ expect(pipeline.reload).to be_running
+ end
+ end
+
+ context 'when there are failed or canceled builds in the first stage' do
+ before do
+ create_build('rspec 1', :failed, 0)
+ create_build('rspec 2', :canceled, 0)
+ create_build('rspec 3', :canceled, 1)
+ create_build('spinach 1', :canceled, 2)
+ end
+
+ it 'retries builds failed builds and marks subsequent for processing' do
+ service.execute(pipeline)
+
+ expect(build('rspec 1')).to be_pending
+ expect(build('rspec 2')).to be_pending
+ expect(build('rspec 3')).to be_created
+ expect(build('spinach 1')).to be_created
+ expect(pipeline.reload).to be_running
+ end
+ end
+
+ context 'when there is failed build present which was run on failure' do
+ before do
+ create_build('rspec 1', :failed, 0)
+ create_build('rspec 2', :canceled, 0)
+ create_build('rspec 3', :canceled, 1)
+ create_build('report 1', :failed, 2)
+ end
+
+ it 'retries builds only in the first stage' do
+ service.execute(pipeline)
+
+ expect(build('rspec 1')).to be_pending
+ expect(build('rspec 2')).to be_pending
+ expect(build('rspec 3')).to be_created
+ expect(build('report 1')).to be_created
+ expect(pipeline.reload).to be_running
+ end
+
+ it 'creates a new job for report job in this case' do
+ service.execute(pipeline)
+
+ expect(statuses.where(name: 'report 1').first).to be_retried
+ end
+ end
+
+ context 'when pipeline contains manual actions' do
+ context 'when there is a canceled manual action in first stage' do
+ before do
+ create_build('rspec 1', :failed, 0)
+ create_build('staging', :canceled, 0, :manual)
+ create_build('rspec 2', :canceled, 1)
+ end
+
+ it 'retries builds failed builds and marks subsequent for processing' do
+ service.execute(pipeline)
+
+ expect(build('rspec 1')).to be_pending
+ expect(build('staging')).to be_skipped
+ expect(build('rspec 2')).to be_created
+ expect(pipeline.reload).to be_running
+ end
+ end
+
+ context 'when there is a skipped manual action in last stage' do
+ before do
+ create_build('rspec 1', :canceled, 0)
+ create_build('staging', :skipped, 1, :manual)
+ end
+
+ it 'retries canceled job and skips manual action' do
+ service.execute(pipeline)
+
+ expect(build('rspec 1')).to be_pending
+ expect(build('staging')).to be_skipped
+ expect(pipeline.reload).to be_running
+ end
+ end
+
+ context 'when there is a created manual action in the last stage' do
+ before do
+ create_build('rspec 1', :canceled, 0)
+ create_build('staging', :created, 1, :manual)
+ end
+
+ it 'retries canceled job and does not update the manual action' do
+ service.execute(pipeline)
+
+ expect(build('rspec 1')).to be_pending
+ expect(build('staging')).to be_created
+ expect(pipeline.reload).to be_running
+ end
+ end
+
+ context 'when there is a created manual action in the first stage' do
+ before do
+ create_build('rspec 1', :canceled, 0)
+ create_build('staging', :created, 0, :manual)
+ end
+
+ it 'retries canceled job and skipps the manual action' do
+ service.execute(pipeline)
+
+ expect(build('rspec 1')).to be_pending
+ expect(build('staging')).to be_skipped
+ expect(pipeline.reload).to be_running
+ end
+ end
+ end
+
+ it 'closes all todos about failed jobs for pipeline' do
+ expect(MergeRequests::AddTodoWhenBuildFailsService)
+ .to receive_message_chain(:new, :close_all)
+
+ service.execute(pipeline)
+ end
+
+ it 'reprocesses the pipeline' do
+ expect(pipeline).to receive(:process!)
+
+ service.execute(pipeline)
+ end
+ end
+
+ context 'when user is not allowed to retry pipeline' do
+ it 'raises an error' do
+ expect { service.execute(pipeline) }
+ .to raise_error Gitlab::Access::AccessDeniedError
+ end
+ end
+
+ def statuses
+ pipeline.reload.statuses
+ end
+
+ def build(name)
+ statuses.latest.find_by(name: name)
+ end
+
+ def create_build(name, status, stage_num, on = 'on_success')
+ create(:ci_build, name: name,
+ status: status,
+ stage: "stage_#{stage_num}",
+ stage_idx: stage_num,
+ when: on,
+ pipeline: pipeline) do |build|
+ pipeline.update_status
+ end
+ end
+end
diff --git a/spec/services/ci/update_runner_service_spec.rb b/spec/services/ci/update_runner_service_spec.rb
new file mode 100644
index 00000000000..e429fcfc72f
--- /dev/null
+++ b/spec/services/ci/update_runner_service_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe Ci::UpdateRunnerService, :services do
+ let(:runner) { create(:ci_runner) }
+
+ describe '#update' do
+ before do
+ allow(runner).to receive(:tick_runner_queue)
+ end
+
+ context 'with description params' do
+ let(:params) { { description: 'new runner' } }
+
+ it 'updates the runner and ticking the queue' do
+ expect(update).to be_truthy
+
+ runner.reload
+
+ expect(runner).to have_received(:tick_runner_queue)
+ expect(runner.description).to eq('new runner')
+ end
+ end
+
+ context 'when params are not valid' do
+ let(:params) { { run_untagged: false } }
+
+ it 'does not update and give false because it is not valid' do
+ expect(update).to be_falsey
+
+ runner.reload
+
+ expect(runner).not_to have_received(:tick_runner_queue)
+ expect(runner.run_untagged).to be_truthy
+ end
+ end
+
+ def update
+ described_class.new(runner).update(params)
+ end
+ end
+end
diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb
index cf0a18aacec..6fb4d517115 100644
--- a/spec/services/create_deployment_service_spec.rb
+++ b/spec/services/create_deployment_service_spec.rb
@@ -234,7 +234,11 @@ describe CreateDeploymentService, services: true do
context 'when build is retried' do
it_behaves_like 'does create environment and deployment' do
- let(:deployable) { Ci::Build.retry(build) }
+ before do
+ project.add_developer(user)
+ end
+
+ let(:deployable) { Ci::Build.retry(build, user) }
subject { deployable.success }
end
diff --git a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
index bb7830c7eea..d80fb8a1af1 100644
--- a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
+++ b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
@@ -17,7 +17,7 @@ describe MergeRequests::AddTodoWhenBuildFailsService do
described_class.new(project, user, commit_message: 'Awesome message')
end
- let(:todo_service) { TodoService.new }
+ let(:todo_service) { spy('todo service') }
let(:merge_request) do
create(:merge_request, merge_user: user,
@@ -107,4 +107,27 @@ describe MergeRequests::AddTodoWhenBuildFailsService do
end
end
end
+
+ describe '#close_all' do
+ context 'when using pipeline that belongs to merge request' do
+ it 'resolves todos about failed builds for pipeline' do
+ service.close_all(pipeline)
+
+ expect(todo_service)
+ .to have_received(:merge_request_build_retried)
+ .with(merge_request)
+ end
+ end
+
+ context 'when pipeline is not related to merge request' do
+ let(:pipeline) { create(:ci_empty_pipeline) }
+
+ it 'does not resolve any todos about failed builds' do
+ service.close_all(pipeline)
+
+ expect(todo_service)
+ .not_to have_received(:merge_request_build_retried)
+ end
+ end
+ end
end
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index 247f0954221..6f31828b825 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -3,6 +3,10 @@ RSpec.configure do |config|
DatabaseCleaner.clean_with(:truncation)
end
+ config.append_after(:context) do
+ DatabaseCleaner.clean_with(:truncation)
+ end
+
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
diff --git a/spec/support/matchers/pagination_matcher.rb b/spec/support/matchers/pagination_matcher.rb
new file mode 100644
index 00000000000..60f5e8239a7
--- /dev/null
+++ b/spec/support/matchers/pagination_matcher.rb
@@ -0,0 +1,5 @@
+RSpec::Matchers.define :include_pagination_headers do |expected|
+ match do |actual|
+ expect(actual.headers).to include('X-Total', 'X-Total-Pages', 'X-Per-Page', 'X-Page', 'X-Next-Page', 'X-Prev-Page', 'Link')
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index 99db6f61bcd..b44d6e69bc6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -852,7 +852,7 @@ boom@2.x.x:
dependencies:
hoek "2.x.x"
-bootstrap-sass@3.3.6:
+bootstrap-sass@^3.3.6:
version "3.3.6"
resolved "https://registry.yarnpkg.com/bootstrap-sass/-/bootstrap-sass-3.3.6.tgz#363b0d300e868d3e70134c1a742bb17288444fd1"
@@ -1265,7 +1265,7 @@ custom-event@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
-d3@3.5.11:
+d3@^3.5.11:
version "3.5.11"
resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.11.tgz#d130750eed0554db70e8432102f920a12407b69c"
@@ -1404,7 +1404,7 @@ domain-browser@^1.1.1:
version "1.1.7"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc"
-dropzone@4.2.0:
+dropzone@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-4.2.0.tgz#fbe7acbb9918e0706489072ef663effeef8a79f3"
@@ -2624,17 +2624,17 @@ jodid25519@^1.0.0:
dependencies:
jsbn "~0.1.0"
-"jquery-ui@github:jquery/jquery-ui#1.11.4":
+"jquery-ui@git+https://github.com/jquery/jquery-ui#1.11.4":
version "1.11.4"
- resolved "https://codeload.github.com/jquery/jquery-ui/tar.gz/d6713024e16de90ea71dc0544ba34e1df01b4d8a"
+ resolved "git+https://github.com/jquery/jquery-ui#d6713024e16de90ea71dc0544ba34e1df01b4d8a"
-jquery-ujs@1.2.1:
+jquery-ujs@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/jquery-ujs/-/jquery-ujs-1.2.1.tgz#6ee75b1ef4e9ac95e7124f8d71f7d351f5548e92"
dependencies:
jquery ">=1.8.0"
-jquery@2.2.1, jquery@>=1.8.0:
+jquery@^2.2.1, jquery@>=1.8.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-2.2.1.tgz#3c3e16854ad3d2ac44ac65021b17426d22ad803f"
@@ -3031,7 +3031,7 @@ moment@2.x:
version "2.17.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.1.tgz#fed9506063f36b10f066c8b59a144d7faebe1d82"
-mousetrap@1.4.6:
+mousetrap@^1.4.6:
version "1.4.6"
resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.4.6.tgz#eaca72e22e56d5b769b7555873b688c3332e390a"
@@ -4296,7 +4296,7 @@ unc-path-regex@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
-underscore@1.8.3:
+underscore@^1.8.3:
version "1.8.3"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
@@ -4387,11 +4387,11 @@ void-elements@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
-vue-resource@0.9.3:
+vue-resource@^0.9.3:
version "0.9.3"
resolved "https://registry.yarnpkg.com/vue-resource/-/vue-resource-0.9.3.tgz#ab46e1c44ea219142dcc28ae4043b3b04c80959d"
-vue@2.0.3:
+vue@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.0.3.tgz#3f7698f83d6ad1f0e35955447901672876c63fde"