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:
authorFilipa Lacerda <filipa@gitlab.com>2017-12-11 22:42:58 +0300
committerFilipa Lacerda <filipa@gitlab.com>2017-12-11 22:42:58 +0300
commit3c19c971df0490772494e224405f13a0f98d6bf4 (patch)
treeff78e561cbbfb49d66f135f8dbf638bf07bcff60 /app
parenta2479aaa1be00416f952eb79fa444328266c16b6 (diff)
parent4ccbd556d98e002b1c521fd3dd7748fe1d9c4044 (diff)
Merge branch 'master' into 38869-datetime
* master: (112 commits) small change to make less conflict with EE version Add cop for use of remove_column Resolve merge conflicts with dev.gitlab.org/master after security release add index for doc/administration/operations/ Remove RubySampler#sample_objects for performance as well Bugfix: User can't change the access level of an access requester Add spec for removing issues.assignee_id updated imports Keep track of storage check timings Remove a header level in the new 'Automatic CE->EE merge' doc Improve down step of removing issues.assignee_id column Fix specs after removing assignee_id field Remove issues.assignee_id column Resolve conflicts in app/models/user.rb Fix image view mode Do not raise when downstream pipeline is created Remove the need for destroy and add a comment in the spec Use build instead of create in importer spec Simplify normalizing of paths Remove allocation tracking code from InfluxDB sampler for performance ...
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/admin.js5
-rw-r--r--app/assets/javascripts/behaviors/toggler_behavior.js3
-rw-r--r--app/assets/javascripts/blob/blob_file_dropzone.js4
-rw-r--r--app/assets/javascripts/blob/blob_line_permalink_updater.js4
-rw-r--r--app/assets/javascripts/commit/image_file.js4
-rw-r--r--app/assets/javascripts/compare.js34
-rw-r--r--app/assets/javascripts/compare_autocomplete.js116
-rw-r--r--app/assets/javascripts/contextual_sidebar.js8
-rw-r--r--app/assets/javascripts/deploy_keys/components/action_btn.vue9
-rw-r--r--app/assets/javascripts/deploy_keys/components/app.vue5
-rw-r--r--app/assets/javascripts/diff.js8
-rw-r--r--app/assets/javascripts/diff_notes/diff_notes_bundle.js3
-rw-r--r--app/assets/javascripts/diff_notes/services/resolve.js2
-rw-r--r--app/assets/javascripts/dispatcher.js13
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js3
-rw-r--r--app/assets/javascripts/fly_out_nav.js2
-rw-r--r--app/assets/javascripts/gl_dropdown.js3
-rw-r--r--app/assets/javascripts/groups/components/app.vue4
-rw-r--r--app/assets/javascripts/groups/components/group_item.vue3
-rw-r--r--app/assets/javascripts/groups/new_group_child.js5
-rw-r--r--app/assets/javascripts/issue.js13
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue94
-rw-r--r--app/assets/javascripts/issue_show/components/description.vue24
-rw-r--r--app/assets/javascripts/issue_show/index.js27
-rw-r--r--app/assets/javascripts/job.js9
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js3
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js126
-rw-r--r--app/assets/javascripts/main.js9
-rw-r--r--app/assets/javascripts/merge_request_tabs.js3
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue4
-rw-r--r--app/assets/javascripts/monitoring/components/graph.vue24
-rw-r--r--app/assets/javascripts/monitoring/components/graph/deployment.vue101
-rw-r--r--app/assets/javascripts/monitoring/mixins/monitoring_mixins.js2
-rw-r--r--app/assets/javascripts/monitoring/utils/date_time_formatters.js1
-rw-r--r--app/assets/javascripts/namespace_select.js4
-rw-r--r--app/assets/javascripts/notes.js3
-rw-r--r--app/assets/javascripts/notes/components/issue_note.vue3
-rw-r--r--app/assets/javascripts/notes/components/issue_notes_app.vue3
-rw-r--r--app/assets/javascripts/notes/index.js10
-rw-r--r--app/assets/javascripts/pager.js4
-rw-r--r--app/assets/javascripts/performance_bar.js3
-rw-r--r--app/assets/javascripts/project.js3
-rw-r--r--app/assets/javascripts/projects/ci_cd_settings_bundle.js19
-rw-r--r--app/assets/javascripts/projects/project_import_gitlab_project.js4
-rw-r--r--app/assets/javascripts/repo/stores/actions.js3
-rw-r--r--app/assets/javascripts/repo/stores/actions/tree.js3
-rw-r--r--app/assets/javascripts/shortcuts.js5
-rw-r--r--app/assets/javascripts/shortcuts_blob.js6
-rw-r--r--app/assets/javascripts/sidebar/sidebar_mediator.js3
-rw-r--r--app/assets/javascripts/todos.js4
-rw-r--r--app/assets/javascripts/tree.js5
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/icon.vue30
-rw-r--r--app/assets/javascripts/vue_shared/components/popup_dialog.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/recaptcha_dialog.vue85
-rw-r--r--app/assets/javascripts/vue_shared/mixins/recaptcha_dialog_implementor.js36
-rw-r--r--app/assets/stylesheets/framework/contextual-sidebar.scss38
-rw-r--r--app/assets/stylesheets/framework/modal.scss7
-rw-r--r--app/assets/stylesheets/framework/variables.scss3
-rw-r--r--app/assets/stylesheets/pages/boards.scss6
-rw-r--r--app/assets/stylesheets/pages/environments.scss19
-rw-r--r--app/assets/stylesheets/pages/issuable.scss2
-rw-r--r--app/controllers/admin/health_check_controller.rb2
-rw-r--r--app/controllers/concerns/issuable_actions.rb10
-rw-r--r--app/controllers/concerns/spammable_actions.rb17
-rw-r--r--app/controllers/groups/group_members_controller.rb2
-rw-r--r--app/controllers/health_controller.rb11
-rw-r--r--app/controllers/profiles/personal_access_tokens_controller.rb4
-rw-r--r--app/controllers/projects/commit_controller.rb17
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb1
-rw-r--r--app/controllers/projects/merge_requests/diffs_controller.rb42
-rw-r--r--app/controllers/projects/merge_requests_controller.rb3
-rw-r--r--app/controllers/projects/pipelines_settings_controller.rb1
-rw-r--r--app/controllers/projects/project_members_controller.rb2
-rw-r--r--app/controllers/projects_controller.rb2
-rw-r--r--app/helpers/application_settings_helper.rb19
-rw-r--r--app/helpers/auto_devops_helper.rb16
-rw-r--r--app/helpers/builds_helper.rb3
-rw-r--r--app/helpers/commits_helper.rb8
-rw-r--r--app/helpers/merge_requests_helper.rb24
-rw-r--r--app/helpers/preferences_helper.rb2
-rw-r--r--app/helpers/storage_health_helper.rb6
-rw-r--r--app/models/application_setting.rb12
-rw-r--r--app/models/ci/build.rb20
-rw-r--r--app/models/commit.rb3
-rw-r--r--app/models/commit_status.rb3
-rw-r--r--app/models/concerns/discussion_on_diff.rb4
-rw-r--r--app/models/diff_discussion.rb6
-rw-r--r--app/models/diff_note.rb7
-rw-r--r--app/models/discussion.rb1
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/issue.rb3
-rw-r--r--app/models/merge_request.rb27
-rw-r--r--app/models/namespace.rb11
-rw-r--r--app/models/note.rb20
-rw-r--r--app/models/personal_access_token.rb21
-rw-r--r--app/models/project.rb5
-rw-r--r--app/models/redirect_route.rb28
-rw-r--r--app/models/repository.rb17
-rw-r--r--app/models/route.rb26
-rw-r--r--app/models/user.rb19
-rw-r--r--app/services/ci/create_pipeline_service.rb25
-rw-r--r--app/services/ci/register_job_service.rb3
-rw-r--r--app/services/merge_requests/refresh_service.rb2
-rw-r--r--app/services/metrics_service.rb2
-rw-r--r--app/services/projects/update_service.rb10
-rw-r--r--app/views/admin/application_settings/_form.html.haml18
-rw-r--r--app/views/discussions/_discussion.html.haml14
-rw-r--r--app/views/layouts/_recaptcha_verification.html.haml15
-rw-r--r--app/views/layouts/_search.html.haml9
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_profile.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml2
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml7
-rw-r--r--app/views/projects/_md_preview.html.haml2
-rw-r--r--app/views/projects/ci/builds/_build.html.haml2
-rw-r--r--app/views/projects/commit/_commit_box.html.haml12
-rw-r--r--app/views/projects/commit/show.html.haml3
-rw-r--r--app/views/projects/commits/_commit.html.haml29
-rw-r--r--app/views/projects/commits/_commits.html.haml7
-rw-r--r--app/views/projects/commits/show.html.haml2
-rw-r--r--app/views/projects/environments/metrics.html.haml2
-rw-r--r--app/views/projects/issues/_discussion.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml4
-rw-r--r--app/views/projects/merge_requests/_commits.html.haml2
-rw-r--r--app/views/projects/merge_requests/diffs/_commit_widget.html.haml5
-rw-r--r--app/views/projects/merge_requests/diffs/_different_base.html.haml11
-rw-r--r--app/views/projects/merge_requests/diffs/_diffs.html.haml25
-rw-r--r--app/views/projects/merge_requests/diffs/_not_all_comments_displayed.html.haml17
-rw-r--r--app/views/projects/merge_requests/diffs/_version_controls.html.haml (renamed from app/views/projects/merge_requests/diffs/_versions.html.haml)26
-rw-r--r--app/views/projects/merge_requests/show.html.haml10
-rw-r--r--app/views/projects/new.html.haml5
-rw-r--r--app/views/projects/pipelines_settings/_show.html.haml23
-rw-r--r--app/views/shared/_outdated_browser.html.haml2
-rw-r--r--app/views/shared/_recaptcha_form.html.haml19
-rw-r--r--app/views/shared/notes/_notes_with_form.html.haml2
138 files changed, 1115 insertions, 637 deletions
diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js
index 34669dd13d6..b0b72c40f25 100644
--- a/app/assets/javascripts/admin.js
+++ b/app/assets/javascripts/admin.js
@@ -1,4 +1,5 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-arrow-callback, camelcase, quotes, comma-dangle, max-len */
+import { refreshCurrentPage } from './lib/utils/url_utility';
window.Admin = (function() {
function Admin() {
@@ -40,10 +41,10 @@ window.Admin = (function() {
return $('.change-owner-link').show();
});
$('li.project_member').bind('ajax:success', function() {
- return gl.utils.refreshCurrentPage();
+ return refreshCurrentPage();
});
$('li.group_member').bind('ajax:success', function() {
- return gl.utils.refreshCurrentPage();
+ return refreshCurrentPage();
});
showBlacklistType = function() {
if ($("input[name='blacklist_type']:checked").val() === 'file') {
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js
index b70b0a9bbf8..417ac31fc86 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.js
+++ b/app/assets/javascripts/behaviors/toggler_behavior.js
@@ -5,6 +5,7 @@
// %button.js-toggle-button
// %div.js-toggle-content
//
+import { getLocationHash } from '../lib/utils/url_utility';
$(() => {
function toggleContainer(container, toggleState) {
@@ -32,7 +33,7 @@ $(() => {
// If we're accessing a permalink, ensure it is not inside a
// closed js-toggle-container!
- const hash = window.gl.utils.getLocationHash();
+ const hash = getLocationHash();
const anchor = hash && document.getElementById(hash);
const container = anchor && $(anchor).closest('.js-toggle-container');
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js
index 0d590a9dbc4..f7ae6f1cd12 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js
@@ -1,6 +1,6 @@
/* eslint-disable func-names, object-shorthand, prefer-arrow-callback */
import Dropzone from 'dropzone';
-import '../lib/utils/url_utility';
+import { visitUrl } from '../lib/utils/url_utility';
import { HIDDEN_CLASS } from '../lib/utils/constants';
import csrf from '../lib/utils/csrf';
@@ -49,7 +49,7 @@ export default class BlobFileDropzone {
});
this.on('success', function (header, response) {
$('#modal-upload-blob').modal('hide');
- window.gl.utils.visitUrl(response.filePath);
+ visitUrl(response.filePath);
});
this.on('maxfilesexceeded', function (file) {
dropzoneMessage.addClass(HIDDEN_CLASS);
diff --git a/app/assets/javascripts/blob/blob_line_permalink_updater.js b/app/assets/javascripts/blob/blob_line_permalink_updater.js
index c8f68860fbd..d36d9f0de2d 100644
--- a/app/assets/javascripts/blob/blob_line_permalink_updater.js
+++ b/app/assets/javascripts/blob/blob_line_permalink_updater.js
@@ -1,7 +1,9 @@
+import { getLocationHash } from '../lib/utils/url_utility';
+
const lineNumberRe = /^L[0-9]+/;
const updateLineNumbersOnBlobPermalinks = (linksToUpdate) => {
- const hash = gl.utils.getLocationHash();
+ const hash = getLocationHash();
if (hash && lineNumberRe.test(hash)) {
const hashUrlString = `#${hash}`;
diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js
index 50d0cb5c86d..5662802525e 100644
--- a/app/assets/javascripts/commit/image_file.js
+++ b/app/assets/javascripts/commit/image_file.js
@@ -121,7 +121,7 @@ export default class ImageFile {
return $('.swipe.view', this.file).each((function(_this) {
return function(index, view) {
var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref;
- ref = this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
+ ref = _this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
$swipeFrame = $('.swipe-frame', view);
$swipeWrap = $('.swipe-wrap', view);
$swipeBar = $('.swipe-bar', view);
@@ -158,7 +158,7 @@ export default class ImageFile {
return $('.onion-skin.view', this.file).each((function(_this) {
return function(index, view) {
var $frame, $track, $dragger, $frameAdded, framePadding, ref, dragging = false;
- ref = this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
+ ref = _this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
$frame = $('.onion-skin-frame', view);
$frameAdded = $('.frame.added', view);
$track = $('.drag-track', view);
diff --git a/app/assets/javascripts/compare.js b/app/assets/javascripts/compare.js
index d8cbf7e99c5..144caf1d278 100644
--- a/app/assets/javascripts/compare.js
+++ b/app/assets/javascripts/compare.js
@@ -1,8 +1,8 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */
import { localTimeAgo } from './lib/utils/datetime_utility';
-window.Compare = (function() {
- function Compare(opts) {
+export default class Compare {
+ constructor(opts) {
this.opts = opts;
this.source_loading = $(".js-source-loading");
this.target_loading = $(".js-target-loading");
@@ -35,12 +35,12 @@ window.Compare = (function() {
this.initialState();
}
- Compare.prototype.initialState = function() {
+ initialState() {
this.getSourceHtml();
- return this.getTargetHtml();
- };
+ this.getTargetHtml();
+ }
- Compare.prototype.getTargetProject = function() {
+ getTargetProject() {
return $.ajax({
url: this.opts.targetProjectUrl,
data: {
@@ -53,22 +53,22 @@ window.Compare = (function() {
return $('.js-target-branch-dropdown .dropdown-content').html(html);
}
});
- };
+ }
- Compare.prototype.getSourceHtml = function() {
- return this.sendAjax(this.opts.sourceBranchUrl, this.source_loading, '.mr_source_commit', {
+ getSourceHtml() {
+ return this.constructor.sendAjax(this.opts.sourceBranchUrl, this.source_loading, '.mr_source_commit', {
ref: $("input[name='merge_request[source_branch]']").val()
});
- };
+ }
- Compare.prototype.getTargetHtml = function() {
- return this.sendAjax(this.opts.targetBranchUrl, this.target_loading, '.mr_target_commit', {
+ getTargetHtml() {
+ return this.constructor.sendAjax(this.opts.targetBranchUrl, this.target_loading, '.mr_target_commit', {
target_project_id: $("input[name='merge_request[target_project_id]']").val(),
ref: $("input[name='merge_request[target_branch]']").val()
});
- };
+ }
- Compare.prototype.sendAjax = function(url, loading, target, data) {
+ static sendAjax(url, loading, target, data) {
var $target;
$target = $(target);
return $.ajax({
@@ -85,7 +85,5 @@ window.Compare = (function() {
localTimeAgo($('.js-timeago', className));
}
});
- };
-
- return Compare;
-})();
+ }
+}
diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js
index 72c0d98d47c..e633ef8a29e 100644
--- a/app/assets/javascripts/compare_autocomplete.js
+++ b/app/assets/javascripts/compare_autocomplete.js
@@ -1,68 +1,60 @@
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, object-shorthand, comma-dangle, prefer-arrow-callback, no-else-return, newline-per-chained-call, wrap-iife, max-len */
-window.CompareAutocomplete = (function() {
- function CompareAutocomplete() {
- this.initDropdown();
- }
-
- CompareAutocomplete.prototype.initDropdown = function() {
- return $('.js-compare-dropdown').each(function() {
- var $dropdown, selected;
- $dropdown = $(this);
- selected = $dropdown.data('selected');
- const $dropdownContainer = $dropdown.closest('.dropdown');
- const $fieldInput = $(`input[name="${$dropdown.data('field-name')}"]`, $dropdownContainer);
- const $filterInput = $('input[type="search"]', $dropdownContainer);
- $dropdown.glDropdown({
- data: function(term, callback) {
- return $.ajax({
- url: $dropdown.data('refs-url'),
- data: {
- ref: $dropdown.data('ref'),
- search: term,
- }
- }).done(function(refs) {
- return callback(refs);
- });
- },
- selectable: true,
- filterable: true,
- filterRemote: true,
- fieldName: $dropdown.data('field-name'),
- filterInput: 'input[type="search"]',
- renderRow: function(ref) {
- var link;
- if (ref.header != null) {
- return $('<li />').addClass('dropdown-header').text(ref.header);
- } else {
- link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref));
- return $('<li />').append(link);
+export default function initCompareAutocomplete() {
+ $('.js-compare-dropdown').each(function() {
+ var $dropdown, selected;
+ $dropdown = $(this);
+ selected = $dropdown.data('selected');
+ const $dropdownContainer = $dropdown.closest('.dropdown');
+ const $fieldInput = $(`input[name="${$dropdown.data('field-name')}"]`, $dropdownContainer);
+ const $filterInput = $('input[type="search"]', $dropdownContainer);
+ $dropdown.glDropdown({
+ data: function(term, callback) {
+ return $.ajax({
+ url: $dropdown.data('refs-url'),
+ data: {
+ ref: $dropdown.data('ref'),
+ search: term,
}
- },
- id: function(obj, $el) {
- return $el.attr('data-ref');
- },
- toggleLabel: function(obj, $el) {
- return $el.text().trim();
- }
- });
- $filterInput.on('keyup', (e) => {
- const keyCode = e.keyCode || e.which;
- if (keyCode !== 13) return;
- const text = $filterInput.val();
- $fieldInput.val(text);
- $('.dropdown-toggle-text', $dropdown).text(text);
- $dropdownContainer.removeClass('open');
- });
-
- $dropdownContainer.on('click', '.dropdown-content a', (e) => {
- $dropdown.prop('title', e.target.text.replace(/_+?/g, '-'));
- if ($dropdown.hasClass('has-tooltip')) {
- $dropdown.tooltip('fixTitle');
+ }).done(function(refs) {
+ return callback(refs);
+ });
+ },
+ selectable: true,
+ filterable: true,
+ filterRemote: true,
+ fieldName: $dropdown.data('field-name'),
+ filterInput: 'input[type="search"]',
+ renderRow: function(ref) {
+ var link;
+ if (ref.header != null) {
+ return $('<li />').addClass('dropdown-header').text(ref.header);
+ } else {
+ link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref));
+ return $('<li />').append(link);
}
- });
+ },
+ id: function(obj, $el) {
+ return $el.attr('data-ref');
+ },
+ toggleLabel: function(obj, $el) {
+ return $el.text().trim();
+ }
+ });
+ $filterInput.on('keyup', (e) => {
+ const keyCode = e.keyCode || e.which;
+ if (keyCode !== 13) return;
+ const text = $filterInput.val();
+ $fieldInput.val(text);
+ $('.dropdown-toggle-text', $dropdown).text(text);
+ $dropdownContainer.removeClass('open');
});
- };
- return CompareAutocomplete;
-})();
+ $dropdownContainer.on('click', '.dropdown-content a', (e) => {
+ $dropdown.prop('title', e.target.text.replace(/_+?/g, '-'));
+ if ($dropdown.hasClass('has-tooltip')) {
+ $dropdown.tooltip('fixTitle');
+ }
+ });
+ });
+}
diff --git a/app/assets/javascripts/contextual_sidebar.js b/app/assets/javascripts/contextual_sidebar.js
index 46b68ebe158..cd20dde2951 100644
--- a/app/assets/javascripts/contextual_sidebar.js
+++ b/app/assets/javascripts/contextual_sidebar.js
@@ -28,7 +28,7 @@ export default class ContextualSidebar {
this.$closeSidebar.on('click', () => this.toggleSidebarNav(false));
this.$overlay.on('click', () => this.toggleSidebarNav(false));
this.$sidebarToggle.on('click', () => {
- const value = !this.$sidebar.hasClass('sidebar-icons-only');
+ const value = !this.$sidebar.hasClass('sidebar-collapsed-desktop');
this.toggleCollapsedSidebar(value);
});
@@ -43,16 +43,16 @@ export default class ContextualSidebar {
}
toggleSidebarNav(show) {
- this.$sidebar.toggleClass('nav-sidebar-expanded', show);
+ this.$sidebar.toggleClass('sidebar-expanded-mobile', show);
this.$overlay.toggleClass('mobile-nav-open', show);
- this.$sidebar.removeClass('sidebar-icons-only');
+ this.$sidebar.removeClass('sidebar-collapsed-desktop');
}
toggleCollapsedSidebar(collapsed) {
const breakpoint = bp.getBreakpointSize();
if (this.$sidebar.length) {
- this.$sidebar.toggleClass('sidebar-icons-only', collapsed);
+ this.$sidebar.toggleClass('sidebar-collapsed-desktop', collapsed);
this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed);
}
ContextualSidebar.setCollapsedCookie(collapsed);
diff --git a/app/assets/javascripts/deploy_keys/components/action_btn.vue b/app/assets/javascripts/deploy_keys/components/action_btn.vue
index 3f993213dd0..f9f2f9bf693 100644
--- a/app/assets/javascripts/deploy_keys/components/action_btn.vue
+++ b/app/assets/javascripts/deploy_keys/components/action_btn.vue
@@ -32,7 +32,9 @@
doAction() {
this.isLoading = true;
- eventHub.$emit(`${this.type}.key`, this.deployKey);
+ eventHub.$emit(`${this.type}.key`, this.deployKey, () => {
+ this.isLoading = false;
+ });
},
},
computed: {
@@ -50,6 +52,9 @@
:disabled="isLoading"
@click="doAction">
{{ text }}
- <loading-icon v-if="isLoading" />
+ <loading-icon
+ v-if="isLoading"
+ :inline="true"
+ />
</button>
</template>
diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue
index 54e13b79a4f..fe046449054 100644
--- a/app/assets/javascripts/deploy_keys/components/app.vue
+++ b/app/assets/javascripts/deploy_keys/components/app.vue
@@ -47,12 +47,15 @@
.then(() => this.fetchKeys())
.catch(() => new Flash('Error enabling deploy key'));
},
- disableKey(deployKey) {
+ disableKey(deployKey, callback) {
// eslint-disable-next-line no-alert
if (confirm('You are going to remove this deploy key. Are you sure?')) {
this.service.disableKey(deployKey.id)
.then(() => this.fetchKeys())
+ .then(callback)
.catch(() => new Flash('Error removing deploy key'));
+ } else {
+ callback();
}
},
},
diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js
index c8874e48c09..a162424b3cf 100644
--- a/app/assets/javascripts/diff.js
+++ b/app/assets/javascripts/diff.js
@@ -1,4 +1,4 @@
-import './lib/utils/url_utility';
+import { getLocationHash } from './lib/utils/url_utility';
import FilesCommentButton from './files_comment_button';
import SingleFileDiff from './single_file_diff';
import imageDiffHelper from './image_diff/helpers/index';
@@ -31,7 +31,7 @@ export default class Diff {
isBound = true;
}
- if (gl.utils.getLocationHash()) {
+ if (getLocationHash()) {
this.highlightSelectedLine();
}
@@ -73,7 +73,7 @@ export default class Diff {
}
openAnchoredDiff(cb) {
- const locationHash = gl.utils.getLocationHash();
+ const locationHash = getLocationHash();
const anchoredDiff = locationHash && locationHash.split('_')[0];
if (!anchoredDiff) return;
@@ -128,7 +128,7 @@ export default class Diff {
}
// eslint-disable-next-line class-methods-use-this
highlightSelectedLine() {
- const hash = gl.utils.getLocationHash();
+ const hash = getLocationHash();
const $diffFiles = $('.diff-file');
$diffFiles.find('.hll').removeClass('hll');
diff --git a/app/assets/javascripts/diff_notes/diff_notes_bundle.js b/app/assets/javascripts/diff_notes/diff_notes_bundle.js
index 0863c3406bd..e0422057090 100644
--- a/app/assets/javascripts/diff_notes/diff_notes_bundle.js
+++ b/app/assets/javascripts/diff_notes/diff_notes_bundle.js
@@ -16,7 +16,8 @@ import './components/diff_note_avatars';
import './components/new_issue_for_discussion';
$(() => {
- const projectPath = document.querySelector('.merge-request').dataset.projectPath;
+ const projectPathHolder = document.querySelector('.merge-request') || document.querySelector('.commit-box');
+ const projectPath = projectPathHolder.dataset.projectPath;
const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn, new-issue-for-discussion-btn';
window.gl = window.gl || {};
diff --git a/app/assets/javascripts/diff_notes/services/resolve.js b/app/assets/javascripts/diff_notes/services/resolve.js
index 6eae54f830b..96fe23640af 100644
--- a/app/assets/javascripts/diff_notes/services/resolve.js
+++ b/app/assets/javascripts/diff_notes/services/resolve.js
@@ -43,7 +43,7 @@ class ResolveServiceClass {
discussion.resolveAllNotes(resolvedBy);
}
- gl.mrWidget.checkStatus();
+ if (gl.mrWidget) gl.mrWidget.checkStatus();
discussion.updateHeadline(data);
})
.catch(() => new Flash('An error occurred when trying to resolve a discussion. Please try again.'));
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 24476d3d757..299e43a4e90 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -22,8 +22,8 @@ import NewCommitForm from './new_commit_form';
import Project from './project';
import projectAvatar from './project_avatar';
/* global MergeRequest */
-/* global Compare */
-/* global CompareAutocomplete */
+import Compare from './compare';
+import initCompareAutocomplete from './compare_autocomplete';
/* global ProjectFindFile */
import ProjectNew from './project_new';
import projectImport from './project_import';
@@ -525,13 +525,6 @@ import ProjectVariables from './project_variables';
case 'projects:settings:ci_cd:show':
// Initialize expandable settings panels
initSettingsPanels();
-
- import(/* webpackChunkName: "ci-cd-settings" */ './projects/ci_cd_settings_bundle')
- .then(ciCdSettings => ciCdSettings.default())
- .catch((err) => {
- Flash(s__('ProjectSettings|Problem setting up the CI/CD settings JavaScript'));
- throw err;
- });
case 'groups:settings:ci_cd:show':
new ProjectVariables();
break;
@@ -629,7 +622,7 @@ import ProjectVariables from './project_variables';
projectAvatar();
switch (path[1]) {
case 'compare':
- new CompareAutocomplete();
+ initCompareAutocomplete();
break;
case 'edit':
shortcut_handler = new ShortcutsNavigation();
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 69c57f923b6..2ba85c7da97 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -1,3 +1,4 @@
+import { visitUrl } from '../lib/utils/url_utility';
import Flash from '../flash';
import FilteredSearchContainer from './container';
import RecentSearchesRoot from './recent_searches_root';
@@ -566,7 +567,7 @@ class FilteredSearchManager {
if (this.updateObject) {
this.updateObject(parameterizedUrl);
} else {
- gl.utils.visitUrl(parameterizedUrl);
+ visitUrl(parameterizedUrl);
}
}
diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js
index 98837c3b2a0..6110d961609 100644
--- a/app/assets/javascripts/fly_out_nav.js
+++ b/app/assets/javascripts/fly_out_nav.js
@@ -21,7 +21,7 @@ let headerHeight = 50;
export const getHeaderHeight = () => headerHeight;
-export const isSidebarCollapsed = () => sidebar && sidebar.classList.contains('sidebar-icons-only');
+export const isSidebarCollapsed = () => sidebar && sidebar.classList.contains('sidebar-collapsed-desktop');
export const canShowActiveSubItems = (el) => {
if (el.classList.contains('active') && !isSidebarCollapsed()) {
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 7ca783d3af6..cf4a70e321e 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -2,6 +2,7 @@
/* global fuzzaldrinPlus */
import _ from 'underscore';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
+import { visitUrl } from './lib/utils/url_utility';
import { isObject } from './lib/utils/type_utility';
var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote, GitLabDropdownInput;
@@ -852,7 +853,7 @@ GitLabDropdown = (function() {
if ($el.length) {
var href = $el.attr('href');
if (href && href !== '#') {
- gl.utils.visitUrl(href);
+ visitUrl(href);
} else {
$el.trigger('click');
}
diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue
index 2c0b6ab4ea8..241e026b84c 100644
--- a/app/assets/javascripts/groups/components/app.vue
+++ b/app/assets/javascripts/groups/components/app.vue
@@ -5,7 +5,7 @@ import eventHub from '../event_hub';
import { getParameterByName } from '../../lib/utils/common_utils';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import { COMMON_STR } from '../constants';
-
+import { mergeUrlParams } from '../../lib/utils/url_utility';
import groupsComponent from './groups.vue';
export default {
@@ -93,7 +93,7 @@ export default {
this.isLoading = false;
$.scrollTo(0);
- const currentPath = gl.utils.mergeUrlParams({ page }, window.location.href);
+ const currentPath = mergeUrlParams({ page }, window.location.href);
window.history.replaceState({
page: currentPath,
}, document.title, currentPath);
diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue
index c76ce762b54..6421547bbde 100644
--- a/app/assets/javascripts/groups/components/group_item.vue
+++ b/app/assets/javascripts/groups/components/group_item.vue
@@ -1,4 +1,5 @@
<script>
+import { visitUrl } from '../../lib/utils/url_utility';
import tooltip from '../../vue_shared/directives/tooltip';
import identicon from '../../vue_shared/components/identicon.vue';
import eventHub from '../event_hub';
@@ -60,7 +61,7 @@ export default {
if (this.hasChildren) {
eventHub.$emit('toggleChildren', this.group);
} else {
- gl.utils.visitUrl(this.group.relativePath);
+ visitUrl(this.group.relativePath);
}
}
},
diff --git a/app/assets/javascripts/groups/new_group_child.js b/app/assets/javascripts/groups/new_group_child.js
index 8e273579aae..a120d501e35 100644
--- a/app/assets/javascripts/groups/new_group_child.js
+++ b/app/assets/javascripts/groups/new_group_child.js
@@ -1,3 +1,4 @@
+import { visitUrl } from '../lib/utils/url_utility';
import DropLab from '../droplab/drop_lab';
import ISetter from '../droplab/plugins/input_setter';
@@ -54,9 +55,9 @@ export default class NewGroupChild {
onClickNewGroupChildButton(e) {
if (e.target.dataset.action === NEW_PROJECT) {
- gl.utils.visitUrl(this.newGroupPath);
+ visitUrl(this.newGroupPath);
} else if (e.target.dataset.action === NEW_SUBGROUP) {
- gl.utils.visitUrl(this.subgroupPath);
+ visitUrl(this.subgroupPath);
}
}
}
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 7de07e9403d..91b5ef1c10a 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -8,18 +8,7 @@ import IssuablesHelper from './helpers/issuables_helper';
export default class Issue {
constructor() {
- if ($('a.btn-close').length) {
- this.taskList = new TaskList({
- dataType: 'issue',
- fieldName: 'description',
- selector: '.detail-page-description',
- onSuccess: (result) => {
- document.querySelector('#task_status').innerText = result.task_status;
- document.querySelector('#task_status_short').innerText = result.task_status_short;
- }
- });
- this.initIssueBtnEventListeners();
- }
+ if ($('a.btn-close').length) this.initIssueBtnEventListeners();
Issue.$btnNewBranch = $('#new-branch');
Issue.createMrDropdownWrap = document.querySelector('.create-mr-dropdown-wrap');
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index 5bdc7c99503..fd1a50dd533 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -1,5 +1,6 @@
<script>
import Visibility from 'visibilityjs';
+import { visitUrl } from '../../lib/utils/url_utility';
import Poll from '../../lib/utils/poll';
import eventHub from '../event_hub';
import Service from '../services/index';
@@ -8,7 +9,7 @@ import titleComponent from './title.vue';
import descriptionComponent from './description.vue';
import editedComponent from './edited.vue';
import formComponent from './form.vue';
-import '../../lib/utils/url_utility';
+import RecaptchaDialogImplementor from '../../vue_shared/mixins/recaptcha_dialog_implementor';
export default {
props: {
@@ -149,6 +150,11 @@ export default {
editedComponent,
formComponent,
},
+
+ mixins: [
+ RecaptchaDialogImplementor,
+ ],
+
methods: {
openForm() {
if (!this.showForm) {
@@ -164,12 +170,14 @@ export default {
closeForm() {
this.showForm = false;
},
+
updateIssuable() {
this.service.updateIssuable(this.store.formState)
.then(res => res.json())
+ .then(data => this.checkForSpam(data))
.then((data) => {
if (location.pathname !== data.web_url) {
- gl.utils.visitUrl(data.web_url);
+ visitUrl(data.web_url);
}
return this.service.getData();
@@ -179,11 +187,24 @@ export default {
this.store.updateState(data);
eventHub.$emit('close.form');
})
- .catch(() => {
- eventHub.$emit('close.form');
- window.Flash(`Error updating ${this.issuableType}`);
+ .catch((error) => {
+ if (error && error.name === 'SpamError') {
+ this.openRecaptcha();
+ } else {
+ eventHub.$emit('close.form');
+ window.Flash(`Error updating ${this.issuableType}`);
+ }
});
},
+
+ closeRecaptchaDialog() {
+ this.store.setFormState({
+ updateLoading: false,
+ });
+
+ this.closeRecaptcha();
+ },
+
deleteIssuable() {
this.service.deleteIssuable()
.then(res => res.json())
@@ -191,7 +212,7 @@ export default {
// Stop the poll so we don't get 404's with the issuable not existing
this.poll.stop();
- gl.utils.visitUrl(data.web_url);
+ visitUrl(data.web_url);
})
.catch(() => {
eventHub.$emit('close.form');
@@ -237,9 +258,9 @@ export default {
</script>
<template>
- <div>
+<div>
+ <div v-if="canUpdate && showForm">
<form-component
- v-if="canUpdate && showForm"
:form-state="formState"
:can-destroy="canDestroy"
:issuable-templates="issuableTemplates"
@@ -251,30 +272,37 @@ export default {
:can-attach-file="canAttachFile"
:enable-autocomplete="enableAutocomplete"
/>
- <div v-else>
- <title-component
- :issuable-ref="issuableRef"
- :can-update="canUpdate"
- :title-html="state.titleHtml"
- :title-text="state.titleText"
- :show-inline-edit-button="showInlineEditButton"
- />
- <description-component
- v-if="state.descriptionHtml"
- :can-update="canUpdate"
- :description-html="state.descriptionHtml"
- :description-text="state.descriptionText"
- :updated-at="state.updatedAt"
- :task-status="state.taskStatus"
- :issuable-type="issuableType"
- :update-url="updateEndpoint"
- />
- <edited-component
- v-if="hasUpdated"
- :updated-at="state.updatedAt"
- :updated-by-name="state.updatedByName"
- :updated-by-path="state.updatedByPath"
- />
- </div>
+
+ <recaptcha-dialog
+ v-show="showRecaptcha"
+ :html="recaptchaHTML"
+ @close="closeRecaptchaDialog"
+ />
+ </div>
+ <div v-else>
+ <title-component
+ :issuable-ref="issuableRef"
+ :can-update="canUpdate"
+ :title-html="state.titleHtml"
+ :title-text="state.titleText"
+ :show-inline-edit-button="showInlineEditButton"
+ />
+ <description-component
+ v-if="state.descriptionHtml"
+ :can-update="canUpdate"
+ :description-html="state.descriptionHtml"
+ :description-text="state.descriptionText"
+ :updated-at="state.updatedAt"
+ :task-status="state.taskStatus"
+ :issuable-type="issuableType"
+ :update-url="updateEndpoint"
+ />
+ <edited-component
+ v-if="hasUpdated"
+ :updated-at="state.updatedAt"
+ :updated-by-name="state.updatedByName"
+ :updated-by-path="state.updatedByPath"
+ />
</div>
+</div>
</template>
diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue
index b7559ced946..feb73481422 100644
--- a/app/assets/javascripts/issue_show/components/description.vue
+++ b/app/assets/javascripts/issue_show/components/description.vue
@@ -1,9 +1,14 @@
<script>
import animateMixin from '../mixins/animate';
import TaskList from '../../task_list';
+ import RecaptchaDialogImplementor from '../../vue_shared/mixins/recaptcha_dialog_implementor';
export default {
- mixins: [animateMixin],
+ mixins: [
+ animateMixin,
+ RecaptchaDialogImplementor,
+ ],
+
props: {
canUpdate: {
type: Boolean,
@@ -51,6 +56,7 @@
this.updateTaskStatusText();
},
},
+
methods: {
renderGFM() {
$(this.$refs['gfm-content']).renderGFM();
@@ -61,9 +67,19 @@
dataType: this.issuableType,
fieldName: 'description',
selector: '.detail-page-description',
+ onSuccess: this.taskListUpdateSuccess.bind(this),
});
}
},
+
+ taskListUpdateSuccess(data) {
+ try {
+ this.checkForSpam(data);
+ } catch (error) {
+ if (error && error.name === 'SpamError') this.openRecaptcha();
+ }
+ },
+
updateTaskStatusText() {
const taskRegexMatches = this.taskStatus.match(/(\d+) of ((?!0)\d+)/);
const $issuableHeader = $('.issuable-meta');
@@ -109,5 +125,11 @@
:data-update-url="updateUrl"
>
</textarea>
+
+ <recaptcha-dialog
+ v-show="showRecaptcha"
+ :html="recaptchaHTML"
+ @close="closeRecaptcha"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js
index aca9dec2a96..a21ce41e65e 100644
--- a/app/assets/javascripts/issue_show/index.js
+++ b/app/assets/javascripts/issue_show/index.js
@@ -5,7 +5,7 @@ import '../vue_shared/vue_resource_interceptor';
document.addEventListener('DOMContentLoaded', () => {
const initialDataEl = document.getElementById('js-issuable-app-initial-data');
- const initialData = JSON.parse(initialDataEl.innerHTML.replace(/&quot;/g, '"'));
+ const props = JSON.parse(initialDataEl.innerHTML.replace(/&quot;/g, '"'));
$('.issuable-edit').on('click', (e) => {
e.preventDefault();
@@ -18,32 +18,9 @@ document.addEventListener('DOMContentLoaded', () => {
components: {
issuableApp,
},
- data() {
- return {
- ...initialData,
- };
- },
render(createElement) {
return createElement('issuable-app', {
- props: {
- canUpdate: this.canUpdate,
- canDestroy: this.canDestroy,
- endpoint: this.endpoint,
- issuableRef: this.issuableRef,
- initialTitleHtml: this.initialTitleHtml,
- initialTitleText: this.initialTitleText,
- initialDescriptionHtml: this.initialDescriptionHtml,
- initialDescriptionText: this.initialDescriptionText,
- issuableTemplates: this.issuableTemplates,
- markdownPreviewPath: this.markdownPreviewPath,
- markdownDocsPath: this.markdownDocsPath,
- projectPath: this.projectPath,
- projectNamespace: this.projectNamespace,
- updatedAt: this.updatedAt,
- updatedByName: this.updatedByName,
- updatedByPath: this.updatedByPath,
- initialTaskStatus: this.initialTaskStatus,
- },
+ props,
});
},
});
diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js
index 8c011a36dd6..198a7823381 100644
--- a/app/assets/javascripts/job.js
+++ b/app/assets/javascripts/job.js
@@ -1,4 +1,5 @@
import _ from 'underscore';
+import { visitUrl } from './lib/utils/url_utility';
import bp from './breakpoints';
import { bytesToKiB } from './lib/utils/number_utils';
import { setCiStatusFavicon } from './lib/utils/common_utils';
@@ -10,7 +11,7 @@ export default class Job {
this.state = null;
this.options = options || $('.js-build-options').data();
- this.pageUrl = this.options.pageUrl;
+ this.pagePath = this.options.pagePath;
this.buildStatus = this.options.buildStatus;
this.state = this.options.logState;
this.buildStage = this.options.buildStage;
@@ -168,11 +169,11 @@ export default class Job {
getBuildTrace() {
return $.ajax({
- url: `${this.pageUrl}/trace.json`,
+ url: `${this.pagePath}/trace.json`,
data: { state: this.state },
})
.done((log) => {
- setCiStatusFavicon(`${this.pageUrl}/status.json`);
+ setCiStatusFavicon(`${this.pagePath}/status.json`);
if (log.state) {
this.state = log.state;
@@ -210,7 +211,7 @@ export default class Job {
}
if (log.status !== this.buildStatus) {
- gl.utils.visitUrl(this.pageUrl);
+ visitUrl(this.pagePath);
}
})
.fail(() => {
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 33cc807912c..b5328c77b25 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -1,3 +1,4 @@
+import { getLocationHash } from './url_utility';
export const getPagePath = (index = 0) => $('body').attr('data-page').split(':')[index];
@@ -65,7 +66,7 @@ export const disableButtonIfEmptyField = (fieldSelector, buttonSelector, eventNa
// automatically adjust scroll position for hash urls taking the height of the navbar into account
// https://github.com/twitter/bootstrap/issues/1768
export const handleLocationHash = () => {
- let hash = window.gl.utils.getLocationHash();
+ let hash = getLocationHash();
if (!hash) return;
// This is required to handle non-unicode characters in hash
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 17236c91490..f1ee9c8f2e5 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -1,93 +1,69 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, one-var, one-var-declaration-per-line, no-void, guard-for-in, no-restricted-syntax, prefer-template, quotes, max-len */
-
-var base;
-var w = window;
-if (w.gl == null) {
- w.gl = {};
-}
-if ((base = w.gl).utils == null) {
- base.utils = {};
-}
// Returns an array containing the value(s) of the
// of the key passed as an argument
-w.gl.utils.getParameterValues = function(sParam) {
- var i, sPageURL, sParameterName, sURLVariables, values;
- sPageURL = decodeURIComponent(window.location.search.substring(1));
- sURLVariables = sPageURL.split('&');
- sParameterName = void 0;
- values = [];
- i = 0;
- while (i < sURLVariables.length) {
- sParameterName = sURLVariables[i].split('=');
+export function getParameterValues(sParam) {
+ const sPageURL = decodeURIComponent(window.location.search.substring(1));
+
+ return sPageURL.split('&').reduce((acc, urlParam) => {
+ const sParameterName = urlParam.split('=');
+
if (sParameterName[0] === sParam) {
- values.push(sParameterName[1].replace(/\+/g, ' '));
+ acc.push(sParameterName[1].replace(/\+/g, ' '));
}
- i += 1;
- }
- return values;
-};
+
+ return acc;
+ }, []);
+}
+
// @param {Object} params - url keys and value to merge
// @param {String} url
-w.gl.utils.mergeUrlParams = function(params, url) {
- var lastChar, newUrl, paramName, paramValue, pattern;
- newUrl = decodeURIComponent(url);
- for (paramName in params) {
- paramValue = params[paramName];
- pattern = new RegExp("\\b(" + paramName + "=).*?(&|$)");
- if (paramValue == null) {
- newUrl = newUrl.replace(pattern, '');
+export function mergeUrlParams(params, url) {
+ let newUrl = Object.keys(params).reduce((acc, paramName) => {
+ const paramValue = params[paramName];
+ const pattern = new RegExp(`\\b(${paramName}=).*?(&|$)`);
+
+ if (paramValue === null) {
+ return acc.replace(pattern, '');
} else if (url.search(pattern) !== -1) {
- newUrl = newUrl.replace(pattern, "$1" + paramValue + "$2");
- } else {
- newUrl = "" + newUrl + (newUrl.indexOf('?') > 0 ? '&' : '?') + paramName + "=" + paramValue;
+ return acc.replace(pattern, `$1${paramValue}$2`);
}
- }
+
+ return `${acc}${acc.indexOf('?') > 0 ? '&' : '?'}${paramName}=${paramValue}`;
+ }, decodeURIComponent(url));
+
// Remove a trailing ampersand
- lastChar = newUrl[newUrl.length - 1];
+ const lastChar = newUrl[newUrl.length - 1];
+
if (lastChar === '&') {
newUrl = newUrl.slice(0, -1);
}
+
return newUrl;
-};
-// removes parameter query string from url. returns the modified url
-w.gl.utils.removeParamQueryString = function(url, param) {
- var urlVariables, variables;
- url = decodeURIComponent(url);
- urlVariables = url.split('&');
- return ((function() {
- var j, len, results;
- results = [];
- for (j = 0, len = urlVariables.length; j < len; j += 1) {
- variables = urlVariables[j];
- if (variables.indexOf(param) === -1) {
- results.push(variables);
- }
- }
- return results;
- })()).join('&');
-};
-w.gl.utils.removeParams = (params) => {
+}
+
+export function removeParamQueryString(url, param) {
+ const decodedUrl = decodeURIComponent(url);
+ const urlVariables = decodedUrl.split('&');
+
+ return urlVariables.filter(variable => variable.indexOf(param) === -1).join('&');
+}
+
+export function removeParams(params) {
const url = document.createElement('a');
url.href = window.location.href;
+
params.forEach((param) => {
- url.search = w.gl.utils.removeParamQueryString(url.search, param);
+ url.search = removeParamQueryString(url.search, param);
});
+
return url.href;
-};
-w.gl.utils.getLocationHash = function(url) {
- var hashIndex;
- if (typeof url === 'undefined') {
- // Note: We can't use window.location.hash here because it's
- // not consistent across browsers - Firefox will pre-decode it
- url = window.location.href;
- }
- hashIndex = url.indexOf('#');
- return hashIndex === -1 ? null : url.substring(hashIndex + 1);
-};
+}
-w.gl.utils.refreshCurrentPage = () => gl.utils.visitUrl(window.location.href);
+export function getLocationHash(url = window.location.href) {
+ const hashIndex = url.indexOf('#');
+
+ return hashIndex === -1 ? null : url.substring(hashIndex + 1);
+}
-// eslint-disable-next-line import/prefer-default-export
export function visitUrl(url, external = false) {
if (external) {
// Simulate `target="blank" rel="noopener noreferrer"`
@@ -100,12 +76,10 @@ export function visitUrl(url, external = false) {
}
}
+export function refreshCurrentPage() {
+ visitUrl(window.location.href);
+}
+
export function redirectTo(url) {
return window.location.assign(url);
}
-
-window.gl = window.gl || {};
-window.gl.utils = {
- ...(window.gl.utils || {}),
- visitUrl,
-};
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index c993cc91de9..d26a9eafd7b 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -28,8 +28,8 @@ import './commit/image_file';
// lib/utils
import { handleLocationHash } from './lib/utils/common_utils';
-import { localTimeAgo, renderTimeago, getLocationHash } from './lib/utils/datetime_utility';
-import './lib/utils/url_utility';
+import { localTimeAgo, renderTimeago } from './lib/utils/datetime_utility';
+import { getLocationHash, visitUrl } from './lib/utils/url_utility';
// behaviors
import './behaviors/';
@@ -40,9 +40,6 @@ import './admin';
import './aside';
import loadAwardsHandler from './awards_handler';
import bp from './breakpoints';
-import './commits';
-import './compare';
-import './compare_autocomplete';
import './confirm_danger_modal';
import Flash, { removeFlashClickListener } from './flash';
import './gl_dropdown';
@@ -294,7 +291,7 @@ $(function () {
const action = `${this.action}${link.search === '' ? '?' : '&'}`;
event.preventDefault();
- gl.utils.visitUrl(`${action}${$(this).serialize()}`);
+ visitUrl(`${action}${$(this).serialize()}`);
});
const flashContainer = document.querySelector('.flash-container');
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 870b0e41a80..e8de17ae634 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -11,6 +11,7 @@ import {
handleLocationHash,
isMetaClick,
} from './lib/utils/common_utils';
+import { getLocationHash } from './lib/utils/url_utility';
import initDiscussionTab from './image_diff/init_discussion_tab';
import Diff from './diff';
import {
@@ -320,7 +321,7 @@ import {
// Scroll any linked note into view
// Similar to `toggler_behavior` in the discussion tab
- const hash = gl.utils.getLocationHash();
+ const hash = getLocationHash();
const anchor = hash && $container.find(`.note[id="${hash}"]`);
if (anchor && anchor.length > 0) {
const notesContent = anchor.closest('.notes_content');
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index cbe24c0915b..8da723ced03 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -21,6 +21,8 @@
hasMetrics: convertPermissionToBoolean(metricsData.hasMetrics),
documentationPath: metricsData.documentationPath,
settingsPath: metricsData.settingsPath,
+ tagsPath: metricsData.tagsPath,
+ projectPath: metricsData.projectPath,
metricsEndpoint: metricsData.additionalMetrics,
deploymentEndpoint: metricsData.deploymentEndpoint,
emptyGettingStartedSvgPath: metricsData.emptyGettingStartedSvgPath,
@@ -112,6 +114,8 @@
:hover-data="hoverData"
:update-aspect-ratio="updateAspectRatio"
:deployment-data="store.deploymentData"
+ :project-path="projectPath"
+ :tags-path="tagsPath"
/>
</graph-group>
</div>
diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue
index f8782fde927..cdae287658b 100644
--- a/app/assets/javascripts/monitoring/components/graph.vue
+++ b/app/assets/javascripts/monitoring/components/graph.vue
@@ -30,6 +30,14 @@
required: false,
default: () => ({}),
},
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ tagsPath: {
+ type: String,
+ required: true,
+ },
},
mixins: [MonitoringMixin],
@@ -251,6 +259,14 @@
:line-color="path.lineColor"
:area-color="path.areaColor"
/>
+ <rect
+ class="prometheus-graph-overlay"
+ :width="(graphWidth - 70)"
+ :height="(graphHeight - 100)"
+ transform="translate(-5, 20)"
+ ref="graphOverlay"
+ @mousemove="handleMouseOverGraph($event)">
+ </rect>
<graph-deployment
:show-deploy-info="showDeployInfo"
:deployment-data="reducedDeploymentData"
@@ -267,14 +283,6 @@
:graph-height-offset="graphHeightOffset"
:show-flag-content="showFlagContent"
/>
- <rect
- class="prometheus-graph-overlay"
- :width="(graphWidth - 70)"
- :height="(graphHeight - 100)"
- transform="translate(-5, 20)"
- ref="graphOverlay"
- @mousemove="handleMouseOverGraph($event)">
- </rect>
</svg>
</svg>
</div>
diff --git a/app/assets/javascripts/monitoring/components/graph/deployment.vue b/app/assets/javascripts/monitoring/components/graph/deployment.vue
index e3b8be0c7fb..026e2fd0c49 100644
--- a/app/assets/javascripts/monitoring/components/graph/deployment.vue
+++ b/app/assets/javascripts/monitoring/components/graph/deployment.vue
@@ -1,5 +1,6 @@
<script>
- import { dateFormat, timeFormat } from '../../utils/date_time_formatters';
+ import { dateFormatWithName, timeFormat } from '../../utils/date_time_formatters';
+ import Icon from '../../../vue_shared/components/icon.vue';
export default {
props: {
@@ -25,6 +26,10 @@
},
},
+ components: {
+ Icon,
+ },
+
computed: {
calculatedHeight() {
return this.graphHeight - this.graphHeightOffset;
@@ -33,7 +38,7 @@
methods: {
refText(d) {
- return d.tag ? d.ref : d.sha.slice(0, 6);
+ return d.tag ? d.ref : d.sha.slice(0, 8);
},
formatTime(deploymentTime) {
@@ -41,7 +46,7 @@
},
formatDate(deploymentTime) {
- return dateFormat(deploymentTime);
+ return dateFormatWithName(deploymentTime);
},
nameDeploymentClass(deployment) {
@@ -54,11 +59,19 @@
positionFlag(deployment) {
let xPosition = 3;
- if (deployment.xPos > (this.graphWidth - 200)) {
- xPosition = -97;
+ if (deployment.xPos > (this.graphWidth - 225)) {
+ xPosition = -142;
}
return xPosition;
},
+
+ svgContainerHeight(tag) {
+ let svgHeight = 80;
+ if (!tag) {
+ svgHeight -= 20;
+ }
+ return svgHeight;
+ },
},
};
</script>
@@ -91,35 +104,75 @@
class="js-deploy-info-box"
:x="positionFlag(deployment)"
y="0"
- width="92"
- height="60">
+ width="134"
+ :height="svgContainerHeight(deployment.tag)">
<rect
class="rect-text-metric deploy-info-rect rect-metric"
x="1"
y="1"
rx="2"
- width="90"
- height="58">
+ width="132"
+ :height="svgContainerHeight(deployment.tag) - 2">
</rect>
- <g
- transform="translate(5, 2)">
- <text
- class="deploy-info-text text-metric-bold">
- {{refText(deployment)}}
- </text>
- </g>
- <text
- class="deploy-info-text"
- y="18"
- transform="translate(5, 2)">
- {{formatDate(deployment.time)}}
- </text>
<text
class="deploy-info-text text-metric-bold"
- y="38"
transform="translate(5, 2)">
- {{formatTime(deployment.time)}}
+ Deployed
</text>
+ <!--The date info-->
+ <g transform="translate(5, 20)">
+ <text class="deploy-info-text">
+ {{formatDate(deployment.time)}}
+ </text>
+ <text
+ class="deploy-info-text text-metric-bold"
+ x="62">
+ {{formatTime(deployment.time)}}
+ </text>
+ </g>
+ <line
+ class="divider-line"
+ x1="0"
+ y1="38"
+ x2="132"
+ :y2="38"
+ stroke="#000">
+ </line>
+ <!--Commit information-->
+ <g transform="translate(5, 40)">
+ <icon
+ name="commit"
+ :width="12"
+ :height="12"
+ :y="3">
+ </icon>
+ <a :xlink:href="deployment.commitUrl">
+ <text
+ class="deploy-info-text deploy-info-text-link"
+ transform="translate(20, 2)">
+ {{refText(deployment)}}
+ </text>
+ </a>
+ </g>
+ <!--Tag information-->
+ <g
+ transform="translate(5, 55)"
+ v-if="deployment.tag">
+ <icon
+ name="label"
+ :width="12"
+ :height="12"
+ :y="5">
+ </icon>
+ <a :xlink:href="deployment.tagUrl">
+ <text
+ class="deploy-info-text deploy-info-text-link"
+ transform="translate(20, 2)"
+ y="2">
+ {{deployment.tag}}
+ </text>
+ </a>
+ </g>
</svg>
</g>
<svg
diff --git a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
index 31f38aca5d6..cbca14ede02 100644
--- a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
+++ b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
@@ -33,7 +33,9 @@ const mixins = {
id: deployment.id,
time,
sha: deployment.sha,
+ commitUrl: `${this.projectPath}/commit/${deployment.sha}`,
tag: deployment.tag,
+ tagUrl: `${this.tagsPath}/${deployment.tag}`,
ref: deployment.ref.name,
xPos,
showDeploymentFlag: false,
diff --git a/app/assets/javascripts/monitoring/utils/date_time_formatters.js b/app/assets/javascripts/monitoring/utils/date_time_formatters.js
index c4c6b1ac1f5..ad07a8465e2 100644
--- a/app/assets/javascripts/monitoring/utils/date_time_formatters.js
+++ b/app/assets/javascripts/monitoring/utils/date_time_formatters.js
@@ -1,6 +1,7 @@
import d3 from 'd3';
export const dateFormat = d3.time.format('%b %-d, %Y');
+export const dateFormatWithName = d3.time.format('%a, %b %-d');
export const timeFormat = d3.time.format('%-I:%M%p');
export const bisectDate = d3.bisector(d => d.time).left;
diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js
index 1d496c64e53..aa377327107 100644
--- a/app/assets/javascripts/namespace_select.js
+++ b/app/assets/javascripts/namespace_select.js
@@ -1,6 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, no-var, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, max-len */
import Api from './api';
-import './lib/utils/url_utility';
+import { mergeUrlParams } from './lib/utils/url_utility';
export default class NamespaceSelect {
constructor(opts) {
@@ -50,7 +50,7 @@ export default class NamespaceSelect {
}
},
url(namespace) {
- return gl.utils.mergeUrlParams({ [fieldName]: namespace.id }, window.location.href);
+ return mergeUrlParams({ [fieldName]: namespace.id }, window.location.href);
},
});
}
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index f487928cb84..042fe44e1c6 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -16,6 +16,7 @@ import Autosize from 'autosize';
import 'vendor/jquery.caret'; // required by jquery.atwho
import 'vendor/jquery.atwho';
import AjaxCache from '~/lib/utils/ajax_cache';
+import { getLocationHash } from './lib/utils/url_utility';
import Flash from './flash';
import CommentTypeToggle from './comment_type_toggle';
import GLForm from './gl_form';
@@ -331,7 +332,7 @@ export default class Notes {
}
static updateNoteTargetSelector($note) {
- const hash = gl.utils.getLocationHash();
+ const hash = getLocationHash();
// Needs to be an explicit true/false for the jQuery `toggleClass(force)`
const addTargetClass = Boolean(hash && $note.filter(`#${hash}`).length > 0);
$note.toggleClass('target', addTargetClass);
diff --git a/app/assets/javascripts/notes/components/issue_note.vue b/app/assets/javascripts/notes/components/issue_note.vue
index 8c81c5d6df3..3ceb961f58e 100644
--- a/app/assets/javascripts/notes/components/issue_note.vue
+++ b/app/assets/javascripts/notes/components/issue_note.vue
@@ -1,5 +1,6 @@
<script>
import { mapGetters, mapActions } from 'vuex';
+ import { escape } from 'underscore';
import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteHeader from './note_header.vue';
@@ -85,7 +86,7 @@
};
this.isRequesting = true;
this.oldContent = this.note.note_html;
- this.note.note_html = noteText;
+ this.note.note_html = escape(noteText);
this.updateNote(data)
.then(() => {
diff --git a/app/assets/javascripts/notes/components/issue_notes_app.vue b/app/assets/javascripts/notes/components/issue_notes_app.vue
index 4cfcffa2391..e4d01285d39 100644
--- a/app/assets/javascripts/notes/components/issue_notes_app.vue
+++ b/app/assets/javascripts/notes/components/issue_notes_app.vue
@@ -1,5 +1,6 @@
<script>
import { mapGetters, mapActions } from 'vuex';
+ import { getLocationHash } from '../../lib/utils/url_utility';
import Flash from '../../flash';
import store from '../stores/';
import * as constants from '../constants';
@@ -95,7 +96,7 @@
this.poll();
},
checkLocationHash() {
- const hash = gl.utils.getLocationHash();
+ const hash = getLocationHash();
const element = document.getElementById(hash);
if (hash && element) {
diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js
index 8d74c5de5cf..a94163a5f87 100644
--- a/app/assets/javascripts/notes/index.js
+++ b/app/assets/javascripts/notes/index.js
@@ -8,10 +8,18 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
},
data() {
const notesDataset = document.getElementById('js-vue-notes').dataset;
+ const parsedUserData = JSON.parse(notesDataset.currentUserData);
+ const currentUserData = parsedUserData ? {
+ id: parsedUserData.id,
+ name: parsedUserData.name,
+ username: parsedUserData.username,
+ avatar_url: parsedUserData.avatar_path || parsedUserData.avatar_url,
+ path: parsedUserData.path,
+ } : {};
return {
noteableData: JSON.parse(notesDataset.noteableData),
- currentUserData: JSON.parse(notesDataset.currentUserData),
+ currentUserData,
notesData: {
lastFetchedAt: notesDataset.lastFetchedAt,
discussionsPath: notesDataset.discussionsPath,
diff --git a/app/assets/javascripts/pager.js b/app/assets/javascripts/pager.js
index e3fc1e2fc2f..6792b984cc5 100644
--- a/app/assets/javascripts/pager.js
+++ b/app/assets/javascripts/pager.js
@@ -1,5 +1,5 @@
import { getParameterByName } from '~/lib/utils/common_utils';
-import '~/lib/utils/url_utility';
+import { removeParams } from './lib/utils/url_utility';
(() => {
const ENDLESS_SCROLL_BOTTOM_PX = 400;
@@ -7,7 +7,7 @@ import '~/lib/utils/url_utility';
const Pager = {
init(limit = 0, preload = false, disable = false, prepareData = $.noop, callback = $.noop) {
- this.url = $('.content_list').data('href') || gl.utils.removeParams(['limit', 'offset']);
+ this.url = $('.content_list').data('href') || removeParams(['limit', 'offset']);
this.limit = limit;
this.offset = parseInt(getParameterByName('offset'), 10) || this.limit;
this.disable = disable;
diff --git a/app/assets/javascripts/performance_bar.js b/app/assets/javascripts/performance_bar.js
index 9bbdf7f513c..0562a681c4b 100644
--- a/app/assets/javascripts/performance_bar.js
+++ b/app/assets/javascripts/performance_bar.js
@@ -1,5 +1,6 @@
import 'vendor/peek';
import 'vendor/peek.performance_bar';
+import { getParameterValues } from './lib/utils/url_utility';
export default class PerformanceBar {
constructor(opts) {
@@ -39,7 +40,7 @@ export default class PerformanceBar {
}
handleLineProfileLink(e) {
- const lineProfilerParameter = gl.utils.getParameterValues('lineprofiler');
+ const lineProfilerParameter = getParameterValues('lineprofiler');
const lineProfilerParameterRegex = new RegExp(`lineprofiler=${lineProfilerParameter[0]}`);
const shouldToggleModal = lineProfilerParameter.length > 0 &&
lineProfilerParameterRegex.test(e.currentTarget.href);
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
index 3131e71d9d6..d4f26b81f30 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/project.js
@@ -1,6 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, no-var, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */
import Cookies from 'js-cookie';
+import { visitUrl } from './lib/utils/url_utility';
import projectSelect from './project_select';
export default class Project {
@@ -122,7 +123,7 @@ export default class Project {
var action = $form.attr('action');
var divider = action.indexOf('?') === -1 ? '?' : '&';
if (shouldVisit) {
- gl.utils.visitUrl(`${action}${divider}${$form.serialize()}`);
+ visitUrl(`${action}${divider}${$form.serialize()}`);
}
}
},
diff --git a/app/assets/javascripts/projects/ci_cd_settings_bundle.js b/app/assets/javascripts/projects/ci_cd_settings_bundle.js
deleted file mode 100644
index 90e418f6771..00000000000
--- a/app/assets/javascripts/projects/ci_cd_settings_bundle.js
+++ /dev/null
@@ -1,19 +0,0 @@
-function updateAutoDevopsRadios(radioWrappers) {
- radioWrappers.forEach((radioWrapper) => {
- const radio = radioWrapper.querySelector('.js-auto-devops-enable-radio');
- const runPipelineCheckboxWrapper = radioWrapper.querySelector('.js-run-auto-devops-pipeline-checkbox-wrapper');
- const runPipelineCheckbox = radioWrapper.querySelector('.js-run-auto-devops-pipeline-checkbox');
-
- if (runPipelineCheckbox) {
- runPipelineCheckbox.checked = radio.checked;
- runPipelineCheckboxWrapper.classList.toggle('hide', !radio.checked);
- }
- });
-}
-
-export default function initCiCdSettings() {
- const radioWrappers = document.querySelectorAll('.js-auto-devops-enable-radio-wrapper');
- radioWrappers.forEach(radioWrapper =>
- radioWrapper.addEventListener('change', () => updateAutoDevopsRadios(radioWrappers)),
- );
-}
diff --git a/app/assets/javascripts/projects/project_import_gitlab_project.js b/app/assets/javascripts/projects/project_import_gitlab_project.js
index c34927499fc..cec6f0dd5a3 100644
--- a/app/assets/javascripts/projects/project_import_gitlab_project.js
+++ b/app/assets/javascripts/projects/project_import_gitlab_project.js
@@ -1,7 +1,7 @@
-import '../lib/utils/url_utility';
+import { getParameterValues } from '../lib/utils/url_utility';
const bindEvents = () => {
- const path = gl.utils.getParameterValues('path')[0];
+ const path = getParameterValues('path')[0];
// get the path url and append it in the inputS
$('.js-path-name').val(path);
diff --git a/app/assets/javascripts/repo/stores/actions.js b/app/assets/javascripts/repo/stores/actions.js
index 120ce96f44d..af5dcf054ef 100644
--- a/app/assets/javascripts/repo/stores/actions.js
+++ b/app/assets/javascripts/repo/stores/actions.js
@@ -1,9 +1,10 @@
import Vue from 'vue';
+import { visitUrl } from '../../lib/utils/url_utility';
import flash from '../../flash';
import service from '../services';
import * as types from './mutation_types';
-export const redirectToUrl = (_, url) => gl.utils.visitUrl(url);
+export const redirectToUrl = (_, url) => visitUrl(url);
export const setInitialData = ({ commit }, data) => commit(types.SET_INITIAL_DATA, data);
diff --git a/app/assets/javascripts/repo/stores/actions/tree.js b/app/assets/javascripts/repo/stores/actions/tree.js
index aa830e946a2..7c251e26bed 100644
--- a/app/assets/javascripts/repo/stores/actions/tree.js
+++ b/app/assets/javascripts/repo/stores/actions/tree.js
@@ -1,3 +1,4 @@
+import { visitUrl } from '../../../lib/utils/url_utility';
import { normalizeHeaders } from '../../../lib/utils/common_utils';
import flash from '../../../flash';
import service from '../../services';
@@ -73,7 +74,7 @@ export const clickedTreeRow = ({ commit, dispatch }, row) => {
} else if (row.type === 'submodule') {
commit(types.TOGGLE_LOADING, row);
- gl.utils.visitUrl(row.url);
+ visitUrl(row.url);
} else if (row.type === 'blob' && row.opened) {
dispatch('setFileActive', row);
} else {
diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js
index ebe7a99ffae..130730b1700 100644
--- a/app/assets/javascripts/shortcuts.js
+++ b/app/assets/javascripts/shortcuts.js
@@ -1,5 +1,6 @@
import Cookies from 'js-cookie';
import Mousetrap from 'mousetrap';
+import { refreshCurrentPage, visitUrl } from './lib/utils/url_utility';
import findAndFollowLink from './shortcuts_dashboard_navigation';
const defaultStopCallback = Mousetrap.stopCallback;
@@ -38,7 +39,7 @@ export default class Shortcuts {
if (typeof findFileURL !== 'undefined' && findFileURL !== null) {
Mousetrap.bind('t', () => {
- gl.utils.visitUrl(findFileURL);
+ visitUrl(findFileURL);
});
}
@@ -62,7 +63,7 @@ export default class Shortcuts {
} else {
Cookies.set(performanceBarCookieName, 'true', { path: '/' });
}
- gl.utils.refreshCurrentPage();
+ refreshCurrentPage();
}
static toggleMarkdownPreview(e) {
diff --git a/app/assets/javascripts/shortcuts_blob.js b/app/assets/javascripts/shortcuts_blob.js
index fbc57bb4304..cf309be4f6f 100644
--- a/app/assets/javascripts/shortcuts_blob.js
+++ b/app/assets/javascripts/shortcuts_blob.js
@@ -1,5 +1,5 @@
/* global Mousetrap */
-
+import { getLocationHash, visitUrl } from './lib/utils/url_utility';
import Shortcuts from './shortcuts';
const defaults = {
@@ -18,9 +18,9 @@ export default class ShortcutsBlob extends Shortcuts {
moveToFilePermalink() {
if (this.options.fileBlobPermalinkUrl) {
- const hash = gl.utils.getLocationHash();
+ const hash = getLocationHash();
const hashUrlString = hash ? `#${hash}` : '';
- gl.utils.visitUrl(`${this.options.fileBlobPermalinkUrl}${hashUrlString}`);
+ visitUrl(`${this.options.fileBlobPermalinkUrl}${hashUrlString}`);
}
}
}
diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js
index d4c07a188b3..ed7ab09be06 100644
--- a/app/assets/javascripts/sidebar/sidebar_mediator.js
+++ b/app/assets/javascripts/sidebar/sidebar_mediator.js
@@ -1,3 +1,4 @@
+import { visitUrl } from '../lib/utils/url_utility';
import Flash from '../flash';
import Service from './services/sidebar_service';
import Store from './stores/sidebar_store';
@@ -81,7 +82,7 @@ export default class SidebarMediator {
.then(response => response.json())
.then((data) => {
if (location.pathname !== data.web_url) {
- gl.utils.visitUrl(data.web_url);
+ visitUrl(data.web_url);
}
});
}
diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js
index 2fffe09c74e..748caecf153 100644
--- a/app/assets/javascripts/todos.js
+++ b/app/assets/javascripts/todos.js
@@ -1,5 +1,5 @@
/* eslint-disable class-methods-use-this, no-unneeded-ternary, quote-props */
-
+import { visitUrl } from './lib/utils/url_utility';
import UsersSelect from './users_select';
import { isMetaClick } from './lib/utils/common_utils';
@@ -150,7 +150,7 @@ export default class Todos {
window.open(todoLink, windowTarget);
} else {
- gl.utils.visitUrl(todoLink);
+ visitUrl(todoLink);
}
}
}
diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js
index 7777ed1c3dc..1a0b2c0415b 100644
--- a/app/assets/javascripts/tree.js
+++ b/app/assets/javascripts/tree.js
@@ -1,4 +1,5 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, class-methods-use-this */
+import { visitUrl } from './lib/utils/url_utility';
export default class TreeView {
constructor() {
@@ -14,7 +15,7 @@ export default class TreeView {
e.preventDefault();
return window.open(path, '_blank');
} else {
- return gl.utils.visitUrl(path);
+ return visitUrl(path);
}
}
});
@@ -56,7 +57,7 @@ export default class TreeView {
} else if (e.which === 13) {
path = $('.tree-item.selected .tree-item-file-name a').attr('href');
if (path) {
- return gl.utils.visitUrl(path);
+ return visitUrl(path);
}
}
});
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
index 00af57d4027..ee1a45cc754 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
@@ -1,4 +1,5 @@
import { getTimeago } from '~/lib/utils/datetime_utility';
+import { visitUrl } from '../../lib/utils/url_utility';
import Flash from '../../flash';
import MemoryUsage from './mr_widget_memory_usage';
import StatusIcon from './mr_widget_status_icon';
@@ -36,7 +37,7 @@ export default {
.then(res => res.json())
.then((res) => {
if (res.redirect_url) {
- gl.utils.visitUrl(res.redirect_url);
+ visitUrl(res.redirect_url);
}
})
.catch(() => {
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
index 1274db2c4c8..9cb3edead86 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
@@ -1,3 +1,4 @@
+import Project from '~/project';
import SmartInterval from '~/smart_interval';
import Flash from '../flash';
import {
@@ -140,6 +141,7 @@ export default {
const el = document.createElement('div');
el.innerHTML = res.body;
document.body.appendChild(el);
+ Project.initRefSwitcher();
}
})
.catch(() => {
diff --git a/app/assets/javascripts/vue_shared/components/icon.vue b/app/assets/javascripts/vue_shared/components/icon.vue
index 4216660da8c..365229ea274 100644
--- a/app/assets/javascripts/vue_shared/components/icon.vue
+++ b/app/assets/javascripts/vue_shared/components/icon.vue
@@ -36,6 +36,30 @@
required: false,
default: '',
},
+
+ width: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+
+ height: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+
+ y: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+
+ x: {
+ type: Number,
+ required: false,
+ default: null,
+ },
},
computed: {
@@ -51,7 +75,11 @@
<template>
<svg
- :class="[iconSizeClass, cssClasses]">
+ :class="[iconSizeClass, cssClasses]"
+ :width="width"
+ :height="height"
+ :x="x"
+ :y="y">
<use
v-bind="{'xlink:href':spriteHref}"/>
</svg>
diff --git a/app/assets/javascripts/vue_shared/components/popup_dialog.vue b/app/assets/javascripts/vue_shared/components/popup_dialog.vue
index 47efee64c6e..6d15bbd84ba 100644
--- a/app/assets/javascripts/vue_shared/components/popup_dialog.vue
+++ b/app/assets/javascripts/vue_shared/components/popup_dialog.vue
@@ -38,7 +38,8 @@ export default {
},
primaryButtonLabel: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
submitDisabled: {
type: Boolean,
@@ -113,8 +114,9 @@ export default {
{{ closeButtonLabel }}
</button>
<button
+ v-if="primaryButtonLabel"
type="button"
- class="btn pull-right"
+ class="btn pull-right js-primary-button"
:disabled="submitDisabled"
:class="btnKindClass"
@click="emitSubmit(true)">
diff --git a/app/assets/javascripts/vue_shared/components/recaptcha_dialog.vue b/app/assets/javascripts/vue_shared/components/recaptcha_dialog.vue
new file mode 100644
index 00000000000..3ec50f14eb4
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/recaptcha_dialog.vue
@@ -0,0 +1,85 @@
+<script>
+import PopupDialog from './popup_dialog.vue';
+
+export default {
+ name: 'recaptcha-dialog',
+
+ props: {
+ html: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+
+ data() {
+ return {
+ script: {},
+ scriptSrc: 'https://www.google.com/recaptcha/api.js',
+ };
+ },
+
+ components: {
+ PopupDialog,
+ },
+
+ methods: {
+ appendRecaptchaScript() {
+ this.removeRecaptchaScript();
+
+ const script = document.createElement('script');
+ script.src = this.scriptSrc;
+ script.classList.add('js-recaptcha-script');
+ script.async = true;
+ script.defer = true;
+
+ this.script = script;
+
+ document.body.appendChild(script);
+ },
+
+ removeRecaptchaScript() {
+ if (this.script instanceof Element) this.script.remove();
+ },
+
+ close() {
+ this.removeRecaptchaScript();
+ this.$emit('close');
+ },
+
+ submit() {
+ this.$el.querySelector('form').submit();
+ },
+ },
+
+ watch: {
+ html() {
+ this.appendRecaptchaScript();
+ },
+ },
+
+ mounted() {
+ window.recaptchaDialogCallback = this.submit.bind(this);
+ },
+};
+</script>
+
+<template>
+<popup-dialog
+ kind="warning"
+ class="recaptcha-dialog js-recaptcha-dialog"
+ :hide-footer="true"
+ :title="__('Please solve the reCAPTCHA')"
+ @toggle="close"
+>
+ <div slot="body">
+ <p>
+ {{__('We want to be sure it is you, please confirm you are not a robot.')}}
+ </p>
+ <div
+ ref="recaptcha"
+ v-html="html"
+ ></div>
+ </div>
+</popup-dialog>
+</template>
diff --git a/app/assets/javascripts/vue_shared/mixins/recaptcha_dialog_implementor.js b/app/assets/javascripts/vue_shared/mixins/recaptcha_dialog_implementor.js
new file mode 100644
index 00000000000..ef70f9432e3
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/mixins/recaptcha_dialog_implementor.js
@@ -0,0 +1,36 @@
+import RecaptchaDialog from '../components/recaptcha_dialog.vue';
+
+export default {
+ data() {
+ return {
+ showRecaptcha: false,
+ recaptchaHTML: '',
+ };
+ },
+
+ components: {
+ RecaptchaDialog,
+ },
+
+ methods: {
+ openRecaptcha() {
+ this.showRecaptcha = true;
+ },
+
+ closeRecaptcha() {
+ this.showRecaptcha = false;
+ },
+
+ checkForSpam(data) {
+ if (!data.recaptcha_html) return data;
+
+ this.recaptchaHTML = data.recaptcha_html;
+
+ const spamError = new Error(data.error_message);
+ spamError.name = 'SpamError';
+ spamError.message = 'SpamError';
+
+ throw spamError;
+ },
+ },
+};
diff --git a/app/assets/stylesheets/framework/contextual-sidebar.scss b/app/assets/stylesheets/framework/contextual-sidebar.scss
index b73932eb7e1..26a2db99e0a 100644
--- a/app/assets/stylesheets/framework/contextual-sidebar.scss
+++ b/app/assets/stylesheets/framework/contextual-sidebar.scss
@@ -1,4 +1,6 @@
.page-with-contextual-sidebar {
+ transition: padding-left $sidebar-transition-duration;
+
@media (min-width: $screen-md-min) {
padding-left: $contextual-sidebar-collapsed-width;
}
@@ -27,8 +29,10 @@
.context-header {
position: relative;
margin-right: 2px;
+ width: $contextual-sidebar-width;
a {
+ transition: padding $sidebar-transition-duration;
font-weight: $gl-font-weight-bold;
display: flex;
align-items: center;
@@ -63,10 +67,10 @@
}
.nav-sidebar {
+ transition: width $sidebar-transition-duration, left $sidebar-transition-duration;
position: fixed;
z-index: 400;
width: $contextual-sidebar-width;
- transition: left $sidebar-transition-duration;
top: $header-height;
bottom: 0;
left: 0;
@@ -74,16 +78,15 @@
box-shadow: inset -2px 0 0 $border-color;
transform: translate3d(0, 0, 0);
- &:not(.sidebar-icons-only) {
+ &:not(.sidebar-collapsed-desktop) {
@media (min-width: $screen-sm-min) and (max-width: $screen-md-max) {
box-shadow: inset -2px 0 0 $border-color,
2px 1px 3px $dropdown-shadow-color;
}
}
- &.sidebar-icons-only {
- width: auto;
- min-width: $contextual-sidebar-collapsed-width;
+ &.sidebar-collapsed-desktop {
+ width: $contextual-sidebar-collapsed-width;
.nav-sidebar-inner-scroll {
overflow-x: hidden;
@@ -108,12 +111,11 @@
}
}
- &.nav-sidebar-expanded {
+ &.sidebar-expanded-mobile {
left: 0;
}
a {
- transition: none;
text-decoration: none;
}
@@ -126,9 +128,10 @@
white-space: nowrap;
a {
+ transition: padding $sidebar-transition-duration;
display: flex;
align-items: center;
- padding: 12px 16px;
+ padding: 12px 15px;
color: $gl-text-color-secondary;
}
@@ -288,7 +291,8 @@
> a {
margin-left: 4px;
- padding-left: 12px;
+ // Subtract width of left border on active element
+ padding-left: 11px;
}
.badge {
@@ -313,6 +317,7 @@
.toggle-sidebar-button,
.close-nav-button {
width: $contextual-sidebar-width - 2px;
+ transition: width $sidebar-transition-duration;
position: fixed;
bottom: 0;
padding: 16px;
@@ -343,20 +348,21 @@
}
}
+.collapse-text {
+ white-space: nowrap;
+ overflow: hidden;
+}
-.sidebar-icons-only {
+.sidebar-collapsed-desktop {
.context-header {
- height: 61px;
+ height: 60px;
+ width: $contextual-sidebar-collapsed-width;
a {
padding: 10px 4px;
}
}
- li a {
- padding: 12px 15px;
- }
-
.sidebar-top-level-items > li {
&.active a {
padding-left: 12px;
@@ -374,8 +380,8 @@
}
.toggle-sidebar-button {
- width: $contextual-sidebar-collapsed-width - 2px;
padding: 16px;
+ width: $contextual-sidebar-collapsed-width - 2px;
.collapse-text,
.icon-angle-double-left {
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 5c9838c1029..ce551e6b7ce 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -48,3 +48,10 @@ body.modal-open {
display: block;
}
+.recaptcha-dialog .recaptcha-form {
+ display: inline-block;
+
+ .recaptcha {
+ margin: 0;
+ }
+}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 4f99c27eff1..a6fa96d834c 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -5,10 +5,9 @@ $grid-size: 8px;
$gutter_collapsed_width: 62px;
$gutter_width: 290px;
$gutter_inner_width: 250px;
-$sidebar-transition-duration: .15s;
+$sidebar-transition-duration: .3s;
$sidebar-breakpoint: 1024px;
$default-transition-duration: .15s;
-$right-sidebar-transition-duration: .3s;
$contextual-sidebar-width: 220px;
$contextual-sidebar-collapsed-width: 50px;
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 3683afa07de..862ea379cbc 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -57,7 +57,7 @@
position: relative;
@media (min-width: $screen-sm-min) {
- transition: width $right-sidebar-transition-duration;
+ transition: width $sidebar-transition-duration;
width: 100%;
&.is-compact {
@@ -453,8 +453,8 @@
.right-sidebar.right-sidebar-expanded {
&.boards-sidebar-slide-enter-active,
&.boards-sidebar-slide-leave-active {
- transition: width $right-sidebar-transition-duration,
- padding $right-sidebar-transition-duration;
+ transition: width $sidebar-transition-duration,
+ padding $sidebar-transition-duration;
}
&.boards-sidebar-slide-enter,
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index b0795353ec1..a5a6b7461a3 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -201,8 +201,9 @@
stroke-width: 1;
}
-.deploy-info-text {
- dominant-baseline: text-before-edge;
+.divider-line {
+ stroke-width: 1;
+ stroke: $gray-darkest;
}
.prometheus-state {
@@ -312,6 +313,20 @@
stroke: $gray-darker;
}
+ .deploy-info-text {
+ dominant-baseline: text-before-edge;
+ font-size: 12px;
+ }
+
+ .deploy-info-text-link {
+ font-family: $monospace_font;
+ fill: $gl-link-color;
+
+ &:hover {
+ fill: $gl-link-hover-color;
+ }
+ }
+
@media (max-width: $screen-sm-max) {
.label-axis-text,
.text-metric-usage,
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 11ee1232bfe..32f2fa88236 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -126,7 +126,7 @@
top: $header-height;
bottom: 0;
right: 0;
- transition: width $right-sidebar-transition-duration;
+ transition: width $sidebar-transition-duration;
background: $gray-light;
z-index: 200;
overflow: hidden;
diff --git a/app/controllers/admin/health_check_controller.rb b/app/controllers/admin/health_check_controller.rb
index 65a17828feb..61247b280b3 100644
--- a/app/controllers/admin/health_check_controller.rb
+++ b/app/controllers/admin/health_check_controller.rb
@@ -5,7 +5,7 @@ class Admin::HealthCheckController < Admin::ApplicationController
end
def reset_storage_health
- Gitlab::Git::Storage::CircuitBreaker.reset_all!
+ Gitlab::Git::Storage::FailureInfo.reset_all!
redirect_to admin_health_check_path,
notice: _('Git storage health information has been reset')
end
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index 744e448e8df..281756af57a 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -21,11 +21,11 @@ module IssuableActions
respond_to do |format|
format.html do
- recaptcha_check_with_fallback { render :edit }
+ recaptcha_check_if_spammable { render :edit }
end
format.json do
- render_entity_json
+ recaptcha_check_if_spammable(false) { render_entity_json }
end
end
@@ -80,6 +80,12 @@ module IssuableActions
private
+ def recaptcha_check_if_spammable(should_redirect = true, &block)
+ return yield unless @issuable.is_a? Spammable
+
+ recaptcha_check_with_fallback(should_redirect, &block)
+ end
+
def render_conflict_response
respond_to do |format|
format.html do
diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb
index ada0dde87fb..03d8e188093 100644
--- a/app/controllers/concerns/spammable_actions.rb
+++ b/app/controllers/concerns/spammable_actions.rb
@@ -23,8 +23,8 @@ module SpammableActions
@spam_config_loaded = Gitlab::Recaptcha.load_configurations!
end
- def recaptcha_check_with_fallback(&fallback)
- if spammable.valid?
+ def recaptcha_check_with_fallback(should_redirect = true, &fallback)
+ if should_redirect && spammable.valid?
redirect_to spammable_path
elsif render_recaptcha?
ensure_spam_config_loaded!
@@ -33,7 +33,18 @@ module SpammableActions
flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
end
- render :verify
+ respond_to do |format|
+ format.html do
+ render :verify
+ end
+
+ format.json do
+ locals = { spammable: spammable, script: false, has_submit: false }
+ recaptcha_html = render_to_string(partial: 'shared/recaptcha_form', formats: :html, locals: locals)
+
+ render json: { recaptcha_html: recaptcha_html }
+ end
+ end
else
yield
end
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index 8fc234a62b1..5919bf54468 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -22,7 +22,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
end
def update
- @group_member = @group.group_members.find(params[:id])
+ @group_member = @group.members_and_requesters.find(params[:id])
return render_403 unless can?(current_user, :update_group_member, @group_member)
diff --git a/app/controllers/health_controller.rb b/app/controllers/health_controller.rb
index 98c2aaa3526..a931b456a93 100644
--- a/app/controllers/health_controller.rb
+++ b/app/controllers/health_controller.rb
@@ -1,5 +1,5 @@
class HealthController < ActionController::Base
- protect_from_forgery with: :exception
+ protect_from_forgery with: :exception, except: :storage_check
include RequiresWhitelistedMonitoringClient
CHECKS = [
@@ -23,6 +23,15 @@ class HealthController < ActionController::Base
render_check_results(results)
end
+ def storage_check
+ results = Gitlab::Git::Storage::Checker.check_all
+
+ render json: {
+ check_interval: Gitlab::CurrentSettings.current_application_settings.circuitbreaker_check_interval,
+ results: results
+ }
+ end
+
private
def render_check_results(results)
diff --git a/app/controllers/profiles/personal_access_tokens_controller.rb b/app/controllers/profiles/personal_access_tokens_controller.rb
index 6d9873e38df..346eab4ba19 100644
--- a/app/controllers/profiles/personal_access_tokens_controller.rb
+++ b/app/controllers/profiles/personal_access_tokens_controller.rb
@@ -8,7 +8,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
@personal_access_token = finder.build(personal_access_token_params)
if @personal_access_token.save
- flash[:personal_access_token] = @personal_access_token.token
+ PersonalAccessToken.redis_store!(current_user.id, @personal_access_token.token)
redirect_to profile_personal_access_tokens_path, notice: "Your new personal access token has been created."
else
set_index_vars
@@ -43,5 +43,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
@inactive_personal_access_tokens = finder(state: 'inactive').execute
@active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at)
+
+ @new_personal_access_token = PersonalAccessToken.redis_getdel(current_user.id)
end
end
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 6ff96a3f295..2e7344b1cad 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -134,6 +134,23 @@ class Projects::CommitController < Projects::ApplicationController
@grouped_diff_discussions = commit.grouped_diff_discussions
@discussions = commit.discussions
+ if merge_request_iid = params[:merge_request_iid]
+ @merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).find_by(iid: merge_request_iid)
+
+ if @merge_request
+ @new_diff_note_attrs.merge!(
+ noteable_type: 'MergeRequest',
+ noteable_id: @merge_request.id
+ )
+
+ merge_request_commit_notes = @merge_request.notes.where(commit_id: @commit.id).inc_relations_for_view
+ merge_request_commit_diff_discussions = merge_request_commit_notes.grouped_diff_discussions(@commit.diff_refs)
+ @grouped_diff_discussions.merge!(merge_request_commit_diff_discussions) do |line_code, left, right|
+ left + right
+ end
+ end
+ end
+
@notes = (@grouped_diff_discussions.values.flatten + @discussions).flat_map(&:notes)
@notes = prepare_notes_for_rendering(@notes, @commit)
end
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index 1269759fc2b..793ae03fb88 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -28,7 +28,6 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
:task_num,
:title,
:discussion_locked,
-
label_ids: []
]
end
diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb
index 9f966889995..fe8525a488c 100644
--- a/app/controllers/projects/merge_requests/diffs_controller.rb
+++ b/app/controllers/projects/merge_requests/diffs_controller.rb
@@ -4,6 +4,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
include RendersNotes
before_action :apply_diff_view_cookie!
+ before_action :commit
before_action :define_diff_vars
before_action :define_diff_comment_vars
@@ -20,18 +21,33 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
private
def define_diff_vars
+ @merge_request_diffs = @merge_request.merge_request_diffs.viewable.order_id_desc
+ @compare = commit || find_merge_request_diff_compare
+ return render_404 unless @compare
+
+ @diffs = @compare.diffs(diff_options)
+ end
+
+ def commit
+ return nil unless commit_id = params[:commit_id].presence
+ return nil unless @merge_request.all_commits.exists?(sha: commit_id)
+
+ @commit ||= @project.commit(commit_id)
+ end
+
+ def find_merge_request_diff_compare
@merge_request_diff =
- if params[:diff_id]
- @merge_request.merge_request_diffs.viewable.find(params[:diff_id])
+ if diff_id = params[:diff_id].presence
+ @merge_request.merge_request_diffs.viewable.find_by(id: diff_id)
else
@merge_request.merge_request_diff
end
- @merge_request_diffs = @merge_request.merge_request_diffs.viewable.order_id_desc
+ return unless @merge_request_diff
+
@comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id }
- if params[:start_sha].present?
- @start_sha = params[:start_sha]
+ if @start_sha = params[:start_sha].presence
@start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha }
unless @start_version
@@ -40,20 +56,18 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
end
end
- @compare =
- if @start_sha
- @merge_request_diff.compare_with(@start_sha)
- else
- @merge_request_diff
- end
-
- @diffs = @compare.diffs(diff_options)
+ if @start_sha
+ @merge_request_diff.compare_with(@start_sha)
+ else
+ @merge_request_diff
+ end
end
def define_diff_comment_vars
@new_diff_note_attrs = {
noteable_type: 'MergeRequest',
- noteable_id: @merge_request.id
+ noteable_id: @merge_request.id,
+ commit_id: @commit&.id
}
@diff_notes_disabled = false
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 37acd1c9787..e7b3b73024b 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -7,11 +7,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
include IssuableCollections
skip_before_action :merge_request, only: [:index, :bulk_update]
-
before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
-
before_action :set_issuables_index, only: [:index]
-
before_action :authenticate_user!, only: [:assign_related_issues]
def index
diff --git a/app/controllers/projects/pipelines_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb
index b890818c475..06ce7328fb5 100644
--- a/app/controllers/projects/pipelines_settings_controller.rb
+++ b/app/controllers/projects/pipelines_settings_controller.rb
@@ -29,7 +29,6 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
:runners_token, :builds_enabled, :build_allow_git_fetch,
:build_timeout_in_minutes, :build_coverage_regex, :public_builds,
:auto_cancel_pending_pipelines, :ci_config_path,
- :run_auto_devops_pipeline_implicit, :run_auto_devops_pipeline_explicit,
auto_devops_attributes: [:id, :domain, :enabled]
)
end
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index d925dcd21ff..5a01a59481b 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -26,7 +26,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end
def update
- @project_member = @project.project_members.find(params[:id])
+ @project_member = @project.members_and_requesters.find(params[:id])
return render_403 unless can?(current_user, :update_project_member, @project_member)
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 3882fa4791d..8e9d6766d80 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -272,7 +272,7 @@ class ProjectsController < Projects::ApplicationController
render 'projects/empty' if @project.empty_repo?
else
- if @project.wiki_enabled?
+ if can?(current_user, :read_wiki, @project)
@project_wiki = @project.wiki
@wiki_home = @project_wiki.find_page('home', params[:version_id])
elsif @project.feature_available?(:issues, current_user)
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index dccde46fa33..b12ea760668 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -124,17 +124,6 @@ module ApplicationSettingsHelper
_('The number of attempts GitLab will make to access a storage.')
end
- def circuitbreaker_backoff_threshold_help_text
- _("The number of failures after which GitLab will start temporarily "\
- "disabling access to a storage shard on a host")
- end
-
- def circuitbreaker_failure_wait_time_help_text
- _("When access to a storage fails. GitLab will prevent access to the "\
- "storage for the time specified here. This allows the filesystem to "\
- "recover. Repositories on failing shards are temporarly unavailable")
- end
-
def circuitbreaker_failure_reset_time_help_text
_("The time in seconds GitLab will keep failure information. When no "\
"failures occur during this time, information about the mount is reset.")
@@ -145,6 +134,11 @@ module ApplicationSettingsHelper
"timeout error will be raised.")
end
+ def circuitbreaker_check_interval_help_text
+ _("The time in seconds between storage checks. When a previous check did "\
+ "complete yet, GitLab will skip a check.")
+ end
+
def visible_attributes
[
:admin_notification_email,
@@ -154,10 +148,9 @@ module ApplicationSettingsHelper
:akismet_enabled,
:auto_devops_enabled,
:circuitbreaker_access_retries,
- :circuitbreaker_backoff_threshold,
+ :circuitbreaker_check_interval,
:circuitbreaker_failure_count_threshold,
:circuitbreaker_failure_reset_time,
- :circuitbreaker_failure_wait_time,
:circuitbreaker_storage_timeout,
:clientside_sentry_dsn,
:clientside_sentry_enabled,
diff --git a/app/helpers/auto_devops_helper.rb b/app/helpers/auto_devops_helper.rb
index ec6194d204f..f4310ca2f06 100644
--- a/app/helpers/auto_devops_helper.rb
+++ b/app/helpers/auto_devops_helper.rb
@@ -8,22 +8,6 @@ module AutoDevopsHelper
!project.ci_service
end
- def show_run_auto_devops_pipeline_checkbox_for_instance_setting?(project)
- return false if project.repository.gitlab_ci_yml
-
- if project&.auto_devops&.enabled.present?
- !project.auto_devops.enabled && current_application_settings.auto_devops_enabled?
- else
- current_application_settings.auto_devops_enabled?
- end
- end
-
- def show_run_auto_devops_pipeline_checkbox_for_explicit_setting?(project)
- return false if project.repository.gitlab_ci_yml
-
- !project.auto_devops_enabled?
- end
-
def auto_devops_warning_message(project)
missing_domain = !project.auto_devops&.has_domain?
missing_service = !project.deployment_platform&.active?
diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb
index aa3a9a055a0..4ec63fdaffc 100644
--- a/app/helpers/builds_helper.rb
+++ b/app/helpers/builds_helper.rb
@@ -20,8 +20,7 @@ module BuildsHelper
def javascript_build_options
{
- page_url: project_job_url(@project, @build),
- build_url: project_job_url(@project, @build, :json),
+ page_path: project_job_path(@project, @build),
build_status: @build.status,
build_stage: @build.stage,
log_state: ''
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index f68e2cd3afa..2d304f7eb91 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -228,4 +228,12 @@ module CommitsHelper
[commits, 0]
end
end
+
+ def commit_path(project, commit, merge_request: nil)
+ if merge_request&.persisted?
+ diffs_project_merge_request_path(project, merge_request, commit_id: commit.id)
+ else
+ project_commit_path(project, commit)
+ end
+ end
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 5b2c58d193d..ce57422f45d 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -101,6 +101,30 @@ module MergeRequestsHelper
}.merge(merge_params_ee(merge_request))
end
+ def tab_link_for(merge_request, tab, options = {}, &block)
+ data_attrs = {
+ action: tab.to_s,
+ target: "##{tab}",
+ toggle: options.fetch(:force_link, false) ? '' : 'tab'
+ }
+
+ url = case tab
+ when :show
+ data_attrs[:target] = '#notes'
+ method(:project_merge_request_path)
+ when :commits
+ method(:commits_project_merge_request_path)
+ when :pipelines
+ method(:pipelines_project_merge_request_path)
+ when :diffs
+ method(:diffs_project_merge_request_path)
+ else
+ raise "Cannot create tab #{tab}."
+ end
+
+ link_to(url[merge_request.project, merge_request], data: data_attrs, &block)
+ end
+
def merge_params_ee(merge_request)
{}
end
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index 8e822ed0ea2..aaee6eaeedd 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -58,7 +58,7 @@ module PreferencesHelper
user_view
elsif user_view == "activity"
"activity"
- elsif @project.wiki_enabled?
+ elsif can?(current_user, :read_wiki, @project)
"wiki"
elsif @project.feature_available?(:issues, current_user)
"projects/issues/issues"
diff --git a/app/helpers/storage_health_helper.rb b/app/helpers/storage_health_helper.rb
index 4d2180f7eee..b76c1228220 100644
--- a/app/helpers/storage_health_helper.rb
+++ b/app/helpers/storage_health_helper.rb
@@ -18,16 +18,12 @@ module StorageHealthHelper
current_failures = circuit_breaker.failure_count
translation_params = { number_of_failures: current_failures,
- maximum_failures: maximum_failures,
- number_of_seconds: circuit_breaker.failure_wait_time }
+ maximum_failures: maximum_failures }
if circuit_breaker.circuit_broken?
s_("%{number_of_failures} of %{maximum_failures} failures. GitLab will not "\
"retry automatically. Reset storage information when the problem is "\
"resolved.") % translation_params
- elsif circuit_breaker.backing_off?
- _("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\
- "block access for %{number_of_seconds} seconds.") % translation_params
else
_("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\
"allow access on the next attempt.") % translation_params
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 3117c98c846..253e213af81 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -153,11 +153,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
numericality: { greater_than_or_equal_to: 0 }
- validates :circuitbreaker_backoff_threshold,
- :circuitbreaker_failure_count_threshold,
- :circuitbreaker_failure_wait_time,
+ validates :circuitbreaker_failure_count_threshold,
:circuitbreaker_failure_reset_time,
:circuitbreaker_storage_timeout,
+ :circuitbreaker_check_interval,
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
@@ -165,13 +164,6 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 1 }
- validates_each :circuitbreaker_backoff_threshold do |record, attr, value|
- if value.to_i >= record.circuitbreaker_failure_count_threshold
- record.errors.add(attr, _("The circuitbreaker backoff threshold should be "\
- "lower than the failure count threshold"))
- end
- end
-
validates :gitaly_timeout_default,
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index d2402b55184..85960f1b6bb 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -6,6 +6,8 @@ module Ci
include Presentable
include Importable
+ MissingDependenciesError = Class.new(StandardError)
+
belongs_to :runner
belongs_to :trigger_request
belongs_to :erased_by, class_name: 'User'
@@ -139,6 +141,10 @@ module Ci
Ci::Build.retry(build, build.user)
end
end
+
+ before_transition any => [:running] do |build|
+ build.validates_dependencies! unless Feature.enabled?('ci_disable_validates_dependencies')
+ end
end
def detailed_status(current_user)
@@ -478,6 +484,20 @@ module Ci
options[:dependencies]&.empty?
end
+ def validates_dependencies!
+ dependencies.each do |dependency|
+ raise MissingDependenciesError unless dependency.valid_dependency?
+ end
+ end
+
+ def valid_dependency?
+ return false unless complete?
+ return false if artifacts_expired?
+ return false if erased?
+
+ true
+ end
+
def hide_secrets(trace)
return unless trace
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 6b28d290f99..307e4fcedfe 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -1,3 +1,4 @@
+# coding: utf-8
class Commit
extend ActiveModel::Naming
extend Gitlab::Cache::RequestCache
@@ -25,7 +26,7 @@ class Commit
DIFF_HARD_LIMIT_FILES = 1000
DIFF_HARD_LIMIT_LINES = 50000
- MIN_SHA_LENGTH = 7
+ MIN_SHA_LENGTH = Gitlab::Git::Commit::MIN_SHA_LENGTH
COMMIT_SHA_PATTERN = /\h{#{MIN_SHA_LENGTH},40}/.freeze
def banzai_render_context(field)
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index ee21ed8e420..c0263c0b4e2 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -43,7 +43,8 @@ class CommitStatus < ActiveRecord::Base
script_failure: 1,
api_failure: 2,
stuck_or_timeout_failure: 3,
- runner_system_failure: 4
+ runner_system_failure: 4,
+ missing_dependency_failure: 5
}
##
diff --git a/app/models/concerns/discussion_on_diff.rb b/app/models/concerns/discussion_on_diff.rb
index f5cbb3becad..4b4d519f3df 100644
--- a/app/models/concerns/discussion_on_diff.rb
+++ b/app/models/concerns/discussion_on_diff.rb
@@ -32,6 +32,10 @@ module DiscussionOnDiff
first_note.position.new_path
end
+ def on_merge_request_commit?
+ for_merge_request? && commit_id.present?
+ end
+
# Returns an array of at most 16 highlighted lines above a diff note
def truncated_diff_lines(highlight: true)
lines = highlight ? highlighted_diff_lines : diff_lines
diff --git a/app/models/diff_discussion.rb b/app/models/diff_discussion.rb
index 6eba87da1a1..4a65738214b 100644
--- a/app/models/diff_discussion.rb
+++ b/app/models/diff_discussion.rb
@@ -24,7 +24,11 @@ class DiffDiscussion < Discussion
return unless for_merge_request?
return {} if active?
- noteable.version_params_for(position.diff_refs)
+ if on_merge_request_commit?
+ { commit_id: commit_id }
+ else
+ noteable.version_params_for(position.diff_refs)
+ end
end
def reply_attributes
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index ae5f138a920..b53d44cda95 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -17,6 +17,7 @@ class DiffNote < Note
validates :noteable_type, inclusion: { in: NOTEABLE_TYPES }
validate :positions_complete
validate :verify_supported
+ validate :diff_refs_match_commit, if: :for_commit?
before_validation :set_original_position, on: :create
before_validation :update_position, on: :create, if: :on_text?
@@ -135,6 +136,12 @@ class DiffNote < Note
errors.add(:position, "is invalid")
end
+ def diff_refs_match_commit
+ return if self.original_position.diff_refs == self.commit.diff_refs
+
+ errors.add(:commit_id, 'does not match the diff refs')
+ end
+
def keep_around_commits
project.repository.keep_around(self.original_position.base_sha)
project.repository.keep_around(self.original_position.start_sha)
diff --git a/app/models/discussion.rb b/app/models/discussion.rb
index 437df923d2d..92482a1a875 100644
--- a/app/models/discussion.rb
+++ b/app/models/discussion.rb
@@ -11,6 +11,7 @@ class Discussion
:author,
:noteable,
+ :commit_id,
:for_commit?,
:for_merge_request?,
diff --git a/app/models/event.rb b/app/models/event.rb
index 0997b056c6a..6053594fab5 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -72,7 +72,7 @@ class Event < ActiveRecord::Base
# We're using preload for "push_event_payload" as otherwise the association
# is not always available (depending on the query being built).
includes(:author, :project, project: :namespace)
- .preload(:target, :push_event_payload)
+ .preload(:push_event_payload, target: :author)
end
scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 33db197e612..bbda848c39d 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -10,6 +10,9 @@ class Issue < ActiveRecord::Base
include RelativePositioning
include TimeTrackable
include ThrottledTouch
+ include IgnorableColumn
+
+ ignore_column :assignee_id
DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 949d42f865c..422f138c4ea 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -649,6 +649,7 @@ class MergeRequest < ActiveRecord::Base
.to_sql
Note.from("(#{union}) #{Note.table_name}")
+ .includes(:noteable)
end
alias_method :discussion_notes, :related_notes
@@ -925,21 +926,27 @@ class MergeRequest < ActiveRecord::Base
.order(id: :desc)
end
- # Note that this could also return SHA from now dangling commits
- #
- def all_commit_shas
- return commit_shas unless persisted?
-
- diffs_relation = merge_request_diffs
-
+ def all_commits
# MySQL doesn't support LIMIT in a subquery.
- diffs_relation = diffs_relation.recent if Gitlab::Database.postgresql?
+ diffs_relation = if Gitlab::Database.postgresql?
+ merge_request_diffs.recent
+ else
+ merge_request_diffs
+ end
MergeRequestDiffCommit
.where(merge_request_diff: diffs_relation)
.limit(10_000)
- .pluck('sha')
- .uniq
+ end
+
+ # Note that this could also return SHA from now dangling commits
+ #
+ def all_commit_shas
+ @all_commit_shas ||= begin
+ return commit_shas unless persisted?
+
+ all_commits.pluck(:sha).uniq
+ end
end
def merge_commit
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 901dbf2ba69..0ff169d4531 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -40,6 +40,7 @@ class Namespace < ActiveRecord::Base
namespace_path: true
validate :nesting_level_allowed
+ validate :allowed_path_by_redirects
delegate :name, to: :owner, allow_nil: true, prefix: true
@@ -257,4 +258,14 @@ class Namespace < ActiveRecord::Base
Namespace.where(id: descendants.select(:id))
.update_all(share_with_group_lock: true)
end
+
+ def allowed_path_by_redirects
+ return if path.nil?
+
+ errors.add(:path, "#{path} has been taken before. Please use another one") if namespace_previously_created_with_same_path?
+ end
+
+ def namespace_previously_created_with_same_path?
+ RedirectRoute.permanent.exists?(path: path)
+ end
end
diff --git a/app/models/note.rb b/app/models/note.rb
index 733bbbc013f..c4c2ab8e67d 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -230,16 +230,18 @@ class Note < ActiveRecord::Base
for_personal_snippet?
end
+ def commit
+ @commit ||= project.commit(commit_id) if commit_id.present?
+ end
+
# override to return commits, which are not active record
def noteable
- if for_commit?
- @commit ||= project.commit(commit_id)
- else
- super
- end
- # Temp fix to prevent app crash
- # if note commit id doesn't exist
+ return commit if for_commit?
+
+ super
rescue
+ # Temp fix to prevent app crash
+ # if note commit id doesn't exist
nil
end
@@ -401,6 +403,10 @@ class Note < ActiveRecord::Base
noteable_object&.touch
end
+ def banzai_render_context(field)
+ super.merge(noteable: noteable)
+ end
+
private
def keep_around_commit
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index cfcb03138b7..063dc521324 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -3,6 +3,8 @@ class PersonalAccessToken < ActiveRecord::Base
include TokenAuthenticatable
add_authentication_token_field :token
+ REDIS_EXPIRY_TIME = 3.minutes
+
serialize :scopes, Array # rubocop:disable Cop/ActiveRecordSerialize
belongs_to :user
@@ -27,6 +29,21 @@ class PersonalAccessToken < ActiveRecord::Base
!revoked? && !expired?
end
+ def self.redis_getdel(user_id)
+ Gitlab::Redis::SharedState.with do |redis|
+ token = redis.get(redis_shared_state_key(user_id))
+ redis.del(redis_shared_state_key(user_id))
+ token
+ end
+ end
+
+ def self.redis_store!(user_id, token)
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set(redis_shared_state_key(user_id), token, ex: REDIS_EXPIRY_TIME)
+ token
+ end
+ end
+
protected
def validate_scopes
@@ -38,4 +55,8 @@ class PersonalAccessToken < ActiveRecord::Base
def set_default_scopes
self.scopes = Gitlab::Auth::DEFAULT_SCOPES if self.scopes.empty?
end
+
+ def self.redis_shared_state_key(user_id)
+ "gitlab:personal_access_token:#{user_id}"
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 41657c171e2..6ae15a0a50f 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -227,7 +227,6 @@ class Project < ActiveRecord::Base
delegate :members, to: :team, prefix: true
delegate :add_user, :add_users, to: :team
delegate :add_guest, :add_reporter, :add_developer, :add_master, to: :team
- delegate :empty_repo?, to: :repository
# Validations
validates :creator, presence: true, on: :create
@@ -499,6 +498,10 @@ class Project < ActiveRecord::Base
auto_devops&.enabled.nil? && !current_application_settings.auto_devops_enabled?
end
+ def empty_repo?
+ repository.empty?
+ end
+
def repository_storage_path
Gitlab.config.repositories.storages[repository_storage].try(:[], 'path')
end
diff --git a/app/models/redirect_route.rb b/app/models/redirect_route.rb
index 31de204d824..20532527346 100644
--- a/app/models/redirect_route.rb
+++ b/app/models/redirect_route.rb
@@ -17,4 +17,32 @@ class RedirectRoute < ActiveRecord::Base
where(wheres, path, "#{sanitize_sql_like(path)}/%")
end
+
+ scope :permanent, -> do
+ if column_permanent_exists?
+ where(permanent: true)
+ else
+ none
+ end
+ end
+
+ scope :temporary, -> do
+ if column_permanent_exists?
+ where(permanent: [false, nil])
+ else
+ all
+ end
+ end
+
+ default_value_for :permanent, false
+
+ def permanent=(value)
+ if self.class.column_permanent_exists?
+ super
+ end
+ end
+
+ def self.column_permanent_exists?
+ ActiveRecord::Base.connection.column_exists?(:redirect_routes, :permanent)
+ end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 82af299ec5e..751306188a0 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -37,7 +37,7 @@ class Repository
issue_template_names merge_request_template_names).freeze
# Methods that use cache_method but only memoize the value
- MEMOIZED_CACHED_METHODS = %i(license empty_repo?).freeze
+ MEMOIZED_CACHED_METHODS = %i(license).freeze
# Certain method caches should be refreshed when certain types of files are
# changed. This Hash maps file types (as returned by Gitlab::FileDetector) to
@@ -497,7 +497,11 @@ class Repository
end
cache_method :exists?
- delegate :empty?, to: :raw_repository
+ def empty?
+ return true unless exists?
+
+ !has_visible_content?
+ end
cache_method :empty?
# The size of this repository in megabytes.
@@ -944,13 +948,8 @@ class Repository
end
end
- def empty_repo?
- !exists? || !has_visible_content?
- end
- cache_method :empty_repo?, memoize_only: true
-
def search_files_by_content(query, ref)
- return [] if empty_repo? || query.blank?
+ return [] if empty? || query.blank?
offset = 2
args = %W(grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
@@ -959,7 +958,7 @@ class Repository
end
def search_files_by_name(query, ref)
- return [] if empty_repo? || query.blank?
+ return [] if empty? || query.blank?
args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{Regexp.escape(query)})
diff --git a/app/models/route.rb b/app/models/route.rb
index 97e8a6ad9e9..7ba3ec06041 100644
--- a/app/models/route.rb
+++ b/app/models/route.rb
@@ -8,6 +8,8 @@ class Route < ActiveRecord::Base
presence: true,
uniqueness: { case_sensitive: false }
+ validate :ensure_permanent_paths
+
after_create :delete_conflicting_redirects
after_update :delete_conflicting_redirects, if: :path_changed?
after_update :create_redirect_for_old_path
@@ -40,7 +42,7 @@ class Route < ActiveRecord::Base
# We are not calling route.delete_conflicting_redirects here, in hopes
# of avoiding deadlocks. The parent (self, in this method) already
# called it, which deletes conflicts for all descendants.
- route.create_redirect(old_path) if attributes[:path]
+ route.create_redirect(old_path, permanent: permanent_redirect?) if attributes[:path]
end
end
end
@@ -50,16 +52,30 @@ class Route < ActiveRecord::Base
end
def conflicting_redirects
- RedirectRoute.matching_path_and_descendants(path)
+ RedirectRoute.temporary.matching_path_and_descendants(path)
end
- def create_redirect(path)
- RedirectRoute.create(source: source, path: path)
+ def create_redirect(path, permanent: false)
+ RedirectRoute.create(source: source, path: path, permanent: permanent)
end
private
def create_redirect_for_old_path
- create_redirect(path_was) if path_changed?
+ create_redirect(path_was, permanent: permanent_redirect?) if path_changed?
+ end
+
+ def permanent_redirect?
+ source_type != "Project"
+ end
+
+ def ensure_permanent_paths
+ return if path.nil?
+
+ errors.add(:path, "#{path} has been taken before. Please use another one") if conflicting_redirect_exists?
+ end
+
+ def conflicting_redirect_exists?
+ RedirectRoute.permanent.matching_path_and_descendants(path).exists?
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index af1c36d9c93..92b461ce3ed 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -315,6 +315,8 @@ class User < ActiveRecord::Base
#
# Returns an ActiveRecord::Relation.
def search(query)
+ query = query.downcase
+
order = <<~SQL
CASE
WHEN users.name = %{query} THEN 0
@@ -324,8 +326,11 @@ class User < ActiveRecord::Base
END
SQL
- fuzzy_search(query, [:name, :email, :username])
- .reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name)
+ where(
+ fuzzy_arel_match(:name, query)
+ .or(fuzzy_arel_match(:username, query))
+ .or(arel_table[:email].eq(query))
+ ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name)
end
# searches user by given pattern
@@ -333,15 +338,17 @@ class User < ActiveRecord::Base
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
def search_with_secondary_emails(query)
+ query = query.downcase
+
email_table = Email.arel_table
matched_by_emails_user_ids = email_table
.project(email_table[:user_id])
- .where(Email.fuzzy_arel_match(:email, query))
+ .where(email_table[:email].eq(query))
where(
fuzzy_arel_match(:name, query)
- .or(fuzzy_arel_match(:email, query))
.or(fuzzy_arel_match(:username, query))
+ .or(arel_table[:email].eq(query))
.or(arel_table[:id].in(matched_by_emails_user_ids))
)
end
@@ -1054,13 +1061,13 @@ class User < ActiveRecord::Base
end
def todos_done_count(force: false)
- Rails.cache.fetch(['users', id, 'todos_done_count'], force: force) do
+ Rails.cache.fetch(['users', id, 'todos_done_count'], force: force, expires_in: 20.minutes) do
TodosFinder.new(self, state: :done).execute.count
end
end
def todos_pending_count(force: false)
- Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force) do
+ Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force, expires_in: 20.minutes) do
TodosFinder.new(self, state: :pending).execute.count
end
end
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 1e5f2ed4dd2..85db2760e23 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -12,18 +12,19 @@ module Ci
def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, &block)
@pipeline = Ci::Pipeline.new
- command = OpenStruct.new(source: source,
- origin_ref: params[:ref],
- checkout_sha: params[:checkout_sha],
- after_sha: params[:after],
- before_sha: params[:before],
- trigger_request: trigger_request,
- schedule: schedule,
- ignore_skip_ci: ignore_skip_ci,
- save_incompleted: save_on_errors,
- seeds_block: block,
- project: project,
- current_user: current_user)
+ command = Gitlab::Ci::Pipeline::Chain::Command.new(
+ source: source,
+ origin_ref: params[:ref],
+ checkout_sha: params[:checkout_sha],
+ after_sha: params[:after],
+ before_sha: params[:before],
+ trigger_request: trigger_request,
+ schedule: schedule,
+ ignore_skip_ci: ignore_skip_ci,
+ save_incompleted: save_on_errors,
+ seeds_block: block,
+ project: project,
+ current_user: current_user)
sequence = Gitlab::Ci::Pipeline::Chain::Sequence
.new(pipeline, command, SEQUENCE)
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index 2ef76e03031..c8b6450c9b5 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -54,6 +54,9 @@ module Ci
# we still have to return 409 in the end,
# to make sure that this is properly handled by runner.
valid = false
+ rescue Ci::Build::MissingDependenciesError
+ build.drop!(:missing_dependency_failure)
+ valid = false
end
end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 434dda89db0..9f05535d4d4 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -6,7 +6,7 @@ module MergeRequests
@oldrev, @newrev = oldrev, newrev
@branch_name = Gitlab::Git.ref_name(ref)
- find_new_commits
+ Gitlab::GitalyClient.allow_n_plus_1_calls(&method(:find_new_commits))
# Be sure to close outstanding MRs before reloading them to avoid generating an
# empty diff during a manual merge
close_merge_requests
diff --git a/app/services/metrics_service.rb b/app/services/metrics_service.rb
index 6b3939aeba5..236e9fe8c44 100644
--- a/app/services/metrics_service.rb
+++ b/app/services/metrics_service.rb
@@ -20,7 +20,7 @@ class MetricsService
end
def metrics_text
- "#{health_metrics_text}#{prometheus_metrics_text}"
+ prometheus_metrics_text.concat(health_metrics_text)
end
private
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 72eecc61c96..ff4c73c886e 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -15,7 +15,7 @@ module Projects
return error("Could not set the default branch") unless project.change_head(params[:default_branch])
end
- if project.update_attributes(update_params)
+ if project.update_attributes(params.except(:default_branch))
if project.previous_changes.include?('path')
project.rename_repo
else
@@ -32,15 +32,13 @@ module Projects
end
def run_auto_devops_pipeline?
- params.dig(:run_auto_devops_pipeline_explicit) == 'true' || params.dig(:run_auto_devops_pipeline_implicit) == 'true'
+ return false if project.repository.gitlab_ci_yml || !project.auto_devops.previous_changes.include?('enabled')
+
+ project.auto_devops.enabled? || (project.auto_devops.enabled.nil? && current_application_settings.auto_devops_enabled?)
end
private
- def update_params
- params.except(:default_branch, :run_auto_devops_pipeline_explicit, :run_auto_devops_pipeline_implicit)
- end
-
def renaming_project_with_container_registry_tags?
new_path = params[:path]
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index a9d0503bc73..3e2dbb07a6c 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -546,6 +546,12 @@
%fieldset
%legend Git Storage Circuitbreaker settings
.form-group
+ = f.label :circuitbreaker_check_interval, _('Check interval'), class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :circuitbreaker_check_interval, class: 'form-control'
+ .help-block
+ = circuitbreaker_check_interval_help_text
+ .form-group
= f.label :circuitbreaker_access_retries, _('Number of access attempts'), class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :circuitbreaker_access_retries, class: 'form-control'
@@ -558,18 +564,6 @@
.help-block
= circuitbreaker_storage_timeout_help_text
.form-group
- = f.label :circuitbreaker_backoff_threshold, _('Number of failures before backing off'), class: 'control-label col-sm-2'
- .col-sm-10
- = f.number_field :circuitbreaker_backoff_threshold, class: 'form-control'
- .help-block
- = circuitbreaker_backoff_threshold_help_text
- .form-group
- = f.label :circuitbreaker_failure_wait_time, _('Seconds to wait after a storage failure'), class: 'control-label col-sm-2'
- .col-sm-10
- = f.number_field :circuitbreaker_failure_wait_time, class: 'form-control'
- .help-block
- = circuitbreaker_failure_wait_time_help_text
- .form-group
= f.label :circuitbreaker_failure_count_threshold, _('Maximum git storage failures'), class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :circuitbreaker_failure_count_threshold, class: 'form-control'
diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml
index 0f03163a2e8..205320ed87c 100644
--- a/app/views/discussions/_discussion.html.haml
+++ b/app/views/discussions/_discussion.html.haml
@@ -32,9 +32,17 @@
- elsif discussion.diff_discussion?
on
= conditional_link_to url.present?, url do
- - unless discussion.active?
- an old version of
- the diff
+ - if discussion.on_merge_request_commit?
+ - unless discussion.active?
+ an outdated change in
+ commit
+
+ %span.commit-sha= Commit.truncate_sha(discussion.commit_id)
+ - else
+ - unless discussion.active?
+ an old version of
+ the diff
+
= time_ago_with_tooltip(discussion.created_at, placement: "bottom", html_class: "note-created-ago")
= render "discussions/headline", discussion: discussion
diff --git a/app/views/layouts/_recaptcha_verification.html.haml b/app/views/layouts/_recaptcha_verification.html.haml
index 77c77dc6754..e6f87ddd383 100644
--- a/app/views/layouts/_recaptcha_verification.html.haml
+++ b/app/views/layouts/_recaptcha_verification.html.haml
@@ -1,5 +1,4 @@
- humanized_resource_name = spammable.class.model_name.human.downcase
-- resource_name = spammable.class.model_name.singular
%h3.page-title
Anti-spam verification
@@ -8,16 +7,4 @@
%p
#{"We detected potential spam in the #{humanized_resource_name}. Please solve the reCAPTCHA to proceed."}
-= form_for form do |f|
- .recaptcha
- - params[resource_name].each do |field, value|
- = hidden_field(resource_name, field, value: value)
- = hidden_field_tag(:spam_log_id, spammable.spam_log.id)
- = hidden_field_tag(:recaptcha_verification, true)
- = recaptcha_tags
-
- -# Yields a block with given extra params.
- = yield
-
- .row-content-block.footer-block
- = f.submit "Submit #{humanized_resource_name}", class: 'btn btn-create'
+= render 'shared/recaptcha_form', spammable: spammable
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 30ae385f62f..52587760ba4 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -13,7 +13,14 @@
.location-badge= label
.search-input-wrap
.dropdown{ data: { url: search_autocomplete_path } }
- = search_field_tag 'search', nil, placeholder: 'Search', class: 'search-input dropdown-menu-toggle no-outline js-search-dashboard-options', spellcheck: false, tabindex: '1', autocomplete: 'off', data: { issues_path: issues_dashboard_url, mr_path: merge_requests_dashboard_url }, aria: { label: 'Search' }
+ = search_field_tag 'search', nil, placeholder: 'Search',
+ class: 'search-input dropdown-menu-toggle no-outline js-search-dashboard-options',
+ spellcheck: false,
+ tabindex: '1',
+ autocomplete: 'off',
+ data: { issues_path: issues_dashboard_path,
+ mr_path: merge_requests_dashboard_path },
+ aria: { label: 'Search' }
%button.hidden.js-dropdown-search-toggle{ type: 'button', data: { toggle: 'dropdown' } }
.dropdown-menu.dropdown-select
= dropdown_content do
diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index 0ec07605631..cb8db306b56 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -1,4 +1,4 @@
-.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
+.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
.nav-sidebar-inner-scroll
.context-header
= link_to admin_root_path, title: 'Admin Overview' do
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 0bf318b0b66..0c27b09f7b1 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -1,7 +1,7 @@
- issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
-.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
+.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
.nav-sidebar-inner-scroll
.context-header
= link_to group_path(@group), title: @group.name do
diff --git a/app/views/layouts/nav/sidebar/_profile.html.haml b/app/views/layouts/nav/sidebar/_profile.html.haml
index 7e23f9c1f05..a5a62a0695f 100644
--- a/app/views/layouts/nav/sidebar/_profile.html.haml
+++ b/app/views/layouts/nav/sidebar/_profile.html.haml
@@ -1,4 +1,4 @@
-.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
+.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
.nav-sidebar-inner-scroll
.context-header
= link_to profile_path, title: 'Profile Settings' do
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 53a9162b703..be39f577ba7 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -1,4 +1,4 @@
-.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
+.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
.nav-sidebar-inner-scroll
- can_edit = can?(current_user, :admin_project, @project)
.context-header
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index 26c2e4c5936..f445e5a2417 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -15,14 +15,13 @@
They are the only accepted password when you have Two-Factor Authentication (2FA) enabled.
.col-lg-8
-
- - if flash[:personal_access_token]
+ - if @new_personal_access_token
.created-personal-access-token-container
%h5.prepend-top-0
Your New Personal Access Token
.form-group
- = text_field_tag 'created-personal-access-token', flash[:personal_access_token], readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-personal-access-token-help-block"
- = clipboard_button(text: flash[:personal_access_token], title: "Copy personal access token to clipboard", placement: "left")
+ = text_field_tag 'created-personal-access-token', @new_personal_access_token, readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-personal-access-token-help-block"
+ = clipboard_button(text: @new_personal_access_token, title: "Copy personal access token to clipboard", placement: "left")
%span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again.
%hr
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 2cd5d0c60ea..c5e3a7945bd 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -2,7 +2,7 @@
- if defined?(@merge_request) && @merge_request.discussion_locked?
.issuable-note-warning
- = icon('lock', class: 'icon')
+ = sprite_icon('lock', size: 16, css_class: 'icon')
%span
= _('This merge request is locked.')
= _('Only project members can comment.')
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index c1842527480..86510b8ab93 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -14,7 +14,7 @@
%td.branch-commit
- if can?(current_user, :read_build, job)
- = link_to project_job_url(job.project, job) do
+ = link_to project_job_path(job.project, job) do
%span.build-link ##{job.id}
- else
%span.build-link ##{job.id}
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 5f607c2ab25..09934c09865 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -47,7 +47,7 @@
%li= link_to s_("DownloadCommit|Email Patches"), project_commit_path(@project, @commit, format: :patch)
%li= link_to s_("DownloadCommit|Plain Diff"), project_commit_path(@project, @commit, format: :diff)
-.commit-box
+.commit-box{ data: { project_path: project_path(@project) } }
%h3.commit-title
= markdown(@commit.title, pipeline: :single_line, author: @commit.author)
- if @commit.description.present?
@@ -80,3 +80,13 @@
- if last_pipeline.duration
in
= time_interval_in_words last_pipeline.duration
+
+ - if @merge_request
+ .well-segment
+ = icon('info-circle fw')
+
+ This commit is part of merge request
+ = succeed '.' do
+ = link_to @merge_request.to_reference, diffs_project_merge_request_path(@project, @merge_request, commit_id: @commit.id)
+
+ Comments created here will be created in the context of that merge request.
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index abb292f8f27..2890e9d2b65 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -6,6 +6,9 @@
- @content_class = limited_container_width
- page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
- page_description @commit.description
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('common_vue')
+ = page_specific_javascript_bundle_tag('diff_notes')
.container-fluid{ class: [limited_container_width, container_class] }
= render "commit_box"
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 1b91a94a9f8..618a6355d23 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -1,7 +1,18 @@
-- ref = local_assigns.fetch(:ref)
-
-- cache_key = [project.full_path, commit.id, current_application_settings, @path.presence, current_controller?(:commits), I18n.locale]
-- cache_key.push(commit.status(ref)) if commit.status(ref)
+- view_details = local_assigns.fetch(:view_details, false)
+- merge_request = local_assigns.fetch(:merge_request, nil)
+- project = local_assigns.fetch(:project) { merge_request&.project }
+- ref = local_assigns.fetch(:ref) { merge_request&.source_branch }
+
+- link = commit_path(project, commit, merge_request: merge_request)
+- cache_key = [project.full_path,
+ commit.id,
+ current_application_settings,
+ @path.presence,
+ current_controller?(:commits),
+ merge_request&.iid,
+ view_details,
+ commit.status(ref),
+ I18n.locale].compact
= cache(cache_key, expires_in: 1.day) do
%li.commit.flex-row.js-toggle-container{ id: "commit-#{commit.short_id}" }
@@ -11,7 +22,7 @@
.commit-detail
.commit-content
- = link_to_markdown_field(commit, :title, project_commit_path(project, commit.id), class: "commit-row-message item-title")
+ = link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title")
%span.commit-row-message.visible-xs-inline
&middot;
= commit.short_id
@@ -31,8 +42,7 @@
- commit_text = _('%{commit_author_link} committed %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago }
#{ commit_text.html_safe }
-
- .commit-actions.hidden-xs
+ .commit-actions.flex-row.hidden-xs
- if request.xhr?
= render partial: 'projects/commit/signature', object: commit.signature
- else
@@ -41,6 +51,9 @@
- if commit.status(ref)
= render_commit_status(commit, ref: ref)
- = link_to commit.short_id, project_commit_path(project, commit), class: "commit-sha btn btn-transparent btn-link"
+ = link_to commit.short_id, link, class: "commit-sha btn btn-transparent btn-link"
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"))
= link_to_browse_code(project, commit)
+
+ - if view_details && merge_request
+ = link_to "View details", project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "btn btn-default"
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index d14897428d0..ac6852751be 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -1,4 +1,7 @@
-- ref = local_assigns.fetch(:ref)
+- merge_request = local_assigns.fetch(:merge_request, nil)
+- project = local_assigns.fetch(:project) { merge_request&.project }
+- ref = local_assigns.fetch(:ref) { merge_request&.source_branch }
+
- commits, hidden = limited_commits(@commits)
- commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits|
@@ -8,7 +11,7 @@
%li.commits-row{ data: { day: day } }
%ul.content-list.commit-list.flex-list
- = render partial: 'projects/commits/commit', collection: commits, locals: { project: project, ref: ref }
+ = render partial: 'projects/commits/commit', collection: commits, locals: { project: project, ref: ref, merge_request: merge_request }
- if hidden > 0
%li.alert.alert-warning
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index ef305120525..ab371521840 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -3,7 +3,7 @@
- page_title _("Commits"), @ref
= content_for :meta_tags do
- = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
+ = auto_discovery_link_tag(:atom, project_commits_path(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
.js-project-commits-show{ 'data-commits-limit' => @limit }
%div{ class: container_class }
diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml
index e0aedcac5e1..56cf16c3778 100644
--- a/app/views/projects/environments/metrics.html.haml
+++ b/app/views/projects/environments/metrics.html.haml
@@ -19,4 +19,6 @@
"empty-loading-svg-path": image_path('illustrations/monitoring/loading'),
"empty-unable-to-connect-svg-path": image_path('illustrations/monitoring/unable_to_connect'),
"additional-metrics": additional_metrics_project_environment_path(@project, @environment, format: :json),
+ "project-path": project_path(@project),
+ "tags-path": project_tags_path(@project),
"has-metrics": "#{@environment.has_metrics?}", deployment_endpoint: project_environment_deployments_path(@project, @environment, format: :json) } }
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index 1eccc0509bd..9779c1985d5 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -14,4 +14,4 @@
notes_path: notes_url,
last_fetched_at: Time.now.to_i,
noteable_data: serialize_issuable(@issue),
- current_user_data: UserSerializer.new.represent(current_user).to_json } }
+ current_user_data: UserSerializer.new.represent(current_user, only_path: true).to_json } }
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 431d5a58daf..2f7aece7440 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -74,10 +74,10 @@
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago')
- #merge-requests{ data: { url: referenced_merge_requests_project_issue_url(@project, @issue) } }
+ #merge-requests{ data: { url: referenced_merge_requests_project_issue_path(@project, @issue) } }
// This element is filled in using JavaScript.
- #related-branches{ data: { url: related_branches_project_issue_url(@project, @issue) } }
+ #related-branches{ data: { url: related_branches_project_issue_path(@project, @issue) } }
// This element is filled in using JavaScript.
.content-block.emoji-block
diff --git a/app/views/projects/merge_requests/_commits.html.haml b/app/views/projects/merge_requests/_commits.html.haml
index 11793919ff7..b414518b597 100644
--- a/app/views/projects/merge_requests/_commits.html.haml
+++ b/app/views/projects/merge_requests/_commits.html.haml
@@ -5,4 +5,4 @@
= custom_icon ('illustration_no_commits')
- else
%ol#commits-list.list-unstyled
- = render "projects/commits/commits", project: @merge_request.source_project, ref: @merge_request.source_branch
+ = render "projects/commits/commits", merge_request: @merge_request
diff --git a/app/views/projects/merge_requests/diffs/_commit_widget.html.haml b/app/views/projects/merge_requests/diffs/_commit_widget.html.haml
new file mode 100644
index 00000000000..2e5594f8cbe
--- /dev/null
+++ b/app/views/projects/merge_requests/diffs/_commit_widget.html.haml
@@ -0,0 +1,5 @@
+- if @commit
+ .info-well.hidden-xs.prepend-top-default
+ .well-segment
+ %ul.blob-commit-info
+ = render 'projects/commits/commit', commit: @commit, merge_request: @merge_request, view_details: true
diff --git a/app/views/projects/merge_requests/diffs/_different_base.html.haml b/app/views/projects/merge_requests/diffs/_different_base.html.haml
new file mode 100644
index 00000000000..0e57066f9c9
--- /dev/null
+++ b/app/views/projects/merge_requests/diffs/_different_base.html.haml
@@ -0,0 +1,11 @@
+- if @merge_request_diff && different_base?(@start_version, @merge_request_diff)
+ .mr-version-controls
+ .content-block
+ = icon('info-circle')
+ Selected versions have different base commits.
+ Changes will include
+ = link_to project_compare_path(@project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do
+ new commits
+ from
+ = succeed '.' do
+ %code.ref-name= @merge_request.target_branch
diff --git a/app/views/projects/merge_requests/diffs/_diffs.html.haml b/app/views/projects/merge_requests/diffs/_diffs.html.haml
index 3d7a8f9d870..60c91024b23 100644
--- a/app/views/projects/merge_requests/diffs/_diffs.html.haml
+++ b/app/views/projects/merge_requests/diffs/_diffs.html.haml
@@ -1,13 +1,18 @@
-- if @merge_request_diff.collected? || @merge_request_diff.overflow?
- = render 'projects/merge_requests/diffs/versions'
- = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, merge_request: true
-- elsif @merge_request_diff.empty?
+= render 'projects/merge_requests/diffs/version_controls'
+= render 'projects/merge_requests/diffs/different_base'
+= render 'projects/merge_requests/diffs/not_all_comments_displayed'
+= render 'projects/merge_requests/diffs/commit_widget'
+
+- if @merge_request_diff&.empty?
.nothing-here-block
= image_tag 'illustrations/merge_request_changes_empty.svg'
- %p
- Nothing to merge from
- %strong= @merge_request.source_branch
- into
- %strong= @merge_request.target_branch
-
+ = succeed '.' do
+ No changes between
+ %span.ref-name= @merge_request.source_branch
+ and
+ %span.ref-name= @merge_request.target_branch
%p= link_to 'Create commit', project_new_blob_path(@project, @merge_request.source_branch), class: 'btn btn-save'
+- else
+ - diff_viewable = @merge_request_diff ? @merge_request_diff.collected? || @merge_request_diff.overflow? : true
+ - if diff_viewable
+ = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, merge_request: true
diff --git a/app/views/projects/merge_requests/diffs/_not_all_comments_displayed.html.haml b/app/views/projects/merge_requests/diffs/_not_all_comments_displayed.html.haml
new file mode 100644
index 00000000000..529fbb8547a
--- /dev/null
+++ b/app/views/projects/merge_requests/diffs/_not_all_comments_displayed.html.haml
@@ -0,0 +1,17 @@
+- if @commit || @start_version || (@merge_request_diff && !@merge_request_diff.latest?)
+ .mr-version-controls
+ .content-block.comments-disabled-notif.clearfix
+ = icon('info-circle')
+ = succeed '.' do
+ - if @commit
+ Only comments from the following commit are shown below
+ - else
+ Not all comments are displayed because you're
+ - if @start_version
+ comparing two versions of the diff
+ - else
+ viewing an old version of the diff
+ .pull-right
+ = link_to diffs_project_merge_request_path(@merge_request.project, @merge_request), class: 'btn btn-sm' do
+ Show latest version
+ = "of the diff" if @commit
diff --git a/app/views/projects/merge_requests/diffs/_versions.html.haml b/app/views/projects/merge_requests/diffs/_version_controls.html.haml
index 9f7152b9824..1c26f0405d2 100644
--- a/app/views/projects/merge_requests/diffs/_versions.html.haml
+++ b/app/views/projects/merge_requests/diffs/_version_controls.html.haml
@@ -1,4 +1,4 @@
-- if @merge_request_diffs.size > 1
+- if @merge_request_diff && @merge_request_diffs.size > 1
.mr-version-controls
.mr-version-menus-container.content-block
Changes between
@@ -71,27 +71,3 @@
(base)
%div
%strong.commit-sha= 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 project_compare_path(@project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do
- new commits
- from
- = succeed '.' do
- %code= @merge_request.target_branch
-
- - if @start_version || !@merge_request_diff.latest?
- .comments-disabled-notif.content-block
- = icon('info-circle')
- Not all comments are displayed because you're
- - if @start_version
- comparing two versions
- - else
- viewing an old version
- of the diff.
-
- .pull-right
- = link_to 'Show latest version', diffs_project_merge_request_path(@project, @merge_request), class: 'btn btn-sm'
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index d88e3d794d3..abff702fd9d 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -8,7 +8,7 @@
= webpack_bundle_tag('common_vue')
= webpack_bundle_tag('diff_notes')
-.merge-request{ 'data-mr-action': "#{j params[:tab].presence || 'show'}", 'data-url' => merge_request_path(@merge_request, format: :json), 'data-project-path' => project_path(@merge_request.project) }
+.merge-request{ data: { mr_action: j(params[:tab].presence || 'show'), url: merge_request_path(@merge_request, format: :json), project_path: project_path(@merge_request.project) } }
= render "projects/merge_requests/mr_title"
.merge-request-details.issuable-details{ data: { id: @merge_request.project.id } }
@@ -38,21 +38,21 @@
.nav-links.scrolling-tabs
%ul.merge-request-tabs
%li.notes-tab
- = link_to project_merge_request_path(@project, @merge_request), data: { target: 'div#notes', action: 'show', toggle: 'tab' } do
+ = tab_link_for @merge_request, :show, force_link: @commit.present? do
Discussion
%span.badge= @merge_request.related_notes.user.count
- if @merge_request.source_project
%li.commits-tab
- = link_to commits_project_merge_request_path(@project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do
+ = tab_link_for @merge_request, :commits do
Commits
%span.badge= @commits_count
- if @pipelines.any?
%li.pipelines-tab
- = link_to pipelines_project_merge_request_path(@project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
+ = tab_link_for @merge_request, :pipelines do
Pipelines
%span.badge.js-pipelines-mr-count= @pipelines.size
%li.diffs-tab
- = link_to diffs_project_merge_request_path(@project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do
+ = tab_link_for @merge_request, :diffs do
Changes
%span.badge= @merge_request.diff_size
#resolve-count-app.line-resolve-all-container.prepend-top-10{ "v-cloak" => true }
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index cad7c2e83db..2f56630c22e 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -18,7 +18,8 @@
A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), #{link_to 'among other things', help_page_path("user/project/index.md", anchor: "projects-features"), target: '_blank'}.
%p
All features are enabled when you create a project, but you can disable the ones you don’t need in the project settings.
- = brand_new_project_guidelines
+ .md
+ = brand_new_project_guidelines
.col-lg-9.js-toggle-container
%ul.nav-links.gitlab-tabs{ role: 'tablist' }
%li.active{ role: 'presentation' }
@@ -85,7 +86,7 @@
= icon('bug', text: 'Fogbugz')
%div
- if gitea_import_enabled?
- = link_to new_import_gitea_url, class: 'btn import_gitea' do
+ = link_to new_import_gitea_path, class: 'btn import_gitea' do
= custom_icon('go_logo')
Gitea
%div
diff --git a/app/views/projects/pipelines_settings/_show.html.haml b/app/views/projects/pipelines_settings/_show.html.haml
index ee4fa663b9f..c63e716180c 100644
--- a/app/views/projects/pipelines_settings/_show.html.haml
+++ b/app/views/projects/pipelines_settings/_show.html.haml
@@ -6,46 +6,35 @@
%h5 Auto DevOps (Beta)
%p
Auto DevOps will automatically build, test, and deploy your application based on a predefined Continuous Integration and Delivery configuration.
- This will happen starting with the next event (e.g.: push) that occurs to the project.
= link_to 'Learn more about Auto DevOps', help_page_path('topics/autodevops/index.md')
- message = auto_devops_warning_message(@project)
- if message
%p.settings-message.text-center
= message.html_safe
= f.fields_for :auto_devops_attributes, @auto_devops do |form|
- .radio.js-auto-devops-enable-radio-wrapper
+ .radio
= form.label :enabled_true do
- = form.radio_button :enabled, 'true', class: 'js-auto-devops-enable-radio'
+ = form.radio_button :enabled, 'true'
%strong Enable Auto DevOps
%br
%span.descr
The Auto DevOps pipeline configuration will be used when there is no <code>.gitlab-ci.yml</code> in the project.
- - if show_run_auto_devops_pipeline_checkbox_for_explicit_setting?(@project)
- .checkbox.hide.js-run-auto-devops-pipeline-checkbox-wrapper
- = label_tag 'project[run_auto_devops_pipeline_explicit]' do
- = check_box_tag 'project[run_auto_devops_pipeline_explicit]', true, false, class: 'js-run-auto-devops-pipeline-checkbox'
- = s_('ProjectSettings|Immediately run a pipeline on the default branch')
- .radio.js-auto-devops-enable-radio-wrapper
+ .radio
= form.label :enabled_false do
- = form.radio_button :enabled, 'false', class: 'js-auto-devops-enable-radio'
+ = form.radio_button :enabled, 'false'
%strong Disable Auto DevOps
%br
%span.descr
An explicit <code>.gitlab-ci.yml</code> needs to be specified before you can begin using Continuous Integration and Delivery.
- .radio.js-auto-devops-enable-radio-wrapper
+ .radio
= form.label :enabled_ do
- = form.radio_button :enabled, '', class: 'js-auto-devops-enable-radio'
+ = form.radio_button :enabled, ''
%strong Instance default (#{current_application_settings.auto_devops_enabled? ? 'enabled' : 'disabled'})
%br
%span.descr
Follow the instance default to either have Auto DevOps enabled or disabled when there is no project specific <code>.gitlab-ci.yml</code>.
- - if show_run_auto_devops_pipeline_checkbox_for_instance_setting?(@project)
- .checkbox.hide.js-run-auto-devops-pipeline-checkbox-wrapper
- = label_tag 'project[run_auto_devops_pipeline_implicit]' do
- = check_box_tag 'project[run_auto_devops_pipeline_implicit]', true, false, class: 'js-run-auto-devops-pipeline-checkbox'
- = s_('ProjectSettings|Immediately run a pipeline on the default branch')
%p
You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages.
= form.text_field :domain, class: 'form-control', placeholder: 'domain.com'
diff --git a/app/views/shared/_outdated_browser.html.haml b/app/views/shared/_outdated_browser.html.haml
index a638b0a805e..8ddb1b2bc99 100644
--- a/app/views/shared/_outdated_browser.html.haml
+++ b/app/views/shared/_outdated_browser.html.haml
@@ -4,5 +4,5 @@
GitLab may not work properly because you are using an outdated web browser.
%br
Please install a
- = link_to 'supported web browser', help_page_url('install/requirements', anchor: 'supported-web-browsers')
+ = link_to 'supported web browser', help_page_path('install/requirements', anchor: 'supported-web-browsers')
for a better experience.
diff --git a/app/views/shared/_recaptcha_form.html.haml b/app/views/shared/_recaptcha_form.html.haml
new file mode 100644
index 00000000000..0e816870f15
--- /dev/null
+++ b/app/views/shared/_recaptcha_form.html.haml
@@ -0,0 +1,19 @@
+- resource_name = spammable.class.model_name.singular
+- humanized_resource_name = spammable.class.model_name.human.downcase
+- script = local_assigns.fetch(:script, true)
+- has_submit = local_assigns.fetch(:has_submit, true)
+
+= form_for resource_name, method: :post, html: { class: 'recaptcha-form js-recaptcha-form' } do |f|
+ .recaptcha
+ - params[resource_name].each do |field, value|
+ = hidden_field(resource_name, field, value: value)
+ = hidden_field_tag(:spam_log_id, spammable.spam_log.id)
+ = hidden_field_tag(:recaptcha_verification, true)
+ = recaptcha_tags script: script, callback: 'recaptchaDialogCallback'
+
+ -# Yields a block with given extra params.
+ = yield
+
+ - if has_submit
+ .row-content-block.footer-block
+ = f.submit "Submit #{humanized_resource_name}", class: 'btn btn-create'
diff --git a/app/views/shared/notes/_notes_with_form.html.haml b/app/views/shared/notes/_notes_with_form.html.haml
index c6e18108c7a..e11f778adf5 100644
--- a/app/views/shared/notes/_notes_with_form.html.haml
+++ b/app/views/shared/notes/_notes_with_form.html.haml
@@ -27,7 +27,7 @@
- elsif discussion_locked
.disabled-comment.text-center.prepend-top-default
%span.issuable-note-warning
- %span.icon= sprite_icon('lock', size: 14)
+ = sprite_icon('lock', size: 16, css_class: 'icon')
%span
This
= issuable.class.to_s.titleize.downcase