Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorLin Jen-Shin <godfat@godfat.org>2016-10-14 14:31:10 +0300
committerLin Jen-Shin <godfat@godfat.org>2016-10-14 14:31:10 +0300
commitdc1d269f67f63eab5f358306ce394b5831377bf7 (patch)
tree2fb75d51a54d6b19e783c11cf12942d7a67cf8db /app
parentb5f9d4c4bc48b252d3175432a3bb6fb1ca394af9 (diff)
parentca3bef554b14ddd2a0d844cd64874885e3f4e90e (diff)
Merge remote-tracking branch 'upstream/master' into pipeline-emails
* upstream/master: (237 commits) Grapify boards API Add test, fix merge error Use local assigns to get the dropdown title Updated issuable dropdown titles Added safety check for formatted values Minor style improvement Fixed conflict and corrected teaspoon test Rename method in test Moved ci_status environments logic to new action ci_envrionments_status and set up frontend polling Refactor ci_status on MergeRequestController Fix indenting error in HAML Show what time ago a MR was deployed Fixed missing links Fixed missing links Refactor merge requests revisions Add link to update docs for source installations Grapify todos API Link to review apps example from docs fix grafana_configuration.md move link Do not run before_script, artifacts, cache in trigger_docs job ...
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/api.js7
-rw-r--r--app/assets/javascripts/boards/boards_bundle.js.es63
-rw-r--r--app/assets/javascripts/boards/services/board_service.js.es610
-rw-r--r--app/assets/javascripts/commit/image_file.js (renamed from app/assets/javascripts/commit/image-file.js)0
-rw-r--r--app/assets/javascripts/cycle_analytics.js.es6 (renamed from app/assets/javascripts/cycle-analytics.js.es6)0
-rw-r--r--app/assets/javascripts/dispatcher.js4
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.es638
-rw-r--r--app/assets/javascripts/gl_dropdown.js1
-rw-r--r--app/assets/javascripts/issues_bulk_assignment.js.es6 (renamed from app/assets/javascripts/issues-bulk-assignment.js.es6)0
-rw-r--r--app/assets/javascripts/label_manager.js.es6 (renamed from app/assets/javascripts/LabelManager.js.es6)0
-rw-r--r--app/assets/javascripts/labels_select.js4
-rw-r--r--app/assets/javascripts/merge_request_widget.js.es6 (renamed from app/assets/javascripts/merge_request_widget.js)76
-rw-r--r--app/assets/javascripts/milestone_select.js2
-rw-r--r--app/assets/javascripts/network/branch_graph.js (renamed from app/assets/javascripts/network/branch-graph.js)0
-rw-r--r--app/assets/javascripts/pipeline.js.es658
-rw-r--r--app/assets/javascripts/project_new.js4
-rw-r--r--app/assets/javascripts/users_select.js7
-rw-r--r--app/assets/stylesheets/framework/avatar.scss4
-rw-r--r--app/assets/stylesheets/framework/blocks.scss2
-rw-r--r--app/assets/stylesheets/framework/buttons.scss8
-rw-r--r--app/assets/stylesheets/framework/forms.scss2
-rw-r--r--app/assets/stylesheets/framework/issue_box.scss2
-rw-r--r--app/assets/stylesheets/framework/logo.scss78
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss2
-rw-r--r--app/assets/stylesheets/framework/mixins.scss18
-rw-r--r--app/assets/stylesheets/framework/mobile.scss2
-rw-r--r--app/assets/stylesheets/framework/selects.scss14
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss6
-rw-r--r--app/assets/stylesheets/framework/typography.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss5
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss2
-rw-r--r--app/assets/stylesheets/pages/editor.scss2
-rw-r--r--app/assets/stylesheets/pages/events.scss2
-rw-r--r--app/assets/stylesheets/pages/labels.scss4
-rw-r--r--app/assets/stylesheets/pages/login.scss6
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss28
-rw-r--r--app/assets/stylesheets/pages/notes.scss2
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss144
-rw-r--r--app/assets/stylesheets/pages/profiles/preferences.scss4
-rw-r--r--app/assets/stylesheets/pages/projects.scss8
-rw-r--r--app/assets/stylesheets/pages/status.scss2
-rw-r--r--app/controllers/ci/application_controller.rb7
-rw-r--r--app/controllers/ci/lints_controller.rb2
-rw-r--r--app/controllers/ci/projects_controller.rb2
-rw-r--r--app/controllers/explore/projects_controller.rb3
-rw-r--r--app/controllers/namespaces_controller.rb25
-rw-r--r--app/controllers/projects/board_lists_controller.rb65
-rw-r--r--app/controllers/projects/boards/issues_controller.rb9
-rw-r--r--app/controllers/projects/boards/lists_controller.rb18
-rw-r--r--app/controllers/projects/boards_controller.rb28
-rw-r--r--app/controllers/projects/graphs_controller.rb6
-rw-r--r--app/controllers/projects/merge_requests_controller.rb47
-rw-r--r--app/controllers/projects/tags_controller.rb2
-rw-r--r--app/controllers/snippets_controller.rb14
-rw-r--r--app/finders/trending_projects_finder.rb16
-rw-r--r--app/helpers/boards_helper.rb12
-rw-r--r--app/helpers/button_helper.rb5
-rw-r--r--app/helpers/merge_requests_helper.rb17
-rw-r--r--app/models/ci/pipeline.rb15
-rw-r--r--app/models/commit_status.rb28
-rw-r--r--app/models/compare.rb21
-rw-r--r--app/models/cycle_analytics.rb14
-rw-r--r--app/models/deployment.rb13
-rw-r--r--app/models/environment.rb15
-rw-r--r--app/models/event.rb8
-rw-r--r--app/models/merge_request.rb15
-rw-r--r--app/models/merge_request_diff.rb21
-rw-r--r--app/models/namespace.rb14
-rw-r--r--app/models/project.rb31
-rw-r--r--app/models/project_group_link.rb2
-rw-r--r--app/models/repository.rb17
-rw-r--r--app/models/trending_project.rb35
-rw-r--r--app/models/user.rb5
-rw-r--r--app/policies/project_policy.rb21
-rw-r--r--app/services/boards/base_service.rb5
-rw-r--r--app/services/boards/create_service.rb17
-rw-r--r--app/services/boards/issues/create_service.rb17
-rw-r--r--app/services/boards/issues/list_service.rb6
-rw-r--r--app/services/boards/issues/move_service.rb8
-rw-r--r--app/services/boards/list_service.rb14
-rw-r--r--app/services/boards/lists/create_service.rb12
-rw-r--r--app/services/boards/lists/destroy_service.rb6
-rw-r--r--app/services/boards/lists/generate_service.rb10
-rw-r--r--app/services/boards/lists/list_service.rb9
-rw-r--r--app/services/boards/lists/move_service.rb5
-rw-r--r--app/services/ci/process_pipeline_service.rb2
-rw-r--r--app/services/compare_service.rb7
-rw-r--r--app/services/git_push_service.rb15
-rw-r--r--app/services/merge_requests/add_todo_when_build_fails_service.rb4
-rw-r--r--app/services/merge_requests/assign_issues_service.rb35
-rw-r--r--app/services/merge_requests/base_service.rb27
-rw-r--r--app/services/merge_requests/build_service.rb23
-rw-r--r--app/services/merge_requests/merge_when_build_succeeds_service.rb9
-rw-r--r--app/services/merge_requests/update_service.rb5
-rw-r--r--app/services/notification_service.rb6
-rw-r--r--app/services/slash_commands/interpret_service.rb21
-rw-r--r--app/services/todo_service.rb6
-rw-r--r--app/validators/namespace_validator.rb1
-rw-r--r--app/views/admin/application_settings/_form.html.haml6
-rw-r--r--app/views/layouts/nav/_project.html.haml2
-rw-r--r--app/views/projects/boards/index.html.haml16
-rw-r--r--app/views/projects/boards/show.html.haml5
-rw-r--r--app/views/projects/branches/_branch.html.haml4
-rw-r--r--app/views/projects/builds/_sidebar.html.haml2
-rw-r--r--app/views/projects/ci/builds/_build_pipeline.html.haml6
-rw-r--r--app/views/projects/commit/_pipeline.html.haml79
-rw-r--r--app/views/projects/commit/_pipeline_stage.html.haml2
-rw-r--r--app/views/projects/commit/_pipeline_status_group.html.haml20
-rw-r--r--app/views/projects/compare/index.html.haml2
-rw-r--r--app/views/projects/diffs/_file.html.haml2
-rw-r--r--app/views/projects/edit.html.haml2
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml2
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml6
-rw-r--r--app/views/projects/issues/_head.html.haml2
-rw-r--r--app/views/projects/issues/edit.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml4
-rw-r--r--app/views/projects/merge_requests/_new_compare.html.haml13
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml50
-rw-r--r--app/views/projects/merge_requests/_show.html.haml2
-rw-r--r--app/views/projects/merge_requests/edit.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_versions.html.haml10
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml16
-rw-r--r--app/views/projects/merge_requests/widget/_open.html.haml1
-rw-r--r--app/views/projects/merge_requests/widget/_show.html.haml3
-rw-r--r--app/views/projects/network/show.html.haml2
-rw-r--r--app/views/shared/icons/_illustration_no_commits.svg1
-rw-r--r--app/views/shared/issuable/_form.html.haml6
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml3
-rw-r--r--app/views/shared/issuable/_milestone_dropdown.html.haml3
-rw-r--r--app/views/snippets/show.html.haml1
-rw-r--r--app/workers/pipeline_process_worker.rb (renamed from app/workers/process_pipeline_worker.rb)2
-rw-r--r--app/workers/pipeline_success_worker.rb12
-rw-r--r--app/workers/pipeline_update_worker.rb (renamed from app/workers/update_pipeline_worker.rb)2
-rw-r--r--app/workers/trending_projects_worker.rb11
-rw-r--r--app/workers/update_merge_requests_worker.rb16
136 files changed, 1062 insertions, 646 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 599331df3f5..56ec1489f89 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -6,11 +6,10 @@
groupProjectsPath: "/api/:version/groups/:id/projects.json",
projectsPath: "/api/:version/projects.json?simple=true",
labelsPath: "/:namespace_path/:project_path/labels",
- licensePath: "/api/:version/licenses/:key",
- gitignorePath: "/api/:version/gitignores/:key",
- gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key",
+ licensePath: "/api/:version/templates/licenses/:key",
+ gitignorePath: "/api/:version/templates/gitignores/:key",
+ gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key",
issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key",
-
group: function(group_id, callback) {
var url = Api.buildUrl(Api.groupPath)
.replace(':id', group_id);
diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6
index 91c12570e09..d4f8f4b9420 100644
--- a/app/assets/javascripts/boards/boards_bundle.js.es6
+++ b/app/assets/javascripts/boards/boards_bundle.js.es6
@@ -28,12 +28,13 @@ $(() => {
state: Store.state,
loading: true,
endpoint: $boardApp.dataset.endpoint,
+ boardId: $boardApp.dataset.boardId,
disabled: $boardApp.dataset.disabled === 'true',
issueLinkBase: $boardApp.dataset.issueLinkBase
},
init: Store.create.bind(Store),
created () {
- gl.boardService = new BoardService(this.endpoint);
+ gl.boardService = new BoardService(this.endpoint, this.boardId);
},
ready () {
Store.disabled = this.disabled;
diff --git a/app/assets/javascripts/boards/services/board_service.js.es6 b/app/assets/javascripts/boards/services/board_service.js.es6
index 2b825c3949f..b9c91cbf31e 100644
--- a/app/assets/javascripts/boards/services/board_service.js.es6
+++ b/app/assets/javascripts/boards/services/board_service.js.es6
@@ -1,15 +1,15 @@
class BoardService {
- constructor (root) {
+ constructor (root, boardId) {
Vue.http.options.root = root;
- this.lists = Vue.resource(`${root}/lists{/id}`, {}, {
+ this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, {
generate: {
method: 'POST',
- url: `${root}/lists/generate.json`
+ url: `${root}/${boardId}/lists/generate.json`
}
});
- this.issue = Vue.resource(`${root}/issues{/id}`, {});
- this.issues = Vue.resource(`${root}/lists{/id}/issues`, {});
+ this.issue = Vue.resource(`${root}/${boardId}/issues{/id}`, {});
+ this.issues = Vue.resource(`${root}/${boardId}/lists{/id}/issues`, {});
Vue.http.interceptors.push((request, next) => {
request.headers['X-CSRF-Token'] = $.rails.csrfToken();
diff --git a/app/assets/javascripts/commit/image-file.js b/app/assets/javascripts/commit/image_file.js
index e893491b19b..e893491b19b 100644
--- a/app/assets/javascripts/commit/image-file.js
+++ b/app/assets/javascripts/commit/image_file.js
diff --git a/app/assets/javascripts/cycle-analytics.js.es6 b/app/assets/javascripts/cycle_analytics.js.es6
index cd9886ba58d..cd9886ba58d 100644
--- a/app/assets/javascripts/cycle-analytics.js.es6
+++ b/app/assets/javascripts/cycle_analytics.js.es6
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 8d99b12102d..f3ef13ce20e 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -21,6 +21,7 @@
shortcut_handler = null;
switch (page) {
case 'projects:boards:show':
+ case 'projects:boards:index':
shortcut_handler = new ShortcutsNavigation();
break;
case 'projects:merge_requests:index':
@@ -126,6 +127,9 @@
new TreeView();
}
break;
+ case 'projects:pipelines:show':
+ new gl.Pipelines();
+ break;
case 'groups:activity':
new Activities();
break;
diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6
index d0786bf0053..845313b6b38 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.es6
+++ b/app/assets/javascripts/gfm_auto_complete.js.es6
@@ -52,37 +52,27 @@
}
}
},
- setup: function(input) {
+ setup: _.debounce(function(input) {
// Add GFM auto-completion to all input fields, that accept GFM input.
this.input = input || $('.js-gfm-input');
// destroy previous instances
this.destroyAtWho();
// set up instances
this.setupAtWho();
- if (this.dataSource) {
- if (!this.dataLoading && !this.cachedData) {
- this.dataLoading = true;
- setTimeout((function(_this) {
- return function() {
- var fetch;
- fetch = _this.fetchData(_this.dataSource);
- return fetch.done(function(data) {
- _this.dataLoading = false;
- return _this.loadData(data);
- });
- };
- // We should wait until initializations are done
- // and only trigger the last .setup since
- // The previous .dataSource belongs to the previous issuable
- // and the last one will have the **proper** .dataSource property
- // TODO: Make this a singleton and turn off events when moving to another page
- })(this), 1000);
- }
- if (this.cachedData != null) {
- return this.loadData(this.cachedData);
- }
+
+ if (this.dataSource && !this.dataLoading && !this.cachedData) {
+ this.dataLoading = true;
+ return this.fetchData(this.dataSource)
+ .done((data) => {
+ this.dataLoading = false;
+ this.loadData(data);
+ });
+ };
+
+ if (this.cachedData != null) {
+ return this.loadData(this.cachedData);
}
- },
+ }, 1000),
setupAtWho: function() {
// Emoji
this.input.atwho({
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index d4403375643..e034ca68645 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -738,6 +738,7 @@
return false;
}
if (currentKeyCode === 13 && currentIndex !== -1) {
+ e.preventDefault();
_this.selectRowAtIndex();
}
};
diff --git a/app/assets/javascripts/issues-bulk-assignment.js.es6 b/app/assets/javascripts/issues_bulk_assignment.js.es6
index 0808f538f01..0808f538f01 100644
--- a/app/assets/javascripts/issues-bulk-assignment.js.es6
+++ b/app/assets/javascripts/issues_bulk_assignment.js.es6
diff --git a/app/assets/javascripts/LabelManager.js.es6 b/app/assets/javascripts/label_manager.js.es6
index bc68e53504f..bc68e53504f 100644
--- a/app/assets/javascripts/LabelManager.js.es6
+++ b/app/assets/javascripts/label_manager.js.es6
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index e356872624a..f1e719937c7 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -292,7 +292,7 @@
return;
}
- if (page === 'projects:boards:show') {
+ if ($('html').hasClass('issue-boards-page')) {
return;
}
if ($dropdown.hasClass('js-multiselect')) {
@@ -334,7 +334,7 @@
page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index';
isMRIndex = page === 'projects:merge_requests:index';
- if (page === 'projects:boards:show') {
+ if ($('html').hasClass('issue-boards-page')) {
if (label.isAny) {
gl.issueBoards.BoardsStore.state.filters['label_name'] = [];
}
diff --git a/app/assets/javascripts/merge_request_widget.js b/app/assets/javascripts/merge_request_widget.js.es6
index 7bbcdf59838..fcadc4bc515 100644
--- a/app/assets/javascripts/merge_request_widget.js
+++ b/app/assets/javascripts/merge_request_widget.js.es6
@@ -1,7 +1,26 @@
-(function() {
+ ((global) => {
var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
- this.MergeRequestWidget = (function() {
+ const DEPLOYMENT_TEMPLATE = `<div class="mr-widget-heading" id="<%- id %>">
+ <div class="ci_widget ci-success">
+ <%= ci_success_icon %>
+ <span>
+ Deployed to
+ <a href="<%- url %>" target="_blank" class="environment">
+ <%- name %>
+ </a>
+ <span class="js-environment-timeago" data-toggle="tooltip" data-placement="top" data-title="<%- deployed_at_formatted %>">
+ <%- deployed_at %>
+ </span>
+ <a class="js-environment-link" href="<%- external_url %>" target="_blank">
+ <i class="fa fa-external-link"></i>
+ View on <%- external_url_formatted %>
+ </a>
+ </span>
+ </div>
+ </div>`;
+
+ global.MergeRequestWidget = (function() {
function MergeRequestWidget(opts) {
// Initialize MergeRequestWidget behavior
//
@@ -10,17 +29,23 @@
// ci_status_url - String, URL to use to check CI status
//
this.opts = opts;
+ this.$widgetBody = $('.mr-widget-body');
$('#modal_merge_info').modal({
show: false
});
this.firstCICheck = true;
this.readyForCICheck = false;
+ this.readyForCIEnvironmentCheck = false;
this.cancel = false;
clearInterval(this.fetchBuildStatusInterval);
+ clearInterval(this.fetchBuildEnvironmentStatusInterval);
this.clearEventListeners();
this.addEventListeners();
this.getCIStatus(false);
+ this.getCIEnvironmentsStatus();
+ this.retrieveSuccessIcon();
this.pollCIStatus();
+ this.pollCIEnvironmentsStatus();
notifyPermissions();
}
@@ -41,6 +66,7 @@
page = $('body').data('page').split(':').last();
if (allowedPages.indexOf(page) < 0) {
clearInterval(_this.fetchBuildStatusInterval);
+ clearInterval(_this.fetchBuildEnvironmentStatusInterval);
_this.cancelPolling();
return _this.clearEventListeners();
}
@@ -48,6 +74,12 @@
})(this));
};
+ MergeRequestWidget.prototype.retrieveSuccessIcon = function() {
+ const $ciSuccessIcon = $('.js-success-icon');
+ this.$ciSuccessIcon = $ciSuccessIcon.html();
+ $ciSuccessIcon.remove();
+ }
+
MergeRequestWidget.prototype.mergeInProgress = function(deleteSourceBranch) {
if (deleteSourceBranch == null) {
deleteSourceBranch = false;
@@ -62,7 +94,7 @@
urlSuffix = deleteSourceBranch ? '?deleted_source_branch=true' : '';
return window.location.href = window.location.pathname + urlSuffix;
} else if (data.merge_error) {
- return $('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>");
+ return this.$widgetBody.html("<h4>" + data.merge_error + "</h4>");
} else {
callback = function() {
return merge_request_widget.mergeInProgress(deleteSourceBranch);
@@ -118,6 +150,7 @@
if (data.status === '') {
return;
}
+ if (data.environments && data.environments.length) _this.renderEnvironments(data.environments);
if (_this.firstCICheck || data.status !== _this.opts.ci_status && (data.status != null)) {
_this.opts.ci_status = data.status;
_this.showCIStatus(data.status);
@@ -150,6 +183,41 @@
})(this));
};
+ MergeRequestWidget.prototype.pollCIEnvironmentsStatus = function() {
+ this.fetchBuildEnvironmentStatusInterval = setInterval(() => {
+ if (!this.readyForCIEnvironmentCheck) return;
+ this.getCIEnvironmentsStatus();
+ this.readyForCIEnvironmentCheck = false;
+ }, 300000);
+ };
+
+ MergeRequestWidget.prototype.getCIEnvironmentsStatus = function() {
+ $.getJSON(this.opts.ci_environments_status_url, (environments) => {
+ if (this.cancel) return;
+ this.readyForCIEnvironmentCheck = true;
+ if (environments && environments.length) this.renderEnvironments(environments);
+ });
+ };
+
+ MergeRequestWidget.prototype.renderEnvironments = function(environments) {
+ for (let i = 0; i < environments.length; i++) {
+ const environment = environments[i];
+ if ($(`.mr-state-widget #${ environment.id }`).length) return;
+ const $template = $(DEPLOYMENT_TEMPLATE);
+ if (!environment.external_url || !environment.external_url_formatted) $('.js-environment-link', $template).remove();
+ if (environment.deployed_at && environment.deployed_at_formatted) {
+ environment.deployed_at = $.timeago(environment.deployed_at) + '.';
+ } else {
+ $('.js-environment-timeago', $template).remove();
+ environment.name += '.';
+ }
+ environment.ci_success_icon = this.$ciSuccessIcon;
+ const templateString = _.unescape($template[0].outerHTML);
+ const template = _.template(templateString)(environment)
+ this.$widgetBody.before(template);
+ }
+ };
+
MergeRequestWidget.prototype.showCIStatus = function(state) {
var allowed_states;
if (state == null) {
@@ -190,4 +258,4 @@
})();
-}).call(this);
+ })(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 26cc6eb0e96..cee42633c79 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -110,7 +110,7 @@
e.preventDefault();
return;
}
- if (page === 'projects:boards:show') {
+ if ($('html').hasClass('issue-boards-page')) {
gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = selected.name;
gl.issueBoards.BoardsStore.updateFiltersUrl();
e.preventDefault();
diff --git a/app/assets/javascripts/network/branch-graph.js b/app/assets/javascripts/network/branch_graph.js
index 91132af273a..91132af273a 100644
--- a/app/assets/javascripts/network/branch-graph.js
+++ b/app/assets/javascripts/network/branch_graph.js
diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6
index 8813bb5dfef..6bf63ee6979 100644
--- a/app/assets/javascripts/pipeline.js.es6
+++ b/app/assets/javascripts/pipeline.js.es6
@@ -1,24 +1,40 @@
-(function() {
- function toggleGraph() {
- const $pipelineBtn = $(this).closest('.toggle-pipeline-btn');
- const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph');
- const $btnText = $(this).find('.toggle-btn-text');
- const $icon = $(this).find('.fa');
-
- $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed');
-
- const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed');
- const expandIcon = 'fa-caret-down';
- const hideIcon = 'fa-caret-up';
-
- if(graphCollapsed) {
- $btnText.text('Expand');
- $icon.removeClass(hideIcon).addClass(expandIcon);
- } else {
- $btnText.text('Hide');
- $icon.removeClass(expandIcon).addClass(hideIcon);
+((global) => {
+
+ class Pipelines {
+ constructor() {
+ $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph);
+ this.addMarginToBuildColumns();
+ }
+
+ toggleGraph() {
+ const $pipelineBtn = $(this).closest('.toggle-pipeline-btn');
+ const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph');
+ const $btnText = $(this).find('.toggle-btn-text');
+ const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed');
+
+ $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed');
+
+
+ graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide')
+ }
+
+ addMarginToBuildColumns() {
+ const $secondChildBuildNode = $('.build:nth-child(2)');
+ if ($secondChildBuildNode.length) {
+ const $firstChildBuildNode = $secondChildBuildNode.prev('.build');
+ const $multiBuildColumn = $secondChildBuildNode.closest('.stage-column');
+ const $previousColumn = $multiBuildColumn.prev('.stage-column');
+ $multiBuildColumn.addClass('left-margin');
+ $firstChildBuildNode.addClass('left-connector');
+ $previousColumn.each(function() {
+ $this = $(this);
+ if ($('.build', $this).length === 1) $this.addClass('no-margin');
+ });
+ }
+ $('.pipeline-graph').removeClass('hidden');
}
}
- $(document).on('click', '.toggle-pipeline-btn', toggleGraph);
-})();
+ global.Pipelines = Pipelines;
+
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js
index a787b11f2a9..3cf41505814 100644
--- a/app/assets/javascripts/project_new.js
+++ b/app/assets/javascripts/project_new.js
@@ -4,7 +4,9 @@
this.ProjectNew = (function() {
function ProjectNew() {
this.toggleSettings = bind(this.toggleSettings, this);
- this.$selects = $('.features select');
+ this.$selects = $('.features select').filter(function () {
+ return $(this).data('field');
+ });
$('.project-edit-container').on('ajax:before', (function(_this) {
return function() {
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index bcabda3ceb2..6aa0e1cd2b6 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -160,7 +160,7 @@
selectedId = user.id;
return;
}
- if (page === 'projects:boards:show') {
+ if ($('html').hasClass('issue-boards-page')) {
selectedId = user.id;
gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id;
gl.issueBoards.BoardsStore.updateFiltersUrl();
@@ -261,10 +261,11 @@
}
}
if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) {
+ var trimmed = query.term.trim();
emailUser = {
name: "Invite \"" + query.term + "\"",
- username: query.term,
- id: query.term
+ username: trimmed,
+ id: trimmed
};
data.results.unshift(emailUser);
}
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index c79b22d4d21..98e301d3799 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -4,7 +4,7 @@
width: 40px;
height: 40px;
padding: 0;
- @include border-radius($avatar_radius);
+ border-radius: $avatar_radius;
border: 1px solid rgba(0, 0, 0, .1);
&.avatar-inline {
@@ -17,7 +17,7 @@
}
&.avatar-tile {
- @include border-radius(0);
+ border-radius: 0;
border: none;
}
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index d315db4cb32..8002e56724b 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -133,7 +133,7 @@
}
.identicon {
- @include border-radius(50%);
+ border-radius: 50%;
}
}
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index d11b2fe7ec2..a7c8d782e9b 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -1,5 +1,5 @@
@mixin btn-default {
- @include border-radius(3px);
+ border-radius: 3px;
font-size: $gl-font-size;
font-weight: 500;
padding: $gl-vert-padding $gl-btn-padding;
@@ -8,7 +8,7 @@
&:active {
outline: none;
background-color: $btn-active-gray;
- @include box-shadow($gl-btn-active-background);
+ box-shadow: $gl-btn-active-background;
}
}
@@ -43,7 +43,7 @@
&:active,
&.active {
- @include box-shadow ($gl-btn-active-background);
+ box-shadow: $gl-btn-active-background;
background-color: $dark;
border-color: $border-dark;
@@ -279,7 +279,7 @@
}
.active {
- @include box-shadow($gl-btn-active-background);
+ box-shadow: $gl-btn-active-background;
border: 1px solid #c6cacf !important;
background-color: #e4e7ed !important;
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index a67d31de2f7..05e8ee0190d 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -73,7 +73,7 @@ label {
}
.form-control {
- @include box-shadow(none);
+ box-shadow: none;
border-radius: 3px;
padding: $gl-vert-padding $gl-input-padding;
}
diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index 8bfc0d583c5..ba3930e03bd 100644
--- a/app/assets/stylesheets/framework/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -16,7 +16,7 @@
margin-top: 5px;
}
- @include border-radius(3px);
+ border-radius: 3px;
display: block;
float: left;
margin-right: 10px;
diff --git a/app/assets/stylesheets/framework/logo.scss b/app/assets/stylesheets/framework/logo.scss
index 3ee3fb4cee5..c214eabcad7 100644
--- a/app/assets/stylesheets/framework/logo.scss
+++ b/app/assets/stylesheets/framework/logo.scss
@@ -1,15 +1,3 @@
-@mixin unique-keyframes {
- $animation-name: unique-id();
- @include webkit-prefix(animation-name, $animation-name);
-
- @-webkit-keyframes #{$animation-name} {
- @content;
- }
- @keyframes #{$animation-name} {
- @content;
- }
-}
-
@mixin tanuki-logo-colors($path-color) {
fill: $path-color;
transition: all 0.8s;
@@ -20,28 +8,6 @@
}
}
-@mixin tanuki-second-highlight-animations($tanuki-color) {
- @include unique-keyframes {
- 10%, 80% {
- fill: #{$tanuki-color}
- }
- 20%, 90% {
- fill: lighten($tanuki-color, 25%);
- }
- }
-}
-
-@mixin tanuki-forth-highlight-animations($tanuki-color) {
- @include unique-keyframes {
- 30%, 60% {
- fill: #{$tanuki-color};
- }
- 40%, 70% {
- fill: lighten($tanuki-color, 25%);
- }
- }
-}
-
.tanuki-logo {
.tanuki-left-ear,
@@ -67,7 +33,7 @@
}
.tanuki-left-cheek {
- @include unique-keyframes {
+ @include include-keyframes(animate-tanuki-left-cheek) {
0%, 10%, 100% {
fill: lighten($tanuki-yellow, 25%);
}
@@ -78,15 +44,29 @@
}
.tanuki-left-eye {
- @include tanuki-second-highlight-animations($tanuki-orange);
+ @include include-keyframes(animate-tanuki-left-eye) {
+ 10%, 80% {
+ fill: $tanuki-orange;
+ }
+ 20%, 90% {
+ fill: lighten($tanuki-orange, 25%);
+ }
+ }
}
.tanuki-left-ear {
- @include tanuki-second-highlight-animations($tanuki-red);
+ @include include-keyframes(animate-tanuki-left-ear) {
+ 10%, 80% {
+ fill: $tanuki-red;
+ }
+ 20%, 90% {
+ fill: lighten($tanuki-red, 25%);
+ }
+ }
}
.tanuki-nose {
- @include unique-keyframes {
+ @include include-keyframes(animate-tanuki-nose) {
20%, 70% {
fill: $tanuki-red;
}
@@ -97,15 +77,29 @@
}
.tanuki-right-eye {
- @include tanuki-forth-highlight-animations($tanuki-orange);
+ @include include-keyframes(animate-tanuki-right-eye) {
+ 30%, 60% {
+ fill: $tanuki-orange;
+ }
+ 40%, 70% {
+ fill: lighten($tanuki-orange, 25%);
+ }
+ }
}
.tanuki-right-ear {
- @include tanuki-forth-highlight-animations($tanuki-red);
+ @include include-keyframes(animate-tanuki-right-ear) {
+ 30%, 60% {
+ fill: $tanuki-red;
+ }
+ 40%, 70% {
+ fill: lighten($tanuki-red, 25%);
+ }
+ }
}
.tanuki-right-cheek {
- @include unique-keyframes {
+ @include include-keyframes(animate-tanuki-right-cheek) {
40% {
fill: $tanuki-yellow;
}
@@ -115,4 +109,4 @@
}
}
}
-} \ No newline at end of file
+}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index edea4ad00eb..6d28d98b283 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -86,7 +86,7 @@
}
.markdown-area {
- @include border-radius(0);
+ border-radius: 0;
background: #fff;
border: 1px solid #ddd;
min-height: 140px;
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 1ec08cdef23..7fabf27a558 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -1,15 +1,4 @@
/**
- * Generic mixins
- */
-@mixin box-shadow($shadow) {
- box-shadow: $shadow;
-}
-
-@mixin border-radius($radius) {
- border-radius: $radius;
-}
-
-/**
* Prefilled mixins
* Mixins with fixed values
*/
@@ -95,3 +84,10 @@
@content;
}
}
+
+@mixin include-keyframes($animation-name) {
+ @include webkit-prefix(animation-name, $animation-name);
+ @include keyframes($animation-name) {
+ @content;
+ }
+}
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 76b93b23b95..9fe390eb09d 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -133,5 +133,5 @@
font-size: 20px;
color: #777;
z-index: 100;
- @include box-shadow(0 1px 2px #ddd);
+ box-shadow: 0 1px 2px #ddd;
}
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index bcd60391543..79cd26714a3 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -46,8 +46,8 @@
}
.select2-drop {
- @include box-shadow(rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0);
- @include border-radius ($border-radius-default);
+ box-shadow: rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0;
+ border-radius: $border-radius-default;
border: none;
min-width: 175px;
}
@@ -72,7 +72,7 @@
.select2-container-active {
.select2-choice, .select2-choices {
- @include box-shadow(none);
+ box-shadow: none;
}
}
@@ -82,13 +82,13 @@
outline: 0;
background-image: none;
background-color: $white-dark;
- @include box-shadow($gl-btn-active-gradient);
+ box-shadow: $gl-btn-active-gradient;
}
}
.select2-container-multi {
.select2-choices {
- @include border-radius($border-radius-default);
+ border-radius: $border-radius-default;
border-color: $input-border;
background: none;
@@ -123,7 +123,7 @@
&.select2-container-active .select2-choices,
&.select2-dropdown-open .select2-choices {
border-color: $border-white-normal;
- @include box-shadow($gl-btn-active-gradient);
+ box-shadow: $gl-btn-active-gradient;
}
}
@@ -157,7 +157,7 @@
background-repeat: no-repeat;
background-position: right 0 bottom 6px;
border: 1px solid $input-border;
- @include border-radius($border-radius-default);
+ border-radius: $border-radius-default;
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
&:focus {
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 557ef7291cf..ec52f326eb9 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -4,7 +4,7 @@
&.page-sidebar-pinned {
.sidebar-wrapper {
- @include box-shadow(none);
+ box-shadow: none;
}
}
@@ -17,7 +17,7 @@
width: 0;
overflow: hidden;
transition: width $sidebar-transition-duration;
- @include box-shadow(2px 0 16px 0 $black-transparent);
+ box-shadow: 2px 0 16px 0 $black-transparent;
}
}
@@ -100,7 +100,7 @@
.count {
float: right;
padding: 0 8px;
- @include border-radius(6px);
+ border-radius: 6px;
}
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 9f2d53d5206..d099a884f54 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -116,7 +116,7 @@
font-size: 13px;
line-height: 1.6em;
overflow-x: auto;
- @include border-radius(2px);
+ border-radius: 2px;
}
p > code {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 14ec310de2d..4c34ed3ebf7 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -17,8 +17,10 @@ $white-normal: #ededed;
$white-dark: #ececec;
$gray-light: #fafafa;
+$gray-lighter: #f9f9f9;
$gray-normal: #f5f5f5;
$gray-dark: #ededed;
+$gray-darker: #eee;
$gray-darkest: #c9c9c9;
$green-light: #38ae67;
@@ -33,6 +35,8 @@ $blue-medium-light: #3498cb;
$blue-medium: #2f8ebf;
$blue-medium-dark: #2d86b4;
+$blue-light-transparent: rgba(44, 159, 216, 0.05);
+
$orange-light: #fc8a51;
$orange-normal: #e75e40;
$orange-dark: #ce5237;
@@ -91,6 +95,7 @@ $table-text-gray: #8f8f8f;
$gl-font-size: 15px;
$gl-title-color: #333;
$gl-text-color: #5c5c5c;
+$gl-text-color-light: #8c8c8c;
$gl-text-green: #4a2;
$gl-text-red: #d12f19;
$gl-text-orange: #d90;
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index 778471a34d7..d732008de3d 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -50,7 +50,7 @@
.bordered-box {
border: 1px solid $border-color;
- @include border-radius($border-radius-default);
+ border-radius: $border-radius-default;
}
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index e1304335271..fcc5f32c738 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -1,7 +1,7 @@
.file-editor {
#editor {
border: none;
- @include border-radius(0);
+ border-radius: 0;
height: 500px;
margin: 0;
padding: 0;
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index 1d00da1266c..789d6237df8 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -91,7 +91,7 @@
float: right;
border: 1px solid #eee;
padding: 5px;
- @include border-radius(5px);
+ border-radius: 5px;
background: $gray-light;
margin-left: 10px;
top: -6px;
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 822830706a5..701c29a3986 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -1,7 +1,7 @@
.suggest-colors {
margin-top: 5px;
a {
- @include border-radius(4px);
+ border-radius: 4px;
width: 30px;
height: 30px;
display: inline-block;
@@ -17,7 +17,7 @@
overflow: hidden;
a {
- @include border-radius(0);
+ border-radius: 0;
width: (100% / 7);
margin-right: 0;
margin-bottom: -5px;
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index 403171d4532..a5ca509163d 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -73,12 +73,12 @@
height: auto;
&.top {
- @include border-radius(5px 5px 0 0);
+ border-radius: 5px 5px 0 0;
margin-bottom: 0;
}
&.bottom {
- @include border-radius(0 0 5px 5px);
+ border-radius: 0 0 5px 5px;
border-top: 0;
margin-bottom: 20px;
}
@@ -86,7 +86,7 @@
&.middle {
border-top: 0;
margin-bottom: 0;
- @include border-radius(0);
+ border-radius: 0;
}
&:active, &:focus {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index bc8693ae467..6a0fae8a3f9 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -6,7 +6,7 @@
background: $background-color;
color: $gl-gray;
border: 1px solid $border-color;
- @include border-radius(2px);
+ border-radius: 2px;
form {
margin-bottom: 0;
@@ -121,6 +121,10 @@
color: #5c5d5e;
}
+ .js-deployment-link {
+ display: inline-block;
+ }
+
.mr-widget-body {
h4 {
font-weight: 600;
@@ -204,6 +208,18 @@
word-break: break-all;
}
+.commits-empty {
+ text-align: center;
+
+ h4 {
+ padding-top: 20px;
+ padding-bottom: 10px;
+ }
+ svg {
+ width: 230px;
+ }
+}
+
.mr-list {
.merge-request {
padding: 10px 15px;
@@ -389,8 +405,12 @@
padding: 16px;
}
+ .content-block {
+ border-top: 1px solid $border-color;
+ padding: $gl-padding-top $gl-padding;
+ }
+
.comments-disabled-notif {
- padding: 10px 16px;
.btn {
margin-left: 5px;
}
@@ -401,10 +421,6 @@
margin: 0 7px;
}
- .comments-disabled-notif {
- border-top: 1px solid $border-color;
- }
-
.dropdown-title {
color: $gl-text-color;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 54124a3d658..d399f84a2ff 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -334,7 +334,7 @@ ul.notes {
.add-diff-note {
margin-top: -4px;
- @include border-radius(40px);
+ border-radius: 40px;
background: #fff;
padding: 4px;
font-size: 16px;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index a2779704eff..7843355f0ab 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -303,16 +303,41 @@
.stage-column {
display: inline-block;
vertical-align: top;
- margin-right: 65px;
+
+ &:not(:last-child) {
+ margin-right: 44px;
+ }
+
+ &.left-margin {
+ &:not(:first-child) {
+ margin-left: 44px;
+
+ .left-connector {
+ &::before {
+ content: '';
+ position: absolute;
+ top: 48%;
+ left: -48px;
+ border-top: 2px solid $border-color;
+ width: 48px;
+ height: 1px;
+ }
+ }
+ }
+ }
+
+ &.no-margin {
+ margin: 0;
+ }
li {
list-style: none;
}
.stage-name {
- margin-bottom: 15px;
+ margin: 0 0 15px 10px;
font-weight: bold;
- width: 150px;
+ width: 176px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -321,17 +346,23 @@
.build {
border: 1px solid $border-color;
position: relative;
- padding: 6px 10px;
+ padding: 7px 10px 8px;
border-radius: 30px;
- width: 150px;
+ width: 186px;
margin-bottom: 10px;
+ &:hover {
+ background-color: $gray-lighter;
+ .dropdown-menu-toggle {
+ background-color: transparent;
+ }
+ }
+
&.playable {
- background-color: $gray-light;
svg {
- height: 12px;
- width: 12px;
+ height: 13px;
+ width: 20px;
position: relative;
top: 1px;
@@ -342,10 +373,20 @@
}
.build-content {
- width: 130px;
+ display: -ms-flexbox;
+ display: -webkit-flex;
+ display: flex;
+ width: 164px;
+
+ .ci-status-icon {
+ svg {
+ height: 20px;
+ width: 20px;
+ }
+ }
.ci-status-text {
- width: 110px;
+ width: 135px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -356,44 +397,56 @@
}
a {
- color: $layout-link-gray;
+ color: $gl-text-color-light;
text-decoration: none;
-
- &:hover {
- .ci-status-text {
- text-decoration: underline;
- }
- }
}
.dropdown-menu-toggle {
border: none;
width: auto;
padding: 0;
- color: $layout-link-gray;
+ color: $gl-text-color-light;
+ flex-grow: 1;
.ci-status-text {
- width: 80px;
+ max-width: 112px;
+ width: auto;
}
}
.grouped-pipeline-dropdown {
padding: 8px 0;
- width: 200px;
+ width: 186px;
left: auto;
- right: -214px;
+ right: -197px;
top: -9px;
- max-height: 245px;
- overflow-y: scroll;
- a:hover {
- .ci-status-text {
- text-decoration: none;
+ ul {
+ max-height: 245px;
+ overflow: auto;
+ }
+
+ a {
+ color: $gl-text-color;
+ padding: 7px 8px 8px;
+
+ &:hover {
+ background-color: $blue-light-transparent;
+ border-radius: 3px;
+
+ .ci-status-text {
+ text-decoration: none;
+ }
}
}
+ svg {
+ width: 14px;
+ height: 14px;
+ }
+
.ci-status-text {
- width: 145px;
+ width: 112px;
}
.arrow {
@@ -426,9 +479,10 @@
}
.badge {
- background-color: $gray-dark;
- color: $layout-link-gray;
+ background-color: $gray-darker;
+ color: $gl-text-color-light;
font-weight: normal;
+ margin-left: $btn-xs-side-margin;
}
}
@@ -442,10 +496,10 @@
&::after {
content: '';
position: absolute;
- top: 50%;
- right: -69px;
+ top: 48%;
+ right: -48px;
border-top: 2px solid $border-color;
- width: 69px;
+ width: 48px;
height: 1px;
}
}
@@ -454,25 +508,25 @@
&:not(:first-child) {
&::after, &::before {
content: '';
- top: -47px;
+ top: -49px;
position: absolute;
border-bottom: 2px solid $border-color;
- width: 20px;
- height: 65px;
+ width: 25px;
+ height: 69px;
}
// Right connecting curves
&::after {
- right: -20px;
+ right: -25px;
border-right: 2px solid $border-color;
- border-radius: 0 0 15px;
+ border-radius: 0 0 20px;
}
// Left connecting curves
&::before {
- left: -20px;
+ left: -25px;
border-left: 2px solid $border-color;
- border-radius: 0 0 0 15px;
+ border-radius: 0 0 0 20px;
}
}
@@ -480,7 +534,7 @@
&:nth-child(2) {
&::after, &::before {
height: 29px;
- top: -10px;
+ top: -9px;
}
.curve {
display: block;
@@ -538,20 +592,20 @@
width: 21px;
height: 25px;
position: absolute;
- top: -29px;
+ top: -32px;
border-top: 2px solid $border-color;
}
&::after {
- left: -39px;
+ left: -44px;
border-right: 2px solid $border-color;
- border-radius: 0 15px;
+ border-radius: 0 20px;
}
&::before {
- right: -39px;
+ right: -44px;
border-left: 2px solid $border-color;
- border-radius: 15px 0 0;
+ border-radius: 20px 0 0;
}
}
}
diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss
index e5859fe7384..f8da0983b77 100644
--- a/app/assets/stylesheets/pages/profiles/preferences.scss
+++ b/app/assets/stylesheets/pages/profiles/preferences.scss
@@ -4,7 +4,7 @@
text-align: center;
.preview {
- @include border-radius(4px);
+ border-radius: 4px;
height: 80px;
margin-bottom: 10px;
@@ -47,7 +47,7 @@
width: 160px;
img {
- @include border-radius(4px);
+ border-radius: 4px;
max-width: 100%;
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 87548dcb590..530fb0c0d05 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -354,7 +354,7 @@ a.deploy-project-label {
justify-content: flex-start;
.fork-thumbnail {
- @include border-radius($border-radius-base);
+ border-radius: $border-radius-base;
background-color: $white-light;
border: 1px solid $border-white-light;
height: 202px;
@@ -371,7 +371,7 @@ a.deploy-project-label {
background-color: $gray-light;
border: 1px solid $gray-dark;
margin: 0 auto;
- @include border-radius(50%);
+ border-radius: 50%;
i {
font-size: 100px;
color: $gray-dark;
@@ -390,7 +390,7 @@ a.deploy-project-label {
}
img {
- @include border-radius(50%);
+ border-radius: 50%;
max-width: 100px;
}
}
@@ -496,7 +496,7 @@ pre.light-well {
}
.light-well {
- @include border-radius (2px);
+ border-radius: 2px;
color: #5b6169;
font-size: 13px;
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index 0ee7ceecae5..c05f3d5ff32 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -4,7 +4,7 @@
margin-right: 10px;
border: 1px solid #eee;
white-space: nowrap;
- @include border-radius(4px);
+ border-radius: 4px;
&:hover {
text-decoration: none;
diff --git a/app/controllers/ci/application_controller.rb b/app/controllers/ci/application_controller.rb
deleted file mode 100644
index 5bb7d499cdc..00000000000
--- a/app/controllers/ci/application_controller.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-module Ci
- class ApplicationController < ::ApplicationController
- def self.railtie_helpers_paths
- "app/helpers/ci"
- end
- end
-end
diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb
index 78012960252..3eb485de9db 100644
--- a/app/controllers/ci/lints_controller.rb
+++ b/app/controllers/ci/lints_controller.rb
@@ -1,5 +1,5 @@
module Ci
- class LintsController < ApplicationController
+ class LintsController < ::ApplicationController
before_action :authenticate_user!
def show
diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb
index aa894fde36b..ff297d6ff13 100644
--- a/app/controllers/ci/projects_controller.rb
+++ b/app/controllers/ci/projects_controller.rb
@@ -1,5 +1,5 @@
module Ci
- class ProjectsController < Ci::ApplicationController
+ class ProjectsController < ::ApplicationController
before_action :project
before_action :no_cache, only: [:badge]
before_action :authorize_read_project!, except: [:badge, :index]
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index 38e5943eb76..a62c6211372 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -21,8 +21,7 @@ class Explore::ProjectsController < Explore::ApplicationController
end
def trending
- @projects = TrendingProjectsFinder.new.execute
- @projects = filter_projects(@projects)
+ @projects = filter_projects(Project.trending)
@projects = @projects.page(params[:page])
respond_to do |format|
diff --git a/app/controllers/namespaces_controller.rb b/app/controllers/namespaces_controller.rb
deleted file mode 100644
index 83eec1bf4a2..00000000000
--- a/app/controllers/namespaces_controller.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-class NamespacesController < ApplicationController
- skip_before_action :authenticate_user!
-
- def show
- namespace = Namespace.find_by(path: params[:id])
-
- if namespace
- if namespace.is_a?(Group)
- group = namespace
- else
- user = namespace.owner
- end
- end
-
- if user
- redirect_to user_path(user)
- elsif group && can?(current_user, :read_group, group)
- redirect_to group_path(group)
- elsif current_user.nil?
- authenticate_user!
- else
- render_404
- end
- end
-end
diff --git a/app/controllers/projects/board_lists_controller.rb b/app/controllers/projects/board_lists_controller.rb
deleted file mode 100644
index 3cfb08d5822..00000000000
--- a/app/controllers/projects/board_lists_controller.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-class Projects::BoardListsController < Projects::ApplicationController
- respond_to :json
-
- before_action :authorize_admin_list!
-
- rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
-
- def create
- list = Boards::Lists::CreateService.new(project, current_user, list_params).execute
-
- if list.valid?
- render json: list.as_json(only: [:id, :list_type, :position], methods: [:title], include: { label: { only: [:id, :title, :description, :color, :priority] } })
- else
- render json: list.errors, status: :unprocessable_entity
- end
- end
-
- def update
- service = Boards::Lists::MoveService.new(project, current_user, move_params)
-
- if service.execute
- head :ok
- else
- head :unprocessable_entity
- end
- end
-
- def destroy
- service = Boards::Lists::DestroyService.new(project, current_user, params)
-
- if service.execute
- head :ok
- else
- head :unprocessable_entity
- end
- end
-
- def generate
- service = Boards::Lists::GenerateService.new(project, current_user)
-
- if service.execute
- render json: project.board.lists.label.as_json(only: [:id, :list_type, :position], methods: [:title], include: { label: { only: [:id, :title, :description, :color, :priority] } })
- else
- head :unprocessable_entity
- end
- end
-
- private
-
- def authorize_admin_list!
- return render_403 unless can?(current_user, :admin_list, project)
- end
-
- def list_params
- params.require(:list).permit(:label_id)
- end
-
- def move_params
- params.require(:list).permit(:position).merge(id: params[:id])
- end
-
- def record_not_found(exception)
- render json: { error: exception.message }, status: :not_found
- end
-end
diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb
index 095af6c35eb..71eb56aed0b 100644
--- a/app/controllers/projects/boards/issues_controller.rb
+++ b/app/controllers/projects/boards/issues_controller.rb
@@ -16,9 +16,8 @@ module Projects
end
def create
- list = project.board.lists.find(params[:list_id])
service = ::Boards::Issues::CreateService.new(project, current_user, issue_params)
- issue = service.execute(list)
+ issue = service.execute
if issue.valid?
render json: serialize_as_json(issue)
@@ -60,15 +59,15 @@ module Projects
end
def filter_params
- params.merge(id: params[:list_id])
+ params.merge(board_id: params[:board_id], id: params[:list_id])
end
def move_params
- params.permit(:id, :from_list_id, :to_list_id)
+ params.permit(:board_id, :id, :from_list_id, :to_list_id)
end
def issue_params
- params.require(:issue).permit(:title).merge(request: request)
+ params.require(:issue).permit(:title).merge(board_id: params[:board_id], list_id: params[:list_id], request: request)
end
def serialize_as_json(resource)
diff --git a/app/controllers/projects/boards/lists_controller.rb b/app/controllers/projects/boards/lists_controller.rb
index b995f586737..76ae41319c4 100644
--- a/app/controllers/projects/boards/lists_controller.rb
+++ b/app/controllers/projects/boards/lists_controller.rb
@@ -5,11 +5,11 @@ module Projects
before_action :authorize_read_list!, only: [:index]
def index
- render json: serialize_as_json(project.board.lists)
+ render json: serialize_as_json(board.lists)
end
def create
- list = ::Boards::Lists::CreateService.new(project, current_user, list_params).execute
+ list = ::Boards::Lists::CreateService.new(project, current_user, list_params).execute(board)
if list.valid?
render json: serialize_as_json(list)
@@ -19,7 +19,7 @@ module Projects
end
def update
- list = project.board.lists.movable.find(params[:id])
+ list = board.lists.movable.find(params[:id])
service = ::Boards::Lists::MoveService.new(project, current_user, move_params)
if service.execute(list)
@@ -30,8 +30,8 @@ module Projects
end
def destroy
- list = project.board.lists.destroyable.find(params[:id])
- service = ::Boards::Lists::DestroyService.new(project, current_user, params)
+ list = board.lists.destroyable.find(params[:id])
+ service = ::Boards::Lists::DestroyService.new(project, current_user)
if service.execute(list)
head :ok
@@ -43,8 +43,8 @@ module Projects
def generate
service = ::Boards::Lists::GenerateService.new(project, current_user)
- if service.execute
- render json: serialize_as_json(project.board.lists.movable)
+ if service.execute(board)
+ render json: serialize_as_json(board.lists.movable)
else
head :unprocessable_entity
end
@@ -60,6 +60,10 @@ module Projects
return render_403 unless can?(current_user, :read_list, project)
end
+ def board
+ @board ||= project.boards.find(params[:board_id])
+ end
+
def list_params
params.require(:list).permit(:label_id)
end
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index 0035633b774..808affa4f98 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -1,12 +1,28 @@
class Projects::BoardsController < Projects::ApplicationController
include IssuableCollections
-
- respond_to :html
- before_action :authorize_read_board!, only: [:show]
+ before_action :authorize_read_board!, only: [:index, :show]
+
+ def index
+ @boards = ::Boards::ListService.new(project, current_user).execute
+
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: serialize_as_json(@boards)
+ end
+ end
+ end
def show
- ::Boards::CreateService.new(project, current_user).execute
+ @board = project.boards.find(params[:id])
+
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: serialize_as_json(@board)
+ end
+ end
end
private
@@ -14,4 +30,8 @@ class Projects::BoardsController < Projects::ApplicationController
def authorize_read_board!
return access_denied! unless can?(current_user, :read_board, project)
end
+
+ def serialize_as_json(resource)
+ resource.as_json(only: [:id])
+ end
end
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index 092ef32e6e3..923e7340e69 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -38,12 +38,12 @@ class Projects::GraphsController < Projects::ApplicationController
@languages = @languages.map do |language|
name, share = language
- color = Digest::SHA256.hexdigest(name)[0...6]
+ color = Linguist::Language[name].color || "##{Digest::SHA256.hexdigest(name)[0...6]}"
{
value: (share.to_f * 100 / total).round(2),
label: name,
- color: "##{color}",
- highlight: "##{color}"
+ color: color,
+ highlight: color
}
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index ffd9833e3b1..9207c954335 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -10,7 +10,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :module_enabled
before_action :merge_request, only: [
:edit, :update, :show, :diffs, :commits, :conflicts, :builds, :pipelines, :merge, :merge_check,
- :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts
+ :ci_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues
]
before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds, :pipelines]
before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :builds, :pipelines]
@@ -31,6 +31,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
# Allow modify merge_request
before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :remove_wip, :sort]
+ before_action :authenticate_user!, only: [:assign_related_issues]
+
before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :resolve_conflicts]
def index
@@ -354,6 +356,25 @@ class Projects::MergeRequestsController < Projects::ApplicationController
render layout: false
end
+ def assign_related_issues
+ result = MergeRequests::AssignIssuesService.new(project, current_user, merge_request: @merge_request).execute
+
+ respond_to do |format|
+ format.html do
+ case result[:count]
+ when 0
+ flash[:error] = "Failed to assign you issues related to the merge request"
+ when 1
+ flash[:notice] = "1 issue has been assigned to you"
+ else
+ flash[:notice] = "#{result[:count]} issues have been assigned to you"
+ end
+
+ redirect_to(merge_request_path(@merge_request))
+ end
+ end
+ end
+
def ci_status
pipeline = @merge_request.pipeline
if pipeline
@@ -382,6 +403,30 @@ class Projects::MergeRequestsController < Projects::ApplicationController
render json: response
end
+ def ci_environments_status
+ environments =
+ begin
+ @merge_request.environments.map do |environment|
+ next unless can?(current_user, :read_environment, environment)
+
+ project = environment.project
+ deployment = environment.first_deployment_for(@merge_request.diff_head_commit)
+
+ {
+ id: environment.id,
+ name: environment.name,
+ url: namespace_project_environment_path(project.namespace, project, environment),
+ external_url: environment.external_url,
+ external_url_formatted: environment.formatted_external_url,
+ deployed_at: deployment.try(:created_at),
+ deployed_at_formatted: deployment.try(:formatted_deployment_time)
+ }
+ end.compact
+ end
+
+ render json: environments
+ end
+
protected
def selected_target_project
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index 6ea8ee62bc5..8fea20cefef 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -20,6 +20,8 @@ class Projects::TagsController < Projects::ApplicationController
def show
@tag = @repository.find_tag(params[:id])
+ return render_404 unless @tag
+
@release = @project.releases.find_or_initialize_by(tag: @tag.name)
@commit = @repository.commit(@tag.target)
end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index d198782138a..dee57e4a388 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -1,10 +1,10 @@
class SnippetsController < ApplicationController
include ToggleAwardEmoji
- before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
+ before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download]
# Allow read snippet
- before_action :authorize_read_snippet!, only: [:show, :raw]
+ before_action :authorize_read_snippet!, only: [:show, :raw, :download]
# Allow modify snippet
before_action :authorize_update_snippet!, only: [:edit, :update]
@@ -12,7 +12,7 @@ class SnippetsController < ApplicationController
# Allow destroy snippet
before_action :authorize_admin_snippet!, only: [:destroy]
- skip_before_action :authenticate_user!, only: [:index, :show, :raw]
+ skip_before_action :authenticate_user!, only: [:index, :show, :raw, :download]
layout 'snippets'
respond_to :html
@@ -75,6 +75,14 @@ class SnippetsController < ApplicationController
)
end
+ def download
+ send_data(
+ @snippet.content,
+ type: 'text/plain; charset=utf-8',
+ filename: @snippet.sanitized_file_name
+ )
+ end
+
protected
def snippet
diff --git a/app/finders/trending_projects_finder.rb b/app/finders/trending_projects_finder.rb
deleted file mode 100644
index c1e434d9926..00000000000
--- a/app/finders/trending_projects_finder.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Finder for retrieving public trending projects in a given time range.
-class TrendingProjectsFinder
- # current_user - The currently logged in User, if any.
- # last_months - The number of months to limit the trending data to.
- def execute(months_limit = 1)
- Rails.cache.fetch(cache_key_for(months_limit), expires_in: 1.day) do
- Project.public_only.trending(months_limit.months.ago)
- end
- end
-
- private
-
- def cache_key_for(months)
- "trending_projects/#{months}"
- end
-end
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
new file mode 100644
index 00000000000..b7247ffa8b2
--- /dev/null
+++ b/app/helpers/boards_helper.rb
@@ -0,0 +1,12 @@
+module BoardsHelper
+ def board_data
+ board = @board || @boards.first
+
+ {
+ endpoint: namespace_project_boards_path(@project.namespace, @project),
+ board_id: board.id,
+ disabled: !can?(current_user, :admin_list, @project),
+ issue_link_base: namespace_project_issues_path(@project.namespace, @project)
+ }
+ end
+end
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index b478580978b..85e1dc33ee8 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -15,13 +15,14 @@ module ButtonHelper
#
# See http://clipboardjs.com/#usage
def clipboard_button(data = {})
+ css_class = data[:class] || 'btn-clipboard btn-transparent'
data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
content_tag :button,
icon('clipboard'),
- class: "btn btn-clipboard",
+ class: "btn #{css_class}",
data: data,
type: :button,
- title: "Copy to Clipboard"
+ title: 'Copy to Clipboard'
end
def http_clone_button(project, placement = 'right', append_link: true)
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 8abe7865fed..249cb44e9d5 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -72,6 +72,19 @@ module MergeRequestsHelper
)
end
+ def mr_assign_issues_link
+ issues = MergeRequests::AssignIssuesService.new(@project,
+ current_user,
+ merge_request: @merge_request,
+ closes_issues: mr_closes_issues
+ ).assignable_issues
+ path = assign_related_issues_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+ if issues.present?
+ pluralize_this_issue = issues.count > 1 ? "these issues" : "this issue"
+ link_to "Assign yourself to #{pluralize_this_issue}", path, method: :post
+ end
+ end
+
def source_branch_with_namespace(merge_request)
branch = link_to(merge_request.source_branch, namespace_project_commits_path(merge_request.source_project.namespace, merge_request.source_project, merge_request.source_branch))
@@ -110,4 +123,8 @@ module MergeRequestsHelper
def version_index(merge_request_diff)
@merge_request_diffs.size - @merge_request_diffs.index(merge_request_diff)
end
+
+ def different_base?(version1, version2)
+ version1 && version2 && version1.base_commit_sha != version2.base_commit_sha
+ end
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index bf2d861b86f..042b1c04054 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -3,6 +3,7 @@ module Ci
extend Ci::Model
include HasStatus
include Importable
+ include AfterCommitQueue
self.table_name = 'ci_commits'
@@ -56,6 +57,10 @@ module Ci
pipeline.finished_at = Time.now
end
+ before_transition do |pipeline|
+ pipeline.update_duration
+ end
+
after_transition [:created, :pending] => :running do |pipeline|
MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)).
update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: nil)
@@ -66,8 +71,8 @@ module Ci
update_all(latest_build_finished_at: pipeline.finished_at)
end
- before_transition do |pipeline|
- pipeline.update_duration
+ after_transition [:created, :pending, :running] => :success do |pipeline|
+ pipeline.run_after_commit { PipelineSuccessWorker.perform_async(id) }
end
after_transition do |pipeline, transition|
@@ -292,9 +297,9 @@ module Ci
# Merge requests for which the current pipeline is running against
# the merge request's latest commit.
def merge_requests
- @merge_requests ||=
- project.merge_requests.where(source_branch: ref).
- select { |merge_request| merge_request.pipeline.try(:id) == id }
+ @merge_requests ||= project.merge_requests
+ .where(source_branch: self.ref)
+ .select { |merge_request| merge_request.pipeline.try(:id) == self.id }
end
def merge_requests_with_active_first
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 9fa8d17e74e..7b554be4f9a 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -1,6 +1,7 @@
class CommitStatus < ActiveRecord::Base
include HasStatus
include Importable
+ include AfterCommitQueue
self.table_name = 'ci_builds'
@@ -85,25 +86,24 @@ class CommitStatus < ActiveRecord::Base
end
after_transition do |commit_status, transition|
- commit_status.pipeline.try do |pipeline|
- break if transition.loopback?
-
- if commit_status.complete?
- ProcessPipelineWorker.perform_async(pipeline.id)
+ next if transition.loopback?
+
+ commit_status.run_after_commit do
+ pipeline.try do |pipeline|
+ if complete?
+ PipelineProcessWorker.perform_async(pipeline.id)
+ else
+ PipelineUpdateWorker.perform_async(pipeline.id)
+ end
end
-
- UpdatePipelineWorker.perform_async(pipeline.id)
end
-
- true
- end
-
- after_transition [:created, :pending, :running] => :success do |commit_status|
- MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status)
end
after_transition any => :failed do |commit_status|
- MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.pipeline.project, nil).execute(commit_status)
+ commit_status.run_after_commit do
+ MergeRequests::AddTodoWhenBuildFailsService
+ .new(pipeline.project, nil).execute(self)
+ end
end
end
diff --git a/app/models/compare.rb b/app/models/compare.rb
index 4856510f526..3a8bbcb1acd 100644
--- a/app/models/compare.rb
+++ b/app/models/compare.rb
@@ -11,9 +11,10 @@ class Compare
end
end
- def initialize(compare, project)
+ def initialize(compare, project, straight: false)
@compare = compare
@project = project
+ @straight = straight
end
def commits
@@ -45,6 +46,18 @@ class Compare
end
end
+ def start_commit_sha
+ start_commit.try(:sha)
+ end
+
+ def base_commit_sha
+ base_commit.try(:sha)
+ end
+
+ def head_commit_sha
+ commit.try(:sha)
+ end
+
def raw_diffs(*args)
@compare.diffs(*args)
end
@@ -58,9 +71,9 @@ class Compare
def diff_refs
Gitlab::Diff::DiffRefs.new(
- base_sha: base_commit.try(:sha),
- start_sha: start_commit.try(:sha),
- head_sha: commit.try(:sha)
+ base_sha: @straight ? start_commit_sha : base_commit_sha,
+ start_sha: start_commit_sha,
+ head_sha: head_commit_sha
)
end
end
diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb
index be295487fd2..8ed4a56b19b 100644
--- a/app/models/cycle_analytics.rb
+++ b/app/models/cycle_analytics.rb
@@ -2,6 +2,8 @@ class CycleAnalytics
include Gitlab::Database::Median
include Gitlab::Database::DateTime
+ DEPLOYMENT_METRIC_STAGES = %i[production staging]
+
def initialize(project, from:)
@project = project
@from = from
@@ -66,7 +68,7 @@ class CycleAnalytics
# cycle analytics stage.
interval_query = Arel::Nodes::As.new(
cte_table,
- subtract_datetimes(base_query, end_time_attrs, start_time_attrs, name.to_s))
+ subtract_datetimes(base_query_for(name), end_time_attrs, start_time_attrs, name.to_s))
median_datetime(cte_table, interval_query, name)
end
@@ -75,7 +77,7 @@ class CycleAnalytics
# closes the given issue) with issue and merge request metrics included. The metrics
# are loaded with an inner join, so issues / merge requests without metrics are
# automatically excluded.
- def base_query
+ def base_query_for(name)
arel_table = MergeRequestsClosingIssues.arel_table
# Load issues
@@ -91,7 +93,11 @@ class CycleAnalytics
join(MergeRequest::Metrics.arel_table).
on(MergeRequest.arel_table[:id].eq(MergeRequest::Metrics.arel_table[:merge_request_id]))
- # Limit to merge requests that have been deployed to production after `@from`
- query.where(MergeRequest::Metrics.arel_table[:first_deployed_to_production_at].gteq(@from))
+ if DEPLOYMENT_METRIC_STAGES.include?(name)
+ # Limit to merge requests that have been deployed to production after `@from`
+ query.where(MergeRequest::Metrics.arel_table[:first_deployed_to_production_at].gteq(@from))
+ end
+
+ query
end
end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 82b27b78229..3d9902d496e 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -40,7 +40,14 @@ class Deployment < ActiveRecord::Base
def includes_commit?(commit)
return false unless commit
- project.repository.is_ancestor?(commit.id, sha)
+ # Before 8.10, deployments didn't have keep-around refs. Any deployment
+ # created before then could have a `sha` referring to a commit that no
+ # longer exists in the repository, so just ignore those.
+ begin
+ project.repository.is_ancestor?(commit.id, sha)
+ rescue Rugged::OdbError
+ false
+ end
end
def update_merge_request_metrics!
@@ -77,6 +84,10 @@ class Deployment < ActiveRecord::Base
take
end
+ def formatted_deployment_time
+ created_at.to_time.in_time_zone.to_s(:medium)
+ end
+
private
def ref_path
diff --git a/app/models/environment.rb b/app/models/environment.rb
index f0f3ee23223..d970bc0a005 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -48,7 +48,22 @@ class Environment < ActiveRecord::Base
self.name == "production"
end
+ def first_deployment_for(commit)
+ ref = project.repository.ref_name_for_sha(ref_path, commit.sha)
+
+ return nil unless ref
+
+ deployment_id = ref.split('/').last
+ deployments.find(deployment_id)
+ end
+
def ref_path
"refs/environments/#{Shellwords.shellescape(name)}"
end
+
+ def formatted_external_url
+ return nil unless external_url
+
+ external_url.gsub(/\A.*?:\/\//, '')
+ end
end
diff --git a/app/models/event.rb b/app/models/event.rb
index 314d5ba438f..0764cb8cabd 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -68,8 +68,10 @@ class Event < ActiveRecord::Base
true
elsif issue? || issue_note?
Ability.allowed?(user, :read_issue, note? ? note_target : target)
+ elsif merge_request? || merge_request_note?
+ Ability.allowed?(user, :read_merge_request, note? ? note_target : target)
else
- ((merge_request? || note?) && target.present?) || milestone?
+ milestone?
end
end
@@ -280,6 +282,10 @@ class Event < ActiveRecord::Base
note? && target && target.for_issue?
end
+ def merge_request_note?
+ note? && target && target.for_merge_request?
+ end
+
def project_snippet_note?
target.for_snippet?
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 852317bbed1..3efb7aead77 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -692,12 +692,15 @@ class MergeRequest < ActiveRecord::Base
def environments
return [] unless diff_head_commit
- environments = source_project.environments_for(
- source_branch, diff_head_commit)
- environments += target_project.environments_for(
- target_branch, diff_head_commit, with_tags: true)
-
- environments.uniq
+ @environments ||=
+ begin
+ environments = source_project.environments_for(
+ source_branch, diff_head_commit)
+ environments += target_project.environments_for(
+ target_branch, diff_head_commit, with_tags: true)
+
+ environments.uniq
+ end
end
def state_human_name
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 36b8b70870b..b8a10b7968e 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -6,6 +6,9 @@ class MergeRequestDiff < ActiveRecord::Base
# Prevent store of diff if commits amount more then 500
COMMITS_SAFE_SIZE = 100
+ # Valid types of serialized diffs allowed by Gitlab::Git::Diff
+ VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta]
+
belongs_to :merge_request
state_machine :state, initial: :empty do
@@ -164,12 +167,24 @@ class MergeRequestDiff < ActiveRecord::Base
self == merge_request.merge_request_diff
end
- def compare_with(sha)
- CompareService.new.execute(project, head_commit_sha, project, sha)
+ def compare_with(sha, straight: true)
+ # When compare merge request versions we want diff A..B instead of A...B
+ # so we handle cases when user does squash and rebase of the commits between versions.
+ # For this reason we set straight to true by default.
+ CompareService.new.execute(project, head_commit_sha, project, sha, straight: straight)
end
private
+ # Old GitLab implementations may have generated diffs as ["--broken-diff"].
+ # Avoid an error 500 by ignoring bad elements. See:
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/20776
+ def valid_raw_diff?(raw)
+ return false unless raw.respond_to?(:each)
+
+ raw.any? { |element| VALID_CLASSES.include?(element.class) }
+ end
+
def dump_commits(commits)
commits.map(&:to_hash)
end
@@ -200,7 +215,7 @@ class MergeRequestDiff < ActiveRecord::Base
end
def load_diffs(raw, options)
- if raw.respond_to?(:each)
+ if valid_raw_diff?(raw)
if paths = options[:paths]
raw = raw.select do |diff|
paths.include?(diff[:old_path]) || paths.include?(diff[:new_path])
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index b7f2b2bbe61..b67049f0f55 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -61,15 +61,13 @@ class Namespace < ActiveRecord::Base
def clean_path(path)
path = path.dup
# Get the email username by removing everything after an `@` sign.
- path.gsub!(/@.*\z/, "")
- # Usernames can't end in .git, so remove it.
- path.gsub!(/\.git\z/, "")
- # Remove dashes at the start of the username.
- path.gsub!(/\A-+/, "")
- # Remove periods at the end of the username.
- path.gsub!(/\.+\z/, "")
+ path.gsub!(/@.*\z/, "")
# Remove everything that's not in the list of allowed characters.
- path.gsub!(/[^a-zA-Z0-9_\-\.]/, "")
+ path.gsub!(/[^a-zA-Z0-9_\-\.]/, "")
+ # Remove trailing violations ('.atom', '.git', or '.')
+ path.gsub!(/(\.atom|\.git|\.)*\z/, "")
+ # Remove leading violations ('-')
+ path.gsub!(/\A\-+/, "")
# Users with the great usernames of "." or ".." would end up with a blank username.
# Work around that by setting their username to "blank", followed by a counter.
diff --git a/app/models/project.rb b/app/models/project.rb
index a89ad9bca1d..9e68b0016e0 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -16,6 +16,9 @@ class Project < ActiveRecord::Base
extend Gitlab::ConfigHelper
+ class BoardLimitExceeded < StandardError; end
+
+ NUMBER_OF_PERMITTED_BOARDS = 1
UNKNOWN_IMPORT_URL = 'http://unknown.git'
cache_markdown_field :description, pipeline: :description
@@ -65,8 +68,7 @@ class Project < ActiveRecord::Base
belongs_to :namespace
has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id'
-
- has_one :board, dependent: :destroy
+ has_many :boards, before_add: :validate_board_limit, dependent: :destroy
# Project services
has_many :services
@@ -376,19 +378,9 @@ class Project < ActiveRecord::Base
%r{(?<project>#{name_pattern}/#{name_pattern})}
end
- def trending(since = 1.month.ago)
- # By counting in the JOIN we don't expose the GROUP BY to the outer query.
- # This means that calls such as "any?" and "count" just return a number of
- # the total count, instead of the counts grouped per project as a Hash.
- join_body = "INNER JOIN (
- SELECT project_id, COUNT(*) AS amount
- FROM notes
- WHERE created_at >= #{sanitize(since)}
- AND system IS FALSE
- GROUP BY project_id
- ) join_note_counts ON projects.id = join_note_counts.project_id"
-
- joins(join_body).reorder('join_note_counts.amount DESC')
+ def trending
+ joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id').
+ reorder('trending_projects.id ASC')
end
def cached_count
@@ -838,11 +830,6 @@ class Project < ActiveRecord::Base
end
end
- def update_merge_requests(oldrev, newrev, ref, user)
- MergeRequests::RefreshService.new(self, user).
- execute(oldrev, newrev, ref)
- end
-
def valid_repo?
repository.exists?
rescue
@@ -1350,4 +1337,8 @@ class Project < ActiveRecord::Base
shared_projects.any?
end
+
+ def validate_board_limit(board)
+ raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS
+ end
end
diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb
index 7613cbdea93..db46def11eb 100644
--- a/app/models/project_group_link.rb
+++ b/app/models/project_group_link.rb
@@ -10,7 +10,7 @@ class ProjectGroupLink < ActiveRecord::Base
belongs_to :group
validates :project_id, presence: true
- validates :group_id, presence: true
+ validates :group, presence: true
validates :group_id, uniqueness: { scope: [:project_id], message: "already shared with this group" }
validates :group_access, presence: true
validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true
diff --git a/app/models/repository.rb b/app/models/repository.rb
index bf59b74495b..72e473871fa 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -111,8 +111,10 @@ class Repository
def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
ref ||= root_ref
- # Limited to 1000 commits for now, could be parameterized?
- args = %W(#{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset} --max-count #{limit} --grep=#{query})
+ args = %W(
+ #{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset}
+ --max-count #{limit} --grep=#{query} --regexp-ignore-case
+ )
args = args.concat(%W(-- #{path})) if path.present?
git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:chomp)
@@ -717,6 +719,14 @@ class Repository
end
end
+ def ref_name_for_sha(ref_path, sha)
+ args = %W(#{Gitlab.config.git.bin_path} for-each-ref --count=1 #{ref_path} --contains #{sha})
+
+ # Not found -> ["", 0]
+ # Found -> ["b8d95eb4969eefacb0a58f6a28f6803f8070e7b9 commit\trefs/environments/production/77\n", 0]
+ Gitlab::Popen.popen(args, path_to_repo).first.split.last
+ end
+
def refs_contains_sha(ref_type, sha)
args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
names = Gitlab::Popen.popen(args, path_to_repo).first
@@ -1014,7 +1024,8 @@ class Repository
root_ref_commit = commit(root_ref)
if branch_commit
- is_ancestor?(branch_commit.id, root_ref_commit.id)
+ same_head = branch_commit.id == root_ref_commit.id
+ !same_head && is_ancestor?(branch_commit.id, root_ref_commit.id)
else
nil
end
diff --git a/app/models/trending_project.rb b/app/models/trending_project.rb
new file mode 100644
index 00000000000..27e3732da17
--- /dev/null
+++ b/app/models/trending_project.rb
@@ -0,0 +1,35 @@
+class TrendingProject < ActiveRecord::Base
+ belongs_to :project
+
+ # The number of months to include in the trending calculation.
+ MONTHS_TO_INCLUDE = 1
+
+ # The maximum number of projects to include in the trending set.
+ PROJECTS_LIMIT = 100
+
+ # Populates the trending projects table with the current list of trending
+ # projects.
+ def self.refresh!
+ # The calculation **must** run in a transaction. If the removal of data and
+ # insertion of new data were to run separately a user might end up with an
+ # empty list of trending projects for a short period of time.
+ transaction do
+ delete_all
+
+ timestamp = connection.quote(MONTHS_TO_INCLUDE.months.ago)
+
+ connection.execute <<-EOF.strip_heredoc
+ INSERT INTO #{table_name} (project_id)
+ SELECT project_id
+ FROM notes
+ INNER JOIN projects ON projects.id = notes.project_id
+ WHERE notes.created_at >= #{timestamp}
+ AND notes.system IS FALSE
+ AND projects.visibility_level = #{Gitlab::VisibilityLevel::PUBLIC}
+ GROUP BY project_id
+ ORDER BY count(*) DESC
+ LIMIT #{PROJECTS_LIMIT};
+ EOF
+ end
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 892ac28d5b3..f367f4616fb 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -589,6 +589,11 @@ class User < ActiveRecord::Base
end
def set_projects_limit
+ # `User.select(:id)` raises
+ # `ActiveModel::MissingAttributeError: missing attribute: projects_limit`
+ # without this safeguard!
+ return unless self.has_attribute?(:projects_limit)
+
connection_default_value_defined = new_record? && !projects_limit_changed?
return unless self.projects_limit.nil? || connection_default_value_defined
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index be25c750d67..be4721d7a51 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -40,7 +40,6 @@ class ProjectPolicy < BasePolicy
can! :read_milestone
can! :read_project_snippet
can! :read_project_member
- can! :read_merge_request
can! :read_note
can! :create_project
can! :create_issue
@@ -63,6 +62,7 @@ class ProjectPolicy < BasePolicy
can! :read_pipeline
can! :read_environment
can! :read_deployment
+ can! :read_merge_request
end
# Permissions given when an user is team member of a project
@@ -98,7 +98,6 @@ class ProjectPolicy < BasePolicy
can! :admin_milestone
can! :admin_project_snippet
can! :admin_project_member
- can! :admin_merge_request
can! :admin_note
can! :admin_wiki
can! :admin_project
@@ -118,6 +117,7 @@ class ProjectPolicy < BasePolicy
can! :read_container_image
can! :build_download_code
can! :build_read_container_image
+ can! :read_merge_request
end
def owner_access!
@@ -139,11 +139,18 @@ class ProjectPolicy < BasePolicy
def team_access!(user)
access = project.team.max_member_access(user.id)
- guest_access! if access >= Gitlab::Access::GUEST
- reporter_access! if access >= Gitlab::Access::REPORTER
- team_member_reporter_access! if access >= Gitlab::Access::REPORTER
- developer_access! if access >= Gitlab::Access::DEVELOPER
- master_access! if access >= Gitlab::Access::MASTER
+ return if access < Gitlab::Access::GUEST
+ guest_access!
+
+ return if access < Gitlab::Access::REPORTER
+ reporter_access!
+ team_member_reporter_access!
+
+ return if access < Gitlab::Access::DEVELOPER
+ developer_access!
+
+ return if access < Gitlab::Access::MASTER
+ master_access!
end
def archived_access!
diff --git a/app/services/boards/base_service.rb b/app/services/boards/base_service.rb
deleted file mode 100644
index b2069ca825a..00000000000
--- a/app/services/boards/base_service.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-module Boards
- class BaseService < ::BaseService
- delegate :board, to: :project
- end
-end
diff --git a/app/services/boards/create_service.rb b/app/services/boards/create_service.rb
index 072a0749285..9bdd7b6f0cf 100644
--- a/app/services/boards/create_service.rb
+++ b/app/services/boards/create_service.rb
@@ -1,16 +1,21 @@
module Boards
- class CreateService < Boards::BaseService
+ class CreateService < BaseService
def execute
- create_board! unless project.board.present?
- project.board
+ if project.boards.empty?
+ create_board!
+ else
+ project.boards.first
+ end
end
private
def create_board!
- project.create_board
- project.board.lists.create(list_type: :backlog)
- project.board.lists.create(list_type: :done)
+ board = project.boards.create
+ board.lists.create(list_type: :backlog)
+ board.lists.create(list_type: :done)
+
+ board
end
end
end
diff --git a/app/services/boards/issues/create_service.rb b/app/services/boards/issues/create_service.rb
index 3701afd441f..c0d7ff5b585 100644
--- a/app/services/boards/issues/create_service.rb
+++ b/app/services/boards/issues/create_service.rb
@@ -1,14 +1,21 @@
module Boards
module Issues
- class CreateService < Boards::BaseService
- def execute(list)
- params.merge!(label_ids: [list.label_id])
- create_issue
+ class CreateService < BaseService
+ def execute
+ create_issue(params.merge(label_ids: [list.label_id]))
end
private
- def create_issue
+ def board
+ @board ||= project.boards.find(params.delete(:board_id))
+ end
+
+ def list
+ @list ||= board.lists.find(params.delete(:list_id))
+ end
+
+ def create_issue(params)
::Issues::CreateService.new(project, current_user, params).execute
end
end
diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb
index 435a8c6e681..fd4a462c7b2 100644
--- a/app/services/boards/issues/list_service.rb
+++ b/app/services/boards/issues/list_service.rb
@@ -1,6 +1,6 @@
module Boards
module Issues
- class ListService < Boards::BaseService
+ class ListService < BaseService
def execute
issues = IssuesFinder.new(current_user, filter_params).execute
issues = without_board_labels(issues) unless list.movable?
@@ -10,6 +10,10 @@ module Boards
private
+ def board
+ @board ||= project.boards.find(params[:board_id])
+ end
+
def list
@list ||= board.lists.find(params[:id])
end
diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb
index 84dc3f70e76..96554a92a02 100644
--- a/app/services/boards/issues/move_service.rb
+++ b/app/services/boards/issues/move_service.rb
@@ -1,6 +1,6 @@
module Boards
module Issues
- class MoveService < Boards::BaseService
+ class MoveService < BaseService
def execute(issue)
return false unless can?(current_user, :update_issue, issue)
return false unless valid_move?
@@ -10,6 +10,10 @@ module Boards
private
+ def board
+ @board ||= project.boards.find(params[:board_id])
+ end
+
def valid_move?
moving_from_list.present? && moving_to_list.present? &&
moving_from_list != moving_to_list
@@ -49,7 +53,7 @@ module Boards
if moving_to_list.movable?
moving_from_list.label_id
else
- board.lists.movable.pluck(:label_id)
+ project.boards.joins(:lists).merge(List.movable).pluck(:label_id)
end
Array(label_ids).compact
diff --git a/app/services/boards/list_service.rb b/app/services/boards/list_service.rb
new file mode 100644
index 00000000000..84f1fc3a4e2
--- /dev/null
+++ b/app/services/boards/list_service.rb
@@ -0,0 +1,14 @@
+module Boards
+ class ListService < BaseService
+ def execute
+ create_board! if project.boards.empty?
+ project.boards
+ end
+
+ private
+
+ def create_board!
+ Boards::CreateService.new(project, current_user).execute
+ end
+ end
+end
diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb
index b1887820bd4..abc7aeece39 100644
--- a/app/services/boards/lists/create_service.rb
+++ b/app/services/boards/lists/create_service.rb
@@ -1,23 +1,23 @@
module Boards
module Lists
- class CreateService < Boards::BaseService
- def execute
+ class CreateService < BaseService
+ def execute(board)
List.transaction do
label = project.labels.find(params[:label_id])
- position = next_position
+ position = next_position(board)
- create_list(label, position)
+ create_list(board, label, position)
end
end
private
- def next_position
+ def next_position(board)
max_position = board.lists.movable.maximum(:position)
max_position.nil? ? 0 : max_position.succ
end
- def create_list(label, position)
+ def create_list(board, label, position)
board.lists.create(label: label, list_type: :label, position: position)
end
end
diff --git a/app/services/boards/lists/destroy_service.rb b/app/services/boards/lists/destroy_service.rb
index 25da3bfb56d..f986e05944c 100644
--- a/app/services/boards/lists/destroy_service.rb
+++ b/app/services/boards/lists/destroy_service.rb
@@ -1,9 +1,11 @@
module Boards
module Lists
- class DestroyService < Boards::BaseService
+ class DestroyService < BaseService
def execute(list)
return false unless list.destroyable?
+ @board = list.board
+
list.with_lock do
decrement_higher_lists(list)
remove_list(list)
@@ -12,6 +14,8 @@ module Boards
private
+ attr_reader :board
+
def decrement_higher_lists(list)
board.lists.movable.where('position > ?', list.position)
.update_all('position = position - 1')
diff --git a/app/services/boards/lists/generate_service.rb b/app/services/boards/lists/generate_service.rb
index 830e386c98b..d8048f1c67e 100644
--- a/app/services/boards/lists/generate_service.rb
+++ b/app/services/boards/lists/generate_service.rb
@@ -1,11 +1,11 @@
module Boards
module Lists
- class GenerateService < Boards::BaseService
- def execute
+ class GenerateService < BaseService
+ def execute(board)
return false unless board.lists.movable.empty?
List.transaction do
- label_params.each { |params| create_list(params) }
+ label_params.each { |params| create_list(board, params) }
end
true
@@ -13,9 +13,9 @@ module Boards
private
- def create_list(params)
+ def create_list(board, params)
label = find_or_create_label(params)
- Lists::CreateService.new(project, current_user, label_id: label.id).execute
+ Lists::CreateService.new(project, current_user, label_id: label.id).execute(board)
end
def find_or_create_label(params)
diff --git a/app/services/boards/lists/list_service.rb b/app/services/boards/lists/list_service.rb
new file mode 100644
index 00000000000..c579ed4c869
--- /dev/null
+++ b/app/services/boards/lists/list_service.rb
@@ -0,0 +1,9 @@
+module Boards
+ module Lists
+ class ListService < BaseService
+ def execute(board)
+ board.lists
+ end
+ end
+ end
+end
diff --git a/app/services/boards/lists/move_service.rb b/app/services/boards/lists/move_service.rb
index 020ff69f4a7..f2a68865f7b 100644
--- a/app/services/boards/lists/move_service.rb
+++ b/app/services/boards/lists/move_service.rb
@@ -1,7 +1,8 @@
module Boards
module Lists
- class MoveService < Boards::BaseService
+ class MoveService < BaseService
def execute(list)
+ @board = list.board
@old_position = list.position
@new_position = params[:position]
@@ -16,7 +17,7 @@ module Boards
private
- attr_reader :old_position, :new_position
+ attr_reader :board, :old_position, :new_position
def valid_move?
new_position.present? && new_position != old_position &&
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index 36c93dddadb..d3dd30b2588 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -16,6 +16,8 @@ module Ci
process_stage(index)
end
+ @pipeline.update_status
+
# Return a flag if a when builds got enqueued
new_builds.flatten.any?
end
diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb
index 6d6075628af..5e8fafca98c 100644
--- a/app/services/compare_service.rb
+++ b/app/services/compare_service.rb
@@ -3,7 +3,7 @@ require 'securerandom'
# Compare 2 branches for one repo or between repositories
# and return Gitlab::Git::Compare object that responds to commits and diffs
class CompareService
- def execute(source_project, source_branch, target_project, target_branch)
+ def execute(source_project, source_branch, target_project, target_branch, straight: false)
source_commit = source_project.commit(source_branch)
return unless source_commit
@@ -23,9 +23,10 @@ class CompareService
raw_compare = Gitlab::Git::Compare.new(
target_project.repository.raw_repository,
target_branch,
- source_sha
+ source_sha,
+ straight
)
- Compare.new(raw_compare, target_project)
+ Compare.new(raw_compare, target_project, straight: straight)
end
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index c499427605a..e8415862de5 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -63,13 +63,12 @@ class GitPushService < BaseService
protected
def update_merge_requests
- @project.update_merge_requests(params[:oldrev], params[:newrev], params[:ref], current_user)
+ UpdateMergeRequestsWorker.perform_async(@project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref])
EventCreateService.new.push(@project, current_user, build_push_data)
- SystemHooksService.new.execute_hooks(build_push_data_system_hook.dup, :push_hooks)
@project.execute_hooks(build_push_data.dup, :push_hooks)
@project.execute_services(build_push_data.dup, :push_hooks)
- Ci::CreatePipelineService.new(project, current_user, build_push_data).execute
+ Ci::CreatePipelineService.new(@project, current_user, build_push_data).execute
ProjectCacheWorker.perform_async(@project.id)
end
@@ -148,16 +147,6 @@ class GitPushService < BaseService
push_commits)
end
- def build_push_data_system_hook
- @push_data_system ||= Gitlab::DataBuilder::Push.build(
- @project,
- current_user,
- params[:oldrev],
- params[:newrev],
- params[:ref],
- [])
- end
-
def push_to_existing_branch?
# Return if this is not a push to a branch (e.g. new commits)
Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev])
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 566049525cb..d572a928a42 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
@@ -2,14 +2,14 @@ module MergeRequests
class AddTodoWhenBuildFailsService < MergeRequests::BaseService
# Adds a todo to the parent merge_request when a CI build fails
def execute(commit_status)
- each_merge_request(commit_status) do |merge_request|
+ commit_status_merge_requests(commit_status) do |merge_request|
todo_service.merge_request_build_failed(merge_request)
end
end
# Closes any pending build failed todos for the parent MRs when a build is retried
def close(commit_status)
- each_merge_request(commit_status) do |merge_request|
+ commit_status_merge_requests(commit_status) do |merge_request|
todo_service.merge_request_build_retried(merge_request)
end
end
diff --git a/app/services/merge_requests/assign_issues_service.rb b/app/services/merge_requests/assign_issues_service.rb
new file mode 100644
index 00000000000..f636e5fec4f
--- /dev/null
+++ b/app/services/merge_requests/assign_issues_service.rb
@@ -0,0 +1,35 @@
+module MergeRequests
+ class AssignIssuesService < BaseService
+ def assignable_issues
+ @assignable_issues ||= begin
+ if current_user == merge_request.author
+ closes_issues.select do |issue|
+ !issue.assignee_id? && can?(current_user, :admin_issue, issue)
+ end
+ else
+ []
+ end
+ end
+ end
+
+ def execute
+ assignable_issues.each do |issue|
+ Issues::UpdateService.new(issue.project, current_user, assignee_id: current_user.id).execute(issue)
+ end
+
+ {
+ count: assignable_issues.count
+ }
+ end
+
+ private
+
+ def merge_request
+ params[:merge_request]
+ end
+
+ def closes_issues
+ @closes_issues ||= params[:closes_issues] || merge_request.closes_issues(current_user)
+ end
+ end
+end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index d0d155b7ee1..58f69a41e14 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -42,28 +42,33 @@ module MergeRequests
super(:merge_request)
end
- def merge_request_from(commit_status)
- branches = commit_status.ref
+ def merge_requests_for(branch)
+ origin_merge_requests = @project.origin_merge_requests
+ .opened.where(source_branch: branch).to_a
- # This is for ref-less builds
- branches ||= @project.repository.branch_names_contains(commit_status.sha)
+ fork_merge_requests = @project.fork_merge_requests
+ .opened.where(source_branch: branch).to_a
- return [] if branches.blank?
+ (origin_merge_requests + fork_merge_requests)
+ .uniq.select(&:source_project)
+ end
- merge_requests = @project.origin_merge_requests.opened.where(source_branch: branches).to_a
- merge_requests += @project.fork_merge_requests.opened.where(source_branch: branches).to_a
+ def pipeline_merge_requests(pipeline)
+ merge_requests_for(pipeline.ref).each do |merge_request|
+ next unless pipeline == merge_request.pipeline
- merge_requests.uniq.select(&:source_project)
+ yield merge_request
+ end
end
- def each_merge_request(commit_status)
- merge_request_from(commit_status).each do |merge_request|
+ def commit_status_merge_requests(commit_status)
+ merge_requests_for(commit_status.ref).each do |merge_request|
pipeline = merge_request.pipeline
next unless pipeline
next unless pipeline.sha == commit_status.sha
- yield merge_request, pipeline
+ yield merge_request
end
end
end
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index e57791f6818..404f75616b5 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -4,7 +4,7 @@ module MergeRequests
merge_request = MergeRequest.new(params)
# Set MR attributes
- merge_request.can_be_created = false
+ merge_request.can_be_created = true
merge_request.compare_commits = []
merge_request.source_project = project unless merge_request.source_project
@@ -22,6 +22,12 @@ module MergeRequests
return build_failed(merge_request, message)
end
+ if merge_request.source_project == merge_request.target_project &&
+ merge_request.target_branch == merge_request.source_branch
+
+ return build_failed(merge_request, 'You must select different branches')
+ end
+
compare = CompareService.new.execute(
merge_request.source_project,
merge_request.source_branch,
@@ -29,17 +35,8 @@ module MergeRequests
merge_request.target_branch,
)
- commits = compare.commits
-
- # At this point we decide if merge request can be created
- # If we have at least one commit to merge -> creation allowed
- if commits.present?
- merge_request.compare_commits = commits
- merge_request.can_be_created = true
- merge_request.compare = compare
- else
- merge_request.can_be_created = false
- end
+ merge_request.compare_commits = compare.commits
+ merge_request.compare = compare
set_title_and_description(merge_request)
end
@@ -89,6 +86,8 @@ module MergeRequests
end
end
+ merge_request.title = merge_request.wip_title if commits.empty?
+
merge_request
end
diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb
index 4ad5fb08311..dc159de0058 100644
--- a/app/services/merge_requests/merge_when_build_succeeds_service.rb
+++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb
@@ -18,12 +18,13 @@ module MergeRequests
merge_request.save
end
- # Triggers the automatic merge of merge_request once the build succeeds
- def trigger(commit_status)
- each_merge_request(commit_status) do |merge_request, pipeline|
+ # Triggers the automatic merge of merge_request once the pipeline succeeds
+ def trigger(pipeline)
+ return unless pipeline.success?
+
+ pipeline_merge_requests(pipeline) do |merge_request|
next unless merge_request.merge_when_build_succeeds?
next unless merge_request.mergeable?
- next unless pipeline.success?
MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params)
end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 9dbec49d163..a37cc3fdf21 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -15,7 +15,10 @@ module MergeRequests
params.except!(:target_branch, :force_remove_source_branch)
end
- merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
+ if params[:force_remove_source_branch].present?
+ merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
+ end
+
handle_wip_event(merge_request)
update(merge_request)
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index de8049b8e2e..72712afc07e 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -475,10 +475,12 @@ class NotificationService
end
def reject_users_without_access(recipients, target)
- return recipients unless target.is_a?(Issue)
+ return recipients unless target.is_a?(Issuable)
+
+ ability = :"read_#{target.to_ability_name}"
recipients.select do |user|
- user.can?(:read_issue, target)
+ user.can?(ability, target)
end
end
diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb
index 1725a30fae5..e4ae3dec8aa 100644
--- a/app/services/slash_commands/interpret_service.rb
+++ b/app/services/slash_commands/interpret_service.rb
@@ -122,7 +122,12 @@ module SlashCommands
command :label do |labels_param|
label_ids = find_label_ids(labels_param)
- @updates[:add_label_ids] = label_ids unless label_ids.empty?
+ if label_ids.any?
+ @updates[:add_label_ids] ||= []
+ @updates[:add_label_ids] += label_ids
+
+ @updates[:add_label_ids].uniq!
+ end
end
desc 'Remove all or specific label(s)'
@@ -136,7 +141,12 @@ module SlashCommands
if labels_param.present?
label_ids = find_label_ids(labels_param)
- @updates[:remove_label_ids] = label_ids unless label_ids.empty?
+ if label_ids.any?
+ @updates[:remove_label_ids] ||= []
+ @updates[:remove_label_ids] += label_ids
+
+ @updates[:remove_label_ids].uniq!
+ end
else
@updates[:label_ids] = []
end
@@ -152,7 +162,12 @@ module SlashCommands
command :relabel do |labels_param|
label_ids = find_label_ids(labels_param)
- @updates[:label_ids] = label_ids unless label_ids.empty?
+ if label_ids.any?
+ @updates[:label_ids] ||= []
+ @updates[:label_ids] += label_ids
+
+ @updates[:label_ids].uniq!
+ end
end
desc 'Add a todo'
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 776530ac0a5..f8e6b2ef094 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -273,12 +273,12 @@ class TodoService
end
def reject_users_without_access(users, project, target)
- if target.is_a?(Note) && target.for_issue?
+ if target.is_a?(Note) && (target.for_issue? || target.for_merge_request?)
target = target.noteable
end
- if target.is_a?(Issue)
- select_users(users, :read_issue, target)
+ if target.is_a?(Issuable)
+ select_users(users, :"read_#{target.to_ability_name}", target)
else
select_users(users, :read_project, project)
end
diff --git a/app/validators/namespace_validator.rb b/app/validators/namespace_validator.rb
index 4dc3b2ab9a0..2821ecf0a88 100644
--- a/app/validators/namespace_validator.rb
+++ b/app/validators/namespace_validator.rb
@@ -24,6 +24,7 @@ class NamespaceValidator < ActiveModel::EachValidator
projects
public
repository
+ robots.txt
s
search
services
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 0d79ca7dc52..c4c68cd7891 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -221,7 +221,11 @@
%fieldset
%legend Metrics
%p
- These settings require a restart to take effect.
+ Setup InfluxDB to measure a wide variety of statistics like the time spent
+ in running SQL queries. These settings require a
+ = link_to 'restart', help_page_path('administration/restart_gitlab')
+ to take effect.
+ = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/introduction')
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index e44a2bfed9d..99a58bbb676 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -116,4 +116,4 @@
-# Shortcut to issue boards
%li.hidden
- = link_to 'Issue Boards', namespace_project_board_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
+ = link_to 'Issue Boards', namespace_project_boards_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
diff --git a/app/views/projects/boards/index.html.haml b/app/views/projects/boards/index.html.haml
new file mode 100644
index 00000000000..885f8e34b55
--- /dev/null
+++ b/app/views/projects/boards/index.html.haml
@@ -0,0 +1,16 @@
+- @no_container = true
+- @content_class = "issue-boards-content"
+- page_title "Boards"
+
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('boards/boards_bundle.js')
+ = page_specific_javascript_tag('boards/test_utils/simulate_drag.js') if Rails.env.test?
+
+= render "projects/issues/head"
+
+= render 'shared/issuable/filter', type: :boards
+
+.boards-list#board-app{ "v-cloak" => true, data: board_data }
+ .boards-app-loading.text-center{ "v-if" => "loading" }
+ = icon("spinner spin")
+ = render "projects/boards/components/board"
diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml
index edbbd3f3d2a..885f8e34b55 100644
--- a/app/views/projects/boards/show.html.haml
+++ b/app/views/projects/boards/show.html.haml
@@ -10,10 +10,7 @@
= render 'shared/issuable/filter', type: :boards
-.boards-list#board-app{ "v-cloak" => true,
- "data-endpoint" => "#{namespace_project_board_path(@project.namespace, @project)}",
- "data-disabled" => "#{!can?(current_user, :admin_list, @project)}",
- "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" }
+.boards-list#board-app{ "v-cloak" => true, data: board_data }
.boards-app-loading.text-center{ "v-if" => "loading" }
= icon("spinner spin")
= render "projects/boards/components/board"
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 5217b8bf028..4480b2f22c3 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -30,8 +30,8 @@
= render 'projects/buttons/download', project: @project, ref: branch.name
- - if can_remove_branch?(@project, branch.name)
- = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
+ - if can?(current_user, :push_code, @project)
+ = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: "btn btn-remove remove-row has-tooltip #{can_remove_branch?(@project, branch.name) ? '' : 'disabled'}", title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")
- if branch.name != @repository.root_ref
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index f5344091cae..966633f1f89 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -128,7 +128,7 @@
- builds.select{|build| build.status == build_status}.each do |build|
.build-job{class: ('active' if build == @build), data: {stage: build.stage}}
= link_to namespace_project_build_path(@project.namespace, @project, build) do
- = icon('right-arrow')
+ = icon('arrow-right')
= ci_icon_for_status(build.status)
%span
- if build.name
diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml
index 547bc0c9c19..017d3ff6af2 100644
--- a/app/views/projects/ci/builds/_build_pipeline.html.haml
+++ b/app/views/projects/ci/builds/_build_pipeline.html.haml
@@ -5,8 +5,10 @@
.ci-status-text= subject.name
- elsif can?(current_user, :read_build, @project)
= link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do
- = render_status_with_link('build', subject.status)
+ %span.ci-status-icon
+ = render_status_with_link('build', subject.status)
.ci-status-text= subject.name
- else
- = render_status_with_link('build', subject.status)
+ %span.ci-status-icon
+ = render_status_with_link('build', subject.status)
= ci_icon_for_status(subject.status)
diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml
index da5b9832ba5..288c06d9b67 100644
--- a/app/views/projects/commit/_pipeline.html.haml
+++ b/app/views/projects/commit/_pipeline.html.haml
@@ -1,45 +1,46 @@
-.row-content-block.build-content.middle-block.pipeline-actions
- .pull-right
- .btn.btn-grouped.btn-white.toggle-pipeline-btn
- %span.toggle-btn-text Hide
- %span pipeline graph
- = icon('caret-up')
- - if can?(current_user, :update_pipeline, pipeline.project)
- - if pipeline.builds.latest.failed.any?(&:retryable?)
- = link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post
+.pipeline-graph-container
+ .row-content-block.build-content.middle-block.pipeline-actions
+ .pull-right
+ .btn.btn-grouped.btn-white.toggle-pipeline-btn
+ %span.toggle-btn-text Hide
+ %span pipeline graph
+ %span.caret
+ - if can?(current_user, :update_pipeline, pipeline.project)
+ - if pipeline.builds.latest.failed.any?(&:retryable?)
+ = link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post
- - if pipeline.builds.running_or_pending.any?
- = link_to "Cancel running", cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
+ - if pipeline.builds.running_or_pending.any?
+ = link_to "Cancel running", cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
- .oneline.clearfix
- - if defined?(pipeline_details) && pipeline_details
- Pipeline
- = link_to "##{pipeline.id}", namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "monospace"
- with
- = pluralize pipeline.statuses.count(:id), "build"
- - if pipeline.ref
- for
- = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace"
- - if defined?(link_to_commit) && link_to_commit
- for commit
- = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "monospace"
- - if pipeline.duration
- in
- = time_interval_in_words pipeline.duration
+ .oneline.clearfix
+ - if defined?(pipeline_details) && pipeline_details
+ Pipeline
+ = link_to "##{pipeline.id}", namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "monospace"
+ with
+ = pluralize pipeline.statuses.count(:id), "build"
+ - if pipeline.ref
+ for
+ = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace"
+ - if defined?(link_to_commit) && link_to_commit
+ for commit
+ = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "monospace"
+ - if pipeline.duration
+ in
+ = time_interval_in_words pipeline.duration
-.row-content-block.build-content.middle-block.pipeline-graph
- .pipeline-visualization
- %ul.stage-column-list
- - stages = pipeline.stages_with_latest_statuses
- - stages.each do |stage, statuses|
- %li.stage-column
- .stage-name
- %a{name: stage}
- - if stage
- = stage.titleize
- .builds-container
- %ul
- = render "projects/commit/pipeline_stage", statuses: statuses
+ .row-content-block.build-content.middle-block.pipeline-graph.hidden
+ .pipeline-visualization
+ %ul.stage-column-list
+ - stages = pipeline.stages_with_latest_statuses
+ - stages.each do |stage, statuses|
+ %li.stage-column
+ .stage-name
+ %a{name: stage}
+ - if stage
+ = stage.titleize
+ .builds-container
+ %ul
+ = render "projects/commit/pipeline_stage", statuses: statuses
- if pipeline.yaml_errors.present?
diff --git a/app/views/projects/commit/_pipeline_stage.html.haml b/app/views/projects/commit/_pipeline_stage.html.haml
index 23c5c51fbc2..289aa5178b1 100644
--- a/app/views/projects/commit/_pipeline_stage.html.haml
+++ b/app/views/projects/commit/_pipeline_stage.html.haml
@@ -10,5 +10,5 @@
- else
%li.build
.curve
- .build-content
+ .dropdown.inline.build-content
= render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses
diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml
index 4e7a6f1af08..5d0d5ba0262 100644
--- a/app/views/projects/commit/_pipeline_status_group.html.haml
+++ b/app/views/projects/commit/_pipeline_status_group.html.haml
@@ -1,11 +1,13 @@
- group_status = CommitStatus.where(id: subject).status
-= render_status_with_link('build', group_status)
-.dropdown.inline
- %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } }
- %span.ci-status-text
- = name
- %span.badge= subject.size
- %ul.dropdown-menu.grouped-pipeline-dropdown
- .arrow
+%button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } }
+ %span.ci-status-icon
+ = render_status_with_link('build', group_status)
+ %span.ci-status-text
+ = name
+ %span.badge= subject.size
+.dropdown-menu.grouped-pipeline-dropdown
+ .arrow
+ %ul
- subject.each do |status|
- = render "projects/#{status.to_partial_path}_pipeline", subject: status
+ %li
+ = render "projects/#{status.to_partial_path}_pipeline", subject: status
diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml
index e9ff8e90dd5..45be6581cfc 100644
--- a/app/views/projects/compare/index.html.haml
+++ b/app/views/projects/compare/index.html.haml
@@ -4,7 +4,7 @@
%div{ class: container_class }
.sub-header-block
- Compare branches, tags or commit ranges.
+ Compare Git revisions.
%br
Fill input field with commit id like
%code.label-branch 4eedf23
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index d07de45fdde..257e0a855bd 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -8,7 +8,7 @@
= link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file", disabled: @diff_notes_disabled do
= icon('comment')
\
-
+ = clipboard_button(clipboard_text: diff_file.new_path, class: 'btn-file-option')
- if editable_diff?(diff_file)
- link_opts = @merge_request.id ? { from_merge_request_id: @merge_request.id } : {}
= edit_blob_link(@merge_request.source_project, @merge_request.source_branch, diff_file.new_path,
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index d19422c8657..c8f84b96cb7 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -91,7 +91,7 @@
Git Large File Storage
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
.col-md-3
- = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control'
+ = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control', data: { field: 'lfs_enabled' }
- if Gitlab.config.registry.enabled
.form-group
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
index 331dc1fcc29..80fe6be49b0 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
@@ -62,5 +62,3 @@
%td.coverage
- if generic_commit_status.try(:coverage)
#{generic_commit_status.coverage}%
-
- %td
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
index 409f4701e4b..0a66d60accc 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
@@ -1,7 +1,9 @@
- if subject.target_url
= link_to subject.target_url do
- = render_status_with_link('commit status', subject.status)
+ %span.ci-status-icon
+ = render_status_with_link('commit status', subject.status)
%span.ci-status-text= subject.name
- else
- = render_status_with_link('commit status', subject.status)
+ %span.ci-status-icon
+ = render_status_with_link('commit status', subject.status)
%span.ci-status-text= subject.name
diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml
index 509b01c548a..4825820c4d9 100644
--- a/app/views/projects/issues/_head.html.haml
+++ b/app/views/projects/issues/_head.html.haml
@@ -10,7 +10,7 @@
Issues
= nav_link(controller: :boards) do
- = link_to namespace_project_board_path(@project.namespace, @project), title: 'Board' do
+ = link_to namespace_project_boards_path(@project.namespace, @project), title: 'Board' do
%span
Board
diff --git a/app/views/projects/issues/edit.html.haml b/app/views/projects/issues/edit.html.haml
index 7cf1923456e..3a6fbbc7fbc 100644
--- a/app/views/projects/issues/edit.html.haml
+++ b/app/views/projects/issues/edit.html.haml
@@ -1,4 +1,4 @@
-- page_title "Edit", "#{@issue.title} (##{@issue.iid})", "Issues"
+- page_title "Edit", "#{@issue.to_reference} #{@issue.title}", "Issues"
%h3.page-title
Edit Issue ##{@issue.iid}
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index b94d6f8633c..09347ad5fff 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,4 +1,4 @@
-- page_title "#{@issue.title} (##{@issue.iid})", "Issues"
+- page_title "#{@issue.to_reference} #{@issue.title}", "Issues"
- page_description @issue.description
- page_card_attributes @issue.card_attributes
@@ -23,8 +23,8 @@
.issuable-actions
.clearfix.issue-btn-group.dropdown
%button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } }
- = icon('caret-down')
Options
+ = icon('caret-down')
.dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul
- if can?(current_user, :create_issue, @project)
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml
index de39964fca8..466ec1475d8 100644
--- a/app/views/projects/merge_requests/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/_new_compare.html.haml
@@ -65,19 +65,6 @@
- if @merge_request.errors.any?
= form_errors(@merge_request)
- - elsif @merge_request.source_branch.present? && @merge_request.target_branch.present?
- .light-well.append-bottom-default
- .center
- %h4
- There isn't anything to merge.
- %p.slead
- - if @merge_request.source_branch == @merge_request.target_branch
- You'll need to use different branch names to get a valid comparison.
- - else
- %span.label-branch #{@merge_request.source_branch}
- and
- %span.label-branch #{@merge_request.target_branch}
- are the same.
= f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn"
:javascript
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 88d8013a0d1..da6927879a4 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -18,29 +18,35 @@
= f.hidden_field :target_branch
.mr-compare.merge-request
- %ul.merge-request-tabs.nav-links.no-top.no-bottom
- %li.commits-tab.active
- = link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do
- Commits
- %span.badge= @commits.size
- - if @pipeline
- %li.builds-tab
- = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do
- Builds
- %span.badge= @statuses.size
- %li.diffs-tab
- = link_to url_for(params.merge(action: 'new_diffs')), data: {target: 'div#diffs', action: 'new/diffs', toggle: 'tab'} do
- Changes
- %span.badge= @merge_request.diff_size
+ - if @commits.empty?
+ .commits-empty
+ %h4
+ There are no commits yet.
+ = custom_icon ('illustration_no_commits')
+ - else
+ %ul.merge-request-tabs.nav-links.no-top.no-bottom
+ %li.commits-tab.active
+ = link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do
+ Commits
+ %span.badge= @commits.size
+ - if @pipeline
+ %li.builds-tab
+ = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do
+ Builds
+ %span.badge= @statuses.size
+ %li.diffs-tab
+ = link_to url_for(params.merge(action: 'new_diffs')), data: {target: 'div#diffs', action: 'new/diffs', toggle: 'tab'} do
+ Changes
+ %span.badge= @merge_request.diff_size
- .tab-content
- #commits.commits.tab-pane.active
- = render "projects/merge_requests/show/commits"
- #diffs.diffs.tab-pane
- - # This tab is always loaded via AJAX
- - if @pipeline
- #builds.builds.tab-pane
- = render "projects/merge_requests/show/builds"
+ .tab-content
+ #commits.commits.tab-pane.active
+ = render "projects/merge_requests/show/commits"
+ #diffs.diffs.tab-pane
+ - # This tab is always loaded via AJAX
+ - if @pipeline
+ #builds.builds.tab-pane
+ = render "projects/merge_requests/show/builds"
.mr-loading-status
= spinner
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 46a2d862c91..47dd51639b5 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -1,4 +1,4 @@
-- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
+- page_title "#{@merge_request.to_reference} #{@merge_request.title}", "Merge Requests"
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
- content_for :page_specific_javascripts do
diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml
index 03159f123f3..7c3ac6652ee 100644
--- a/app/views/projects/merge_requests/edit.html.haml
+++ b/app/views/projects/merge_requests/edit.html.haml
@@ -1,4 +1,4 @@
-- page_title "Edit", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
+- page_title "Edit", "#{@merge_request.to_reference} #{@merge_request.title}", "Merge Requests"
%h3.page-title
Edit Merge Request #{@merge_request.to_reference}
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index 9f2a0f5d99a..e7c5bca6a37 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -19,8 +19,8 @@
.issuable-actions
.clearfix.issue-btn-group.dropdown
%button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } }
- = icon('caret-down')
Options
+ = icon('caret-down')
.dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul
%li{ class: merge_request_button_visibility(@merge_request, true) }
diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml
index 988ac0feae1..eab48b78cb3 100644
--- a/app/views/projects/merge_requests/show/_versions.html.haml
+++ b/app/views/projects/merge_requests/show/_versions.html.haml
@@ -64,6 +64,16 @@
#{@merge_request.target_branch} (base)
.monospace #{short_sha(@merge_request_diff.base_commit_sha)}
+ - if different_base?(@start_version, @merge_request_diff)
+ .content-block
+ = icon('info-circle')
+ Selected versions have different base commits.
+ Changes will include
+ = link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do
+ new commits
+ from
+ %code #{@merge_request.target_branch}
+
- unless @merge_request_diff.latest? && !@start_sha
.comments-disabled-notif.content-block
= icon('info-circle')
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index 5b7f83c344f..a82c846baa7 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -44,17 +44,5 @@
= icon("times-circle")
Could not connect to the CI server. Please check your settings and try again.
-- @merge_request.environments.sort_by(&:name).each do |environment|
- - if can?(current_user, :read_environment, environment)
- .mr-widget-heading
- .ci_widget.ci-success
- = ci_icon_for_status("success")
- %span
- Deployed to
- = succeed '.' do
- = link_to environment.name, environment_path(environment), class: 'environment'
- - external_url = environment.external_url
- - if external_url
- = link_to external_url, target: '_blank' do
- %span.hidden-xs View on #{external_url.gsub(/\A.*?:\/\//, '')}
- = icon('external-link', right: true)
+.js-success-icon.hidden
+ = ci_icon_for_status('success')
diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml
index 6f5ee5f16c5..842b6df310d 100644
--- a/app/views/projects/merge_requests/widget/_open.html.haml
+++ b/app/views/projects/merge_requests/widget/_open.html.haml
@@ -35,3 +35,4 @@
Accepting this merge request will close #{"issue".pluralize(mr_closes_issues.size)}
= succeed '.' do
!= markdown issues_sentence(mr_closes_issues), pipeline: :gfm, author: @merge_request.author
+ = mr_assign_issues_link
diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml
index ea618263a4a..608fdf1c5f5 100644
--- a/app/views/projects/merge_requests/widget/_show.html.haml
+++ b/app/views/projects/merge_requests/widget/_show.html.haml
@@ -12,6 +12,7 @@
merge_check_url: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
check_enable: #{@merge_request.unchecked? ? "true" : "false"},
ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
+ ci_environments_status_url: "#{ci_environments_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
gitlab_icon: "#{asset_path 'gitlab_logo.png'}",
ci_status: "#{@merge_request.pipeline ? @merge_request.pipeline.status : ''}",
ci_message: {
@@ -33,4 +34,4 @@
merge_request_widget.clearEventListeners();
}
- merge_request_widget = new MergeRequestWidget(opts);
+ merge_request_widget = new window.gl.MergeRequestWidget(opts);
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index b2ece44d966..29df1bab04e 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -8,7 +8,7 @@
.project-network
.controls
= form_tag namespace_project_network_path(@project.namespace, @project, @id), method: :get, class: 'form-inline network-form' do |f|
- = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: 'search-input form-control input-mx-250 search-sha'
+ = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Git revision", class: 'search-input form-control input-mx-250 search-sha'
= button_tag class: 'btn btn-success' do
= icon('search')
.inline.prepend-left-20
diff --git a/app/views/shared/icons/_illustration_no_commits.svg b/app/views/shared/icons/_illustration_no_commits.svg
new file mode 100644
index 00000000000..4f9d9add60d
--- /dev/null
+++ b/app/views/shared/icons/_illustration_no_commits.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 168 107" xmlns:xlink="http://www.w3.org/1999/xlink"><g fill="#eee" fill-rule="evenodd"><path d="m4.01 2h1.102c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-1.102c-2.218 0-4.01 1.788-4.01 4 0 .552.448 1 1 1 .552 0 1-.448 1-1 0-1.108.892-2 2.01-2m12.702 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m8.088 0c.822 0 1.554.503 1.86 1.254.208.512.791.758 1.303.55.512-.208.758-.791.55-1.303-.609-1.497-2.069-2.5-3.712-2.5h-2.188c-.552 0-1 .448-1 1 0 .552.448 1 1 1h2.188m2.01 12.518c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7m0 11.6c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7m0 11.6c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7m0 6.282c0 1.108-.892 2-2.01 2h-.72c-.552 0-1 .448-1 1 0 .552.448 1 1 1h.72c2.218 0 4.01-1.788 4.01-4v-.382c0-.552-.448-1-1-1-.552 0-1 .448-1 1v.382m-14.325 2c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-8.47 0c-.755 0-1.438-.424-1.782-1.085-.255-.49-.859-.681-1.349-.426-.49.255-.681.859-.426 1.349.684 1.316 2.046 2.162 3.556 2.162h2.57c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-2.57m-2.01-12.136c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7m0-11.6c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7m0-11.6c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7m0-6.664c0-.552-.448-1-1-1-.552 0-1 .448-1 1v.764c0 .552.448 1 1 1 .552 0 1-.448 1-1v-.764" id="0"/><circle cx="21" cy="24" r="10"/><rect width="33" height="3" x="37" y="18" rx="1.5" id="1"/><rect width="53" height="3" x="37" y="27" rx="1.5" id="2"/><path d="m131 29c0 .552.447.999.996.999h22.01c.545 0 .996-.451.996-.999v-9c0-.552-.447-.999-.996-.999h-22.01c-.545 0-.996.451-.996.999v9m.996-12h22.01c1.655 0 2.996 1.344 2.996 2.999v9c0 1.657-1.35 2.999-2.996 2.999h-22.01c-1.655 0-2.996-1.344-2.996-2.999v-9c0-1.657 1.35-2.999 2.996-2.999" id="3"/><g transform="translate(0 59)"><use xlink:href="#0"/><circle cx="21" cy="24" r="10"/><use xlink:href="#1"/><use xlink:href="#2"/><use xlink:href="#3"/></g></g></svg> \ No newline at end of file
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index c3f4e10c954..a75653d842f 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -85,20 +85,20 @@
.issuable-form-select-holder
- if issuable.assignee_id
= f.hidden_field :assignee_id
- = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
+ = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee", show_menu_above: true } })
.form-group.issue-milestone
= f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}"
.col-sm-10{ class: ("col-lg-8" if has_due_date) }
.issuable-form-select-holder
- = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_menu_above: true, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input"
+ = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_menu_above: true, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
.form-group
- has_labels = issuable.project.labels.any?
= f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}"
= f.hidden_field :label_ids, multiple: true, value: ''
.col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" }
.issuable-form-select-holder
- = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false, show_menu_above: 'true' }
+ = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false, show_menu_above: 'true' }, dropdown_title: "Select label"
- if has_due_date
.col-lg-6
.form-group
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index 6d307611640..22b5a6aa11b 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -8,6 +8,7 @@
- classes = local_assigns.fetch(:classes, [])
- selected = local_assigns.fetch(:selected, nil)
- selected_toggle = local_assigns.fetch(:selected_toggle, nil)
+- dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by label")
- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path), labels: labels_filter_path, default_label: "Labels"}
- dropdown_data.merge!(data_options)
- classes << 'js-extra-options' if extra_options
@@ -23,7 +24,7 @@
= multi_label_name(selected, "Labels")
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
- = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label", show_footer: show_footer, show_create: show_create }
+ = render partial: "shared/issuable/label_page_default", locals: { title: dropdown_title, show_footer: show_footer, show_create: show_create }
- if show_create && project && can?(current_user, :admin_label, project)
= render partial: "shared/issuable/label_page_create"
= dropdown_loading
diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml
index ab3cc33d18f..f27a9002ec2 100644
--- a/app/views/shared/issuable/_milestone_dropdown.html.haml
+++ b/app/views/shared/issuable/_milestone_dropdown.html.haml
@@ -2,9 +2,10 @@
- extra_class = extra_class || ''
- show_menu_above = show_menu_above || false
- selected_text = selected.try(:title)
+- dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by milestone")
- if selected.present?
= hidden_field_tag(name, name == :milestone_title ? selected.title : selected.id)
-= dropdown_tag(milestone_dropdown_label(selected_text), options: { title: "Filter by milestone", toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone",
+= dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone",
placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do
- if project
%ul.dropdown-footer-list
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index cd89155c616..27d7a6c5bb6 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -9,6 +9,7 @@
.file-actions
= clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
= link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
+ = link_to 'Download', download_snippet_path(@snippet), class: "btn btn-sm"
= render 'shared/snippets/blob'
= render 'award_emoji/awards_block', awardable: @snippet, inline: true \ No newline at end of file
diff --git a/app/workers/process_pipeline_worker.rb b/app/workers/pipeline_process_worker.rb
index 26ea5f1c24d..f44227d7086 100644
--- a/app/workers/process_pipeline_worker.rb
+++ b/app/workers/pipeline_process_worker.rb
@@ -1,4 +1,4 @@
-class ProcessPipelineWorker
+class PipelineProcessWorker
include Sidekiq::Worker
sidekiq_options queue: :default
diff --git a/app/workers/pipeline_success_worker.rb b/app/workers/pipeline_success_worker.rb
new file mode 100644
index 00000000000..5dd443fea59
--- /dev/null
+++ b/app/workers/pipeline_success_worker.rb
@@ -0,0 +1,12 @@
+class PipelineSuccessWorker
+ include Sidekiq::Worker
+ sidekiq_options queue: :default
+
+ def perform(pipeline_id)
+ Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline|
+ MergeRequests::MergeWhenBuildSucceedsService
+ .new(pipeline.project, nil)
+ .trigger(pipeline)
+ end
+ end
+end
diff --git a/app/workers/update_pipeline_worker.rb b/app/workers/pipeline_update_worker.rb
index 6ef5678073e..44a7f24e401 100644
--- a/app/workers/update_pipeline_worker.rb
+++ b/app/workers/pipeline_update_worker.rb
@@ -1,4 +1,4 @@
-class UpdatePipelineWorker
+class PipelineUpdateWorker
include Sidekiq::Worker
sidekiq_options queue: :default
diff --git a/app/workers/trending_projects_worker.rb b/app/workers/trending_projects_worker.rb
new file mode 100644
index 00000000000..df4c4a6628b
--- /dev/null
+++ b/app/workers/trending_projects_worker.rb
@@ -0,0 +1,11 @@
+class TrendingProjectsWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :trending_projects
+
+ def perform
+ Rails.logger.info('Refreshing trending projects')
+
+ TrendingProject.refresh!
+ end
+end
diff --git a/app/workers/update_merge_requests_worker.rb b/app/workers/update_merge_requests_worker.rb
new file mode 100644
index 00000000000..03f0528cdae
--- /dev/null
+++ b/app/workers/update_merge_requests_worker.rb
@@ -0,0 +1,16 @@
+class UpdateMergeRequestsWorker
+ include Sidekiq::Worker
+
+ def perform(project_id, user_id, oldrev, newrev, ref)
+ project = Project.find_by(id: project_id)
+ return unless project
+
+ user = User.find_by(id: user_id)
+ return unless user
+
+ MergeRequests::RefreshService.new(project, user).execute(oldrev, newrev, ref)
+
+ push_data = Gitlab::DataBuilder::Push.build(project, user, oldrev, newrev, ref, [])
+ SystemHooksService.new.execute_hooks(push_data, :push_hooks)
+ end
+end