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 ...
This commit is contained in:
commit
3c19c971df
33
CHANGELOG.md
33
CHANGELOG.md
|
|
@ -2,6 +2,17 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 10.2.4 (2017-12-07)
|
||||
|
||||
### Security (5 changes)
|
||||
|
||||
- Fix e-mail address disclosure through member search fields
|
||||
- Prevent creating issues through API when user does not have permissions
|
||||
- Prevent an information disclosure in the Groups API
|
||||
- Fix user without access to private Wiki being able to see it on the project page
|
||||
- Fix Cross-Site Scripting (XSS) vulnerability while editing a comment
|
||||
|
||||
|
||||
## 10.2.3 (2017-11-30)
|
||||
|
||||
### Fixed (7 changes)
|
||||
|
|
@ -237,6 +248,17 @@ entry.
|
|||
- Add Gitaly metrics to the performance bar.
|
||||
|
||||
|
||||
## 10.1.5 (2017-12-07)
|
||||
|
||||
### Security (5 changes)
|
||||
|
||||
- Fix e-mail address disclosure through member search fields
|
||||
- Prevent creating issues through API when user does not have permissions
|
||||
- Prevent an information disclosure in the Groups API
|
||||
- Fix user without access to private Wiki being able to see it on the project page
|
||||
- Fix Cross-Site Scripting (XSS) vulnerability while editing a comment
|
||||
|
||||
|
||||
## 10.1.4 (2017-11-14)
|
||||
|
||||
### Fixed (4 changes)
|
||||
|
|
@ -485,6 +507,17 @@ entry.
|
|||
- creation of keys moved to services. !13331 (haseebeqx)
|
||||
- Add username as GL_USERNAME in hooks.
|
||||
|
||||
## 10.0.7 (2017-12-07)
|
||||
|
||||
### Security (5 changes)
|
||||
|
||||
- Fix e-mail address disclosure through member search fields
|
||||
- Prevent creating issues through API when user does not have permissions
|
||||
- Prevent an information disclosure in the Groups API
|
||||
- Fix user without access to private Wiki being able to see it on the project page
|
||||
- Fix Cross-Site Scripting (XSS) vulnerability while editing a comment
|
||||
|
||||
|
||||
## 10.0.5 (2017-11-03)
|
||||
|
||||
- [FIXED] Fix incorrect X-axis labels in Prometheus graphs. !14258
|
||||
|
|
|
|||
|
|
@ -598,6 +598,7 @@ merge request:
|
|||
present time and never use past tense (has been/was). For example instead
|
||||
of _prohibited this user from being saved due to the following errors:_ the
|
||||
text should be _sorry, we could not create your account because:_
|
||||
1. Code should be written in [US English][us-english]
|
||||
|
||||
This is also the style used by linting tools such as
|
||||
[RuboCop](https://github.com/bbatsov/rubocop),
|
||||
|
|
@ -663,6 +664,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
|
|||
[GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues
|
||||
[polling-etag]: https://docs.gitlab.com/ce/development/polling.html
|
||||
[testing]: doc/development/testing_guide/index.md
|
||||
[us-english]: https://en.wikipedia.org/wiki/American_English
|
||||
|
||||
[^1]: Please note that specs other than JavaScript specs are considered backend
|
||||
code.
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
0.57.0
|
||||
0.59.0
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
5.10.1
|
||||
5.10.2
|
||||
|
|
|
|||
5
Gemfile
5
Gemfile
|
|
@ -283,7 +283,7 @@ group :metrics do
|
|||
gem 'influxdb', '~> 0.2', require: false
|
||||
|
||||
# Prometheus
|
||||
gem 'prometheus-client-mmap', '~> 0.7.0.beta39'
|
||||
gem 'prometheus-client-mmap', '~> 0.7.0.beta43'
|
||||
gem 'raindrops', '~> 0.18'
|
||||
end
|
||||
|
||||
|
|
@ -411,3 +411,6 @@ gem 'flipper-active_record', '~> 0.10.2'
|
|||
# Structured logging
|
||||
gem 'lograge', '~> 0.5'
|
||||
gem 'grape_logging', '~> 1.7'
|
||||
|
||||
# Asset synchronization
|
||||
gem 'asset_sync', '~> 2.2.0'
|
||||
|
|
|
|||
12
Gemfile.lock
12
Gemfile.lock
|
|
@ -58,6 +58,11 @@ GEM
|
|||
asciidoctor (1.5.3)
|
||||
asciidoctor-plantuml (0.0.7)
|
||||
asciidoctor (~> 1.5)
|
||||
asset_sync (2.2.0)
|
||||
activemodel (>= 4.1.0)
|
||||
fog-core
|
||||
mime-types (>= 2.99)
|
||||
unf
|
||||
ast (2.3.0)
|
||||
atomic (1.1.99)
|
||||
attr_encrypted (3.0.3)
|
||||
|
|
@ -486,7 +491,6 @@ GEM
|
|||
mini_mime (0.1.4)
|
||||
mini_portile2 (2.3.0)
|
||||
minitest (5.7.0)
|
||||
mmap2 (2.2.9)
|
||||
mousetrap-rails (1.4.6)
|
||||
multi_json (1.12.2)
|
||||
multi_xml (0.6.0)
|
||||
|
|
@ -623,8 +627,7 @@ GEM
|
|||
parser
|
||||
unparser
|
||||
procto (0.0.3)
|
||||
prometheus-client-mmap (0.7.0.beta39)
|
||||
mmap2 (~> 2.2, >= 2.2.9)
|
||||
prometheus-client-mmap (0.7.0.beta43)
|
||||
pry (0.10.4)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
|
|
@ -977,6 +980,7 @@ DEPENDENCIES
|
|||
asana (~> 0.6.0)
|
||||
asciidoctor (~> 1.5.2)
|
||||
asciidoctor-plantuml (= 0.0.7)
|
||||
asset_sync (~> 2.2.0)
|
||||
attr_encrypted (~> 3.0.0)
|
||||
awesome_print (~> 1.2.0)
|
||||
babosa (~> 1.0.2)
|
||||
|
|
@ -1109,7 +1113,7 @@ DEPENDENCIES
|
|||
peek-sidekiq (~> 1.0.3)
|
||||
pg (~> 0.18.2)
|
||||
premailer-rails (~> 1.9.7)
|
||||
prometheus-client-mmap (~> 0.7.0.beta39)
|
||||
prometheus-client-mmap (~> 0.7.0.beta43)
|
||||
pry-byebug (~> 3.4.1)
|
||||
pry-rails (~> 0.3.4)
|
||||
rack-attack (~> 4.4.1)
|
||||
|
|
|
|||
|
|
@ -130,7 +130,8 @@ freeze date (the 7th) should have a corresponding Enterprise Edition merge
|
|||
request, even if there are no conflicts. This is to reduce the size of the
|
||||
subsequent EE merge, as we often merge a lot to CE on the release date. For more
|
||||
information, see
|
||||
[limit conflicts with EE when developing on CE][limit_ee_conflicts].
|
||||
[Automatic CE->EE merge][automatic_ce_ee_merge] and
|
||||
[Guidelines for implementing Enterprise Edition features][ee_features].
|
||||
|
||||
### After the 7th
|
||||
|
||||
|
|
@ -281,4 +282,5 @@ still an issue I encourage you to open it on the [GitLab.com issue tracker](http
|
|||
["Implement design & UI elements" guidelines]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#implement-design-ui-elements
|
||||
[Thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review
|
||||
[done]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#definition-of-done
|
||||
[limit_ee_conflicts]: https://docs.gitlab.com/ce/development/limit_ee_conflicts.html
|
||||
[automatic_ce_ee_merge]: https://docs.gitlab.com/ce/development/automatic_ce_ee_merge.html
|
||||
[ee_features]: https://docs.gitlab.com/ce/development/ee_features.html
|
||||
|
|
|
|||
|
|
@ -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') {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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}`;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}).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);
|
||||
}
|
||||
});
|
||||
$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');
|
||||
}
|
||||
});
|
||||
},
|
||||
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');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
||||
|
|
|
|||
|
|
@ -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 || {};
|
||||
|
|
|
|||
|
|
@ -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.'));
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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(/"/g, '"'));
|
||||
const props = JSON.parse(initialDataEl.innerHTML.replace(/"/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,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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('#');
|
||||
}
|
||||
|
||||
export function getLocationHash(url = window.location.href) {
|
||||
const hashIndex = url.indexOf('#');
|
||||
|
||||
return hashIndex === -1 ? null : url.substring(hashIndex + 1);
|
||||
};
|
||||
}
|
||||
|
||||
w.gl.utils.refreshCurrentPage = () => gl.utils.visitUrl(window.location.href);
|
||||
|
||||
// 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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
);
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -48,3 +48,10 @@ body.modal-open {
|
|||
display: block;
|
||||
}
|
||||
|
||||
.recaptcha-dialog .recaptcha-form {
|
||||
display: inline-block;
|
||||
|
||||
.recaptcha {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
|
|||
:task_num,
|
||||
:title,
|
||||
:discussion_locked,
|
||||
|
||||
label_ids: []
|
||||
]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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: ''
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
##
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ class Discussion
|
|||
:author,
|
||||
|
||||
:noteable,
|
||||
:commit_id,
|
||||
:for_commit?,
|
||||
:for_merge_request?,
|
||||
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue