Merge remote-tracking branch 'origin/master' into backport-ee-changes-for-build-minutes
This commit is contained in:
commit
ac92554dfc
|
@ -35,7 +35,6 @@ stages:
|
|||
.dedicated-runner: &dedicated-runner
|
||||
tags:
|
||||
- gitlab-org
|
||||
- 2gb
|
||||
|
||||
.knapsack-state: &knapsack-state
|
||||
services: []
|
||||
|
|
130
CHANGELOG.md
130
CHANGELOG.md
|
@ -2,6 +2,136 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 8.16.0 (2017-02-22)
|
||||
|
||||
- Add LDAP Rake task to rename a provider. !2181
|
||||
- Validate label's title length. !5767 (Tomáš Kukrál)
|
||||
- Allow to add deploy keys with write-access. !5807 (Ali Ibrahim)
|
||||
- Allow to use + symbol in filenames. !6644 (blackst0ne)
|
||||
- Search bar redesign first iteration. !7345
|
||||
- Fix date inconsistency on due date picker. !7422 (Giuliano Varriale)
|
||||
- Add email confirmation field to registration form. !7432
|
||||
- Updated project visibility settings UX. !7645
|
||||
- Go to a project order. !7737 (Jacopo Beschi @jacopo-beschi)
|
||||
- Support slash comand `/merge` for merging merge requests. !7746 (Jarka Kadlecova)
|
||||
- Add more storage statistics. !7754 (Markus Koller)
|
||||
- Add support for PlantUML diagrams in AsciiDoc documents. !7810 (Horacio Sanson)
|
||||
- Remove extra orphaned rows when removing stray namespaces. !7841
|
||||
- Added lighter count badge background-color for on white backgrounds. !7873
|
||||
- Fixes issue boards list colored top border visual glitch. !7898 (Pier Paolo Ramon)
|
||||
- change 'gray' color theme name to 'black' to match the actual color. !7908 (BM5k)
|
||||
- Remove trailing whitespace when generating changelog entry. !7948
|
||||
- Remove checking branches state in issue new branch button. !8023
|
||||
- Log LDAP blocking/unblocking events to application log. !8042 (Markus Koller)
|
||||
- ensure permalinks scroll to correct position on multiple clicks. !8046
|
||||
- Allow to use ENV variables in redis config. !8073 (Semyon Pupkov)
|
||||
- fix button layout issue on branches page. !8074
|
||||
- Reduce DB-load for build-queues by storing last_update in Redis. !8084
|
||||
- Record and show last used date of SSH Keys. !8113 (Vincent Wong)
|
||||
- Resolves overflow in compare branch and tags dropdown. !8118
|
||||
- Replace wording for slash command confirmation message. !8123
|
||||
- remove build_user. !8162 (Arsenev Vladislav)
|
||||
- Prevent empty pagination when list is not empty. !8172
|
||||
- Make successful pipeline emails off for watchers. !8176
|
||||
- Improve copy in Issue Tracker empty state. !8202
|
||||
- Adds CSS class to status icon on MR widget to prevent non-colored icon. !8219
|
||||
- Improve visibility of "Resolve conflicts" and "Merge locally" actions. !8229
|
||||
- Add Gitaly to the architecture documentation. !8264 (Pablo Carranza <pablo@gitlab.com>)
|
||||
- Sort numbers in build names more intelligently. !8277
|
||||
- Show nested groups tab on group page. !8308
|
||||
- Rename users with namespace ending with .git. !8309
|
||||
- Rename filename to file path in tooltip of file header in merge request diff. !8314
|
||||
- About GitLab link in sidebar that links to help page. !8316
|
||||
- Merged the 'Groups' and 'Projects' tabs when viewing user profiles. !8323 (James Gregory)
|
||||
- re-enable change username button after failure. !8332
|
||||
- Darkened hr border color in descriptions because of update of bootstrap. !8333
|
||||
- display merge request discussion tab for empty branches. !8347
|
||||
- Fix double spaced CI log. !8349 (Jared Deckard <jared.deckard@gmail.com>)
|
||||
- Refactored note edit form to improve frontend performance on MR and Issues pages, especially pages with has a lot of discussions in it. !8356
|
||||
- Make CTRL+Enter submits a new merge request. !8360 (Saad Shahd)
|
||||
- Fixes too short input for placeholder message in commit listing page. !8367
|
||||
- Fix typo: seach to search. !8370
|
||||
- Adds label to Environments "Date Created". !8376 (Saad Shahd)
|
||||
- Convert project setting text into protected branch path link. !8377 (Ken Ding)
|
||||
- Precompile all JavaScript fixtures. !8384
|
||||
- Use original casing for build action text. !8387
|
||||
- Scroll to bottom on build completion if autoscroll was active. !8391
|
||||
- Properly handle failed reCAPTCHA on user registration. !8403
|
||||
- Changed alerts to be responsive, centered text on smaller viewports. !8424 (Connor Smallman)
|
||||
- Pass Gitaly resource path to gitlab-workhorse if Gitaly is enabled. !8440
|
||||
- Fixes and Improves CSS and HTML problems in mini pipeline graph and builds dropdown. !8443
|
||||
- Don't instrument 405 Grape calls. !8445
|
||||
- Change CI template linter textarea with Ace Editor. !8452 (Didem Acet)
|
||||
- Removes unneeded `window` declaration in environments related code. !8456
|
||||
- API: fix query response for `/projects/:id/issues?milestone="No%20Milestone"`. !8457 (Panagiotis Atmatzidis, David Eisner)
|
||||
- Fix broken url on group avatar. !8464 (hogewest)
|
||||
- Fixes buttons not being accessible via the keyboard when creating new group. !8469
|
||||
- Restore backup correctly when "BACKUP" environment variable is passed. !8477
|
||||
- Add new endpoints for Time Tracking. !8483
|
||||
- Fix Compare page throws 500 error when any branch/reference is not selected. !8492 (Martin Cabrera)
|
||||
- Treat environments matching `production/*` as Production. !8500
|
||||
- Hide build artifacts keep button if operation is not allowed. !8501
|
||||
- Update the gitlab-markup gem to the version 1.5.1. !8509
|
||||
- Remove Lock Icon on Protected Tag. !8513 (Sergey Nikitin)
|
||||
- Use cached values to compute total issues count in milestone index pages. !8518
|
||||
- Speed up dashboard milestone index by scoping IssuesFinder to user authorized projects. !8524
|
||||
- Copy <some text> to clipboard. !8535
|
||||
- Check for env[Grape::Env::GRAPE_ROUTING_ARGS] instead of endpoint.route. !8544
|
||||
- Fixes builds dropdown making request when clicked to be closed. !8545
|
||||
- Fixes pipeline status cell is too wide by adding missing classes in table head cells. !8549
|
||||
- Mutate the attribute instead of issuing a write operation to the DB in `ProjectFeaturesCompatibility` concern. !8552
|
||||
- Fix links to commits pages on pipelines list page. !8558
|
||||
- Ensure updating project settings shows a flash message on success. !8579 (Sandish Chen)
|
||||
- Fixes big pipeline and small pipeline width problems and tooltips text being outside the tooltip. !8593
|
||||
- Autoresize markdown preview. !8607 (Didem Acet)
|
||||
- Link external build badge to its target URL. !8611
|
||||
- Adjust ProjectStatistic#repository_size with values saved as MB. !8616
|
||||
- Correct User-agent placement in robots.txt. !8623 (Eric Sabelhaus)
|
||||
- Record used SSH keys only once per day. !8655
|
||||
- Do not generate pipeline branch/tag path if not present. !8658
|
||||
- Fix Merge When Pipeline Succeeds immediate merge bug. !8685
|
||||
- Fix blame 500 error on invalid path. !25761 (Jeff Stubler)
|
||||
- Added animations to issue boards interactions.
|
||||
- Check if user can read project before being assigned to issue.
|
||||
- Show 'too many changes' message for created merge requests when they are too large.
|
||||
- Fix redirect after update file when user has forked project.
|
||||
- Parse JIRA issue references even if Issue Tracker is disabled.
|
||||
- Made download artifacts button accessible via keyboard by changing it from an anchor tag to an actual button. (Ryan Harris)
|
||||
- Make play button on Pipelines page accessible via keyboard. (Ryan Harris)
|
||||
- Decreases font-size on login page.
|
||||
- Fixed merge request tabs dont move when opening collapsed sidebar.
|
||||
- Display project avatars on Admin Area and Projects pages for mobile views. (Ryan Harris)
|
||||
- Fix participants margins to fit on one line.
|
||||
- 26352 Change Profile settings to User / Settings.
|
||||
- Fix Commits API to accept a Project path upon POST.
|
||||
- Expire related caches after changing HEAD. (Minqi Pan)
|
||||
- Add various hover animations throughout the application.
|
||||
- Re-order update steps in the 8.14 -> 8.15 upgrade guide.
|
||||
- Move award emoji's out of the discussion tab for merge requests.
|
||||
- Synchronize all project authorization refreshing work to prevent race conditions.
|
||||
- Remove the project_authorizations.id column.
|
||||
- Combined the settings options project members and groups into a single one called members.
|
||||
- Change earlier to task_status_short to avoid titlebar line wraps.
|
||||
- 25701 standardize text colors.
|
||||
- Handle HTTP errors in environment list.
|
||||
- Re-add Google Cloud Storage as a backup strategy.
|
||||
- Change status colors of runners to better defaults.
|
||||
- Added number_with_delimiter to counter on milestone panels. (Ryan Harris)
|
||||
- Query external CI statuses in the background.
|
||||
- Allow group and project paths when transferring projects via the API.
|
||||
- Don't validate environment urls on .gitlab-ci.yml.
|
||||
- Fix a Grape deprecation, use `#request_method` instead of `#route_method`.
|
||||
- Fill missing authorized projects rows.
|
||||
- Allow API query to find projects with dots in their name. (Bruno Melli)
|
||||
- Fix import/export wrong user mapping.
|
||||
- Removed bottom padding from merge manually from CLI because of repositioning award emoji's.
|
||||
- Fix project queued for deletion re-creation tooltip.
|
||||
- Fix search group/project filtering to show results.
|
||||
- Fix 500 error when POSTing to Users API with optional confirm param.
|
||||
- 26504 Fix styling of MR jump to discussion button.
|
||||
- Add margin to markdown math blocks.
|
||||
- Add hover state to MR comment reply button.
|
||||
|
||||
## 8.15.4 (2017-01-09)
|
||||
|
||||
- Make successful pipeline emails off for watchers. !8176
|
||||
|
|
|
@ -1 +1 @@
|
|||
1.2.1
|
||||
1.3.0
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -21,7 +21,7 @@ gem 'rugged', '~> 0.24.0'
|
|||
# Authentication libraries
|
||||
gem 'devise', '~> 4.2'
|
||||
gem 'doorkeeper', '~> 4.2.0'
|
||||
gem 'omniauth', '~> 1.3.1'
|
||||
gem 'omniauth', '~> 1.3.2'
|
||||
gem 'omniauth-auth0', '~> 1.4.1'
|
||||
gem 'omniauth-azure-oauth2', '~> 0.0.6'
|
||||
gem 'omniauth-cas3', '~> 1.1.2'
|
||||
|
|
|
@ -449,7 +449,7 @@ GEM
|
|||
octokit (4.6.2)
|
||||
sawyer (~> 0.8.0, >= 0.5.3)
|
||||
oj (2.17.4)
|
||||
omniauth (1.3.1)
|
||||
omniauth (1.3.2)
|
||||
hashie (>= 1.2, < 4)
|
||||
rack (>= 1.0, < 3)
|
||||
omniauth-auth0 (1.4.1)
|
||||
|
@ -925,7 +925,7 @@ DEPENDENCIES
|
|||
oauth2 (~> 1.2.0)
|
||||
octokit (~> 4.6.2)
|
||||
oj (~> 2.17.4)
|
||||
omniauth (~> 1.3.1)
|
||||
omniauth (~> 1.3.2)
|
||||
omniauth-auth0 (~> 1.4.1)
|
||||
omniauth-authentiq (~> 0.2.0)
|
||||
omniauth-azure-oauth2 (~> 0.0.6)
|
||||
|
|
|
@ -113,4 +113,4 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on
|
|||
## Is it awesome?
|
||||
|
||||
Thanks for [asking this question](https://twitter.com/supersloth/status/489462789384056832) Joshua.
|
||||
[These people](https://twitter.com/gitlab/favorites) seem to like it.
|
||||
[These people](https://twitter.com/gitlab/likes) seem to like it.
|
||||
|
|
|
@ -62,6 +62,7 @@ var DropDown = function(list) {
|
|||
this.list = list;
|
||||
this.items = [];
|
||||
this.getItems();
|
||||
this.initTemplateString();
|
||||
this.addEvents();
|
||||
this.initialState = list.innerHTML;
|
||||
};
|
||||
|
@ -72,6 +73,17 @@ Object.assign(DropDown.prototype, {
|
|||
return this.items;
|
||||
},
|
||||
|
||||
initTemplateString: function() {
|
||||
var items = this.items || this.getItems();
|
||||
|
||||
var templateString = '';
|
||||
if(items.length > 0) {
|
||||
templateString = items[items.length - 1].outerHTML;
|
||||
}
|
||||
this.templateString = templateString;
|
||||
return this.templateString;
|
||||
},
|
||||
|
||||
clickEvent: function(e) {
|
||||
// climb up the tree to find the LI
|
||||
var selected = utils.closest(e.target, 'LI');
|
||||
|
@ -111,30 +123,21 @@ Object.assign(DropDown.prototype, {
|
|||
|
||||
addData: function(data) {
|
||||
this.data = (this.data || []).concat(data);
|
||||
this.render(data);
|
||||
this.render(this.data);
|
||||
},
|
||||
|
||||
// call render manually on data;
|
||||
render: function(data){
|
||||
// debugger
|
||||
// empty the list first
|
||||
var sampleItem;
|
||||
var templateString = this.templateString;
|
||||
var newChildren = [];
|
||||
var toAppend;
|
||||
|
||||
for(var i = 0; i < this.items.length; i++) {
|
||||
var item = this.items[i];
|
||||
sampleItem = item;
|
||||
if(item.parentNode && item.parentNode.dataset.hasOwnProperty('dynamic')) {
|
||||
item.parentNode.removeChild(item);
|
||||
}
|
||||
}
|
||||
|
||||
newChildren = this.data.map(function(dat){
|
||||
var html = utils.t(sampleItem.outerHTML, dat);
|
||||
newChildren = (data ||[]).map(function(dat){
|
||||
var html = utils.t(templateString, dat);
|
||||
var template = document.createElement('div');
|
||||
template.innerHTML = html;
|
||||
// console.log(template.content)
|
||||
|
||||
// Help set the image src template
|
||||
var imageTags = template.querySelectorAll('img[data-src]');
|
||||
|
@ -156,7 +159,7 @@ Object.assign(DropDown.prototype, {
|
|||
if(toAppend) {
|
||||
toAppend.innerHTML = newChildren.join('');
|
||||
} else {
|
||||
this.list.innerHTML = newChildren.join('');
|
||||
this.list.innerHTML = newChildren.join('');
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -173,10 +176,7 @@ Object.assign(DropDown.prototype, {
|
|||
},
|
||||
|
||||
destroy: function() {
|
||||
if (!this.hidden) {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
this.hide();
|
||||
this.list.removeEventListener('click', this.clickWrapper);
|
||||
}
|
||||
});
|
||||
|
@ -278,7 +278,7 @@ require('./window')(function(w){
|
|||
self.hooks[i].list.hide();
|
||||
}
|
||||
}.bind(this);
|
||||
w.addEventListener('click', this.windowClickedWrapper);
|
||||
document.addEventListener('click', this.windowClickedWrapper);
|
||||
},
|
||||
|
||||
removeEvents: function(){
|
||||
|
@ -307,7 +307,7 @@ require('./window')(function(w){
|
|||
if(!list){
|
||||
list = document.querySelector(hook.dataset[utils.toDataCamelCase(DATA_TRIGGER)]);
|
||||
}
|
||||
|
||||
|
||||
if(hook) {
|
||||
if(hook.tagName === 'A' || hook.tagName === 'BUTTON') {
|
||||
this.hooks.push(new HookButton(hook, list, plugins, config));
|
||||
|
@ -462,6 +462,8 @@ Object.assign(HookInput.prototype, {
|
|||
var self = this;
|
||||
|
||||
this.mousedown = function mousedown(e) {
|
||||
if(self.hasRemovedEvents) return;
|
||||
|
||||
var mouseEvent = new CustomEvent('mousedown.dl', {
|
||||
detail: {
|
||||
hook: self,
|
||||
|
@ -474,6 +476,8 @@ Object.assign(HookInput.prototype, {
|
|||
}
|
||||
|
||||
this.input = function input(e) {
|
||||
if(self.hasRemovedEvents) return;
|
||||
|
||||
var inputEvent = new CustomEvent('input.dl', {
|
||||
detail: {
|
||||
hook: self,
|
||||
|
@ -487,10 +491,14 @@ Object.assign(HookInput.prototype, {
|
|||
}
|
||||
|
||||
this.keyup = function keyup(e) {
|
||||
if(self.hasRemovedEvents) return;
|
||||
|
||||
keyEvent(e, 'keyup.dl');
|
||||
}
|
||||
|
||||
this.keydown = function keydown(e) {
|
||||
if(self.hasRemovedEvents) return;
|
||||
|
||||
keyEvent(e, 'keydown.dl');
|
||||
}
|
||||
|
||||
|
@ -520,7 +528,8 @@ Object.assign(HookInput.prototype, {
|
|||
this.trigger.addEventListener('keydown', this.keydown);
|
||||
},
|
||||
|
||||
removeEvents: function(){
|
||||
removeEvents: function() {
|
||||
this.hasRemovedEvents = true;
|
||||
this.trigger.removeEventListener('mousedown', this.mousedown);
|
||||
this.trigger.removeEventListener('input', this.input);
|
||||
this.trigger.removeEventListener('keyup', this.keyup);
|
||||
|
@ -578,7 +587,7 @@ require('./window')(function(w){
|
|||
var listItems = removeHighlight(list);
|
||||
if(currentIndex>0){
|
||||
if(!listItems[currentIndex-1]){
|
||||
currentIndex = currentIndex-1;
|
||||
currentIndex = currentIndex-1;
|
||||
}
|
||||
listItems[currentIndex-1].classList.add('dropdown-active');
|
||||
}
|
||||
|
@ -630,7 +639,7 @@ require('./window')(function(w){
|
|||
return;
|
||||
}
|
||||
if(currentKey === 'ArrowUp') {
|
||||
isUpArrow = true;
|
||||
isUpArrow = true;
|
||||
}
|
||||
if(currentKey === 'ArrowDown') {
|
||||
isDownArrow = true;
|
||||
|
@ -668,16 +677,16 @@ var camelize = function(str) {
|
|||
};
|
||||
|
||||
var closest = function(thisTag, stopTag) {
|
||||
while(thisTag.tagName !== stopTag && thisTag.tagName !== 'HTML'){
|
||||
while(thisTag && thisTag.tagName !== stopTag && thisTag.tagName !== 'HTML'){
|
||||
thisTag = thisTag.parentNode;
|
||||
}
|
||||
return thisTag;
|
||||
};
|
||||
|
||||
var isDropDownParts = function(target) {
|
||||
if(target.tagName === 'HTML') { return false; }
|
||||
if(!target || target.tagName === 'HTML') { return false; }
|
||||
return (
|
||||
target.hasAttribute(DATA_TRIGGER) ||
|
||||
target.hasAttribute(DATA_TRIGGER) ||
|
||||
target.hasAttribute(DATA_DROPDOWN)
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
/* global CustomEvent */
|
||||
/* eslint-disable no-global-assign */
|
||||
|
||||
// Custom event support for IE
|
||||
CustomEvent = function CustomEvent(event, parameters) {
|
||||
const params = parameters || { bubbles: false, cancelable: false, detail: undefined };
|
||||
const evt = document.createEvent('CustomEvent');
|
||||
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
|
||||
return evt;
|
||||
};
|
||||
|
||||
CustomEvent.prototype = window.Event.prototype;
|
|
@ -9,7 +9,7 @@
|
|||
this.config = {
|
||||
droplabFilter: {
|
||||
template: 'hint',
|
||||
filterFunction: gl.DropdownUtils.filterHint,
|
||||
filterFunction: gl.DropdownUtils.filterHint.bind(null, input),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -20,6 +20,9 @@
|
|||
if (selected.tagName === 'LI') {
|
||||
if (selected.hasAttribute('data-value')) {
|
||||
this.dismissDropdown();
|
||||
} else if (selected.getAttribute('data-action') === 'submit') {
|
||||
this.dismissDropdown();
|
||||
this.dispatchFormSubmitEvent();
|
||||
} else {
|
||||
const token = selected.querySelector('.js-filter-hint').innerText.trim();
|
||||
const tag = selected.querySelector('.js-filter-tag').innerText.trim();
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
loadingTemplate: this.loadingTemplate,
|
||||
},
|
||||
droplabFilter: {
|
||||
filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol),
|
||||
filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
}
|
||||
|
||||
getSearchInput() {
|
||||
const query = this.input.value.trim();
|
||||
const query = gl.DropdownUtils.getSearchInput(this.input);
|
||||
const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
|
||||
|
||||
return lastToken.value || '';
|
||||
|
|
|
@ -20,17 +20,15 @@
|
|||
return escapedText;
|
||||
}
|
||||
|
||||
static filterWithSymbol(filterSymbol, item, query) {
|
||||
static filterWithSymbol(filterSymbol, input, item) {
|
||||
const updatedItem = item;
|
||||
const query = gl.DropdownUtils.getSearchInput(input);
|
||||
const { lastToken, searchToken } = gl.FilteredSearchTokenizer.processTokens(query);
|
||||
|
||||
if (lastToken !== searchToken) {
|
||||
const title = updatedItem.title.toLowerCase();
|
||||
let value = lastToken.value.toLowerCase();
|
||||
|
||||
if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) {
|
||||
value = value.slice(1);
|
||||
}
|
||||
value = value.replace(/"(.*?)"/g, str => str.slice(1).slice(0, -1));
|
||||
|
||||
// Eg. filterSymbol = ~ for labels
|
||||
const matchWithoutSymbol = lastToken.symbol === filterSymbol && title.indexOf(value) !== -1;
|
||||
|
@ -44,8 +42,9 @@
|
|||
return updatedItem;
|
||||
}
|
||||
|
||||
static filterHint(item, query) {
|
||||
static filterHint(input, item) {
|
||||
const updatedItem = item;
|
||||
const query = gl.DropdownUtils.getSearchInput(input);
|
||||
let { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
|
||||
lastToken = lastToken.key || lastToken || '';
|
||||
|
||||
|
@ -72,6 +71,48 @@
|
|||
// Return boolean based on whether it was set
|
||||
return dataValue !== null;
|
||||
}
|
||||
|
||||
static getSearchInput(filteredSearchInput) {
|
||||
const inputValue = filteredSearchInput.value;
|
||||
const { right } = gl.DropdownUtils.getInputSelectionPosition(filteredSearchInput);
|
||||
|
||||
return inputValue.slice(0, right);
|
||||
}
|
||||
|
||||
static getInputSelectionPosition(input) {
|
||||
const selectionStart = input.selectionStart;
|
||||
let inputValue = input.value;
|
||||
// Replace all spaces inside quote marks with underscores
|
||||
// This helps with matching the beginning & end of a token:key
|
||||
inputValue = inputValue.replace(/"(.*?)"/g, str => str.replace(/\s/g, '_'));
|
||||
|
||||
// Get the right position for the word selected
|
||||
// Regex matches first space
|
||||
let right = inputValue.slice(selectionStart).search(/\s/);
|
||||
|
||||
if (right >= 0) {
|
||||
right += selectionStart;
|
||||
} else if (right < 0) {
|
||||
right = inputValue.length;
|
||||
}
|
||||
|
||||
// Get the left position for the word selected
|
||||
// Regex matches last non-whitespace character
|
||||
let left = inputValue.slice(0, right).search(/\S+$/);
|
||||
|
||||
if (selectionStart === 0) {
|
||||
left = 0;
|
||||
} else if (selectionStart === inputValue.length && left < 0) {
|
||||
left = inputValue.length;
|
||||
} else if (left < 0) {
|
||||
left = selectionStart;
|
||||
}
|
||||
|
||||
return {
|
||||
left,
|
||||
right,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
}
|
||||
|
||||
this.dismissDropdown();
|
||||
this.dispatchInputEvent();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,7 +79,16 @@
|
|||
dispatchInputEvent() {
|
||||
// Propogate input change to FilteredSearchDropdownManager
|
||||
// so that it can determine which dropdowns to open
|
||||
this.input.dispatchEvent(new Event('input'));
|
||||
this.input.dispatchEvent(new CustomEvent('input', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
}));
|
||||
}
|
||||
|
||||
dispatchFormSubmitEvent() {
|
||||
// dispatchEvent() is necessary as form.submit() does not
|
||||
// trigger event handlers
|
||||
this.input.form.dispatchEvent(new Event('submit'));
|
||||
}
|
||||
|
||||
hideDropdown() {
|
||||
|
|
|
@ -57,28 +57,33 @@
|
|||
|
||||
static addWordToInput(tokenName, tokenValue = '') {
|
||||
const input = document.querySelector('.filtered-search');
|
||||
const inputValue = input.value;
|
||||
const word = `${tokenName}:${tokenValue}`;
|
||||
|
||||
const { lastToken, searchToken } = gl.FilteredSearchTokenizer.processTokens(input.value);
|
||||
const lastSearchToken = searchToken.split(' ').last();
|
||||
const lastInputCharacter = input.value[input.value.length - 1];
|
||||
const lastInputTrimmedCharacter = input.value.trim()[input.value.trim().length - 1];
|
||||
// Get the string to replace
|
||||
let newCaretPosition = input.selectionStart;
|
||||
const { left, right } = gl.DropdownUtils.getInputSelectionPosition(input);
|
||||
|
||||
// Remove the typed tokenName
|
||||
if (word.indexOf(lastSearchToken) === 0 && searchToken !== '') {
|
||||
// Remove spaces after the colon
|
||||
if (lastInputCharacter === ' ' && lastInputTrimmedCharacter === ':') {
|
||||
input.value = input.value.trim();
|
||||
}
|
||||
input.value = `${inputValue.substr(0, left)}${word}${inputValue.substr(right)}`;
|
||||
|
||||
input.value = input.value.slice(0, -1 * lastSearchToken.length);
|
||||
} else if (lastInputCharacter !== ' ' || (lastToken && lastToken.value[lastToken.value.length - 1] === ' ')) {
|
||||
// Remove the existing tokenValue
|
||||
const lastTokenString = `${lastToken.key}:${lastToken.symbol}${lastToken.value}`;
|
||||
input.value = input.value.slice(0, -1 * lastTokenString.length);
|
||||
// If we have added a tokenValue at the end of the input,
|
||||
// add a space and set selection to the end
|
||||
if (right >= inputValue.length && tokenValue !== '') {
|
||||
input.value += ' ';
|
||||
newCaretPosition = input.value.length;
|
||||
}
|
||||
|
||||
input.value += word;
|
||||
gl.FilteredSearchDropdownManager.updateInputCaretPosition(newCaretPosition, input);
|
||||
}
|
||||
|
||||
static updateInputCaretPosition(selectionStart, input) {
|
||||
// Reset the position
|
||||
// Sometimes can end up at end of input
|
||||
input.setSelectionRange(selectionStart, selectionStart);
|
||||
|
||||
const { right } = gl.DropdownUtils.getInputSelectionPosition(input);
|
||||
|
||||
input.setSelectionRange(right, right);
|
||||
}
|
||||
|
||||
updateCurrentDropdownOffset() {
|
||||
|
@ -90,9 +95,18 @@
|
|||
this.font = window.getComputedStyle(this.filteredSearchInput).font;
|
||||
}
|
||||
|
||||
const input = this.filteredSearchInput;
|
||||
const inputText = input.value.slice(0, input.selectionStart);
|
||||
const filterIconPadding = 27;
|
||||
const offset = gl.text
|
||||
.getTextWidth(this.filteredSearchInput.value, this.font) + filterIconPadding;
|
||||
let offset = gl.text.getTextWidth(inputText, this.font) + filterIconPadding;
|
||||
|
||||
const currentDropdownWidth = this.mapping[key].element.clientWidth === 0 ? 200 :
|
||||
this.mapping[key].element.clientWidth;
|
||||
const offsetMaxWidth = this.filteredSearchInput.clientWidth - currentDropdownWidth;
|
||||
|
||||
if (offsetMaxWidth < offset) {
|
||||
offset = offsetMaxWidth;
|
||||
}
|
||||
|
||||
this.mapping[key].reference.setOffset(offset);
|
||||
}
|
||||
|
@ -148,9 +162,9 @@
|
|||
|
||||
setDropdown() {
|
||||
const { lastToken, searchToken } = this.tokenizer
|
||||
.processTokens(this.filteredSearchInput.value);
|
||||
.processTokens(gl.DropdownUtils.getSearchInput(this.filteredSearchInput));
|
||||
|
||||
if (this.filteredSearchInput.value.split('').last() === ' ') {
|
||||
if (this.currentDropdown) {
|
||||
this.updateCurrentDropdownOffset();
|
||||
}
|
||||
|
||||
|
|
|
@ -25,24 +25,32 @@
|
|||
}
|
||||
|
||||
bindEvents() {
|
||||
this.handleFormSubmit = this.handleFormSubmit.bind(this);
|
||||
this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager);
|
||||
this.toggleClearSearchButtonWrapper = this.toggleClearSearchButton.bind(this);
|
||||
this.checkForEnterWrapper = this.checkForEnter.bind(this);
|
||||
this.clearSearchWrapper = this.clearSearch.bind(this);
|
||||
this.checkForBackspaceWrapper = this.checkForBackspace.bind(this);
|
||||
this.tokenChange = this.tokenChange.bind(this);
|
||||
|
||||
this.filteredSearchInput.form.addEventListener('submit', this.handleFormSubmit);
|
||||
this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper);
|
||||
this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper);
|
||||
this.filteredSearchInput.addEventListener('keydown', this.checkForEnterWrapper);
|
||||
this.filteredSearchInput.addEventListener('keyup', this.checkForBackspaceWrapper);
|
||||
this.filteredSearchInput.addEventListener('click', this.tokenChange);
|
||||
this.filteredSearchInput.addEventListener('keyup', this.tokenChange);
|
||||
this.clearSearchButton.addEventListener('click', this.clearSearchWrapper);
|
||||
}
|
||||
|
||||
unbindEvents() {
|
||||
this.filteredSearchInput.form.removeEventListener('submit', this.handleFormSubmit);
|
||||
this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper);
|
||||
this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper);
|
||||
this.filteredSearchInput.removeEventListener('keydown', this.checkForEnterWrapper);
|
||||
this.filteredSearchInput.removeEventListener('keyup', this.checkForBackspaceWrapper);
|
||||
this.filteredSearchInput.removeEventListener('click', this.tokenChange);
|
||||
this.filteredSearchInput.removeEventListener('keyup', this.tokenChange);
|
||||
this.clearSearchButton.removeEventListener('click', this.clearSearchWrapper);
|
||||
}
|
||||
|
||||
|
@ -83,8 +91,14 @@
|
|||
this.dropdownManager.resetDropdowns();
|
||||
}
|
||||
|
||||
handleFormSubmit(e) {
|
||||
e.preventDefault();
|
||||
this.search();
|
||||
}
|
||||
|
||||
loadSearchParamsFromURL() {
|
||||
const params = gl.utils.getUrlParamsArray();
|
||||
const usernameParams = this.getUsernameParams();
|
||||
const inputValues = [];
|
||||
|
||||
params.forEach((p) => {
|
||||
|
@ -115,6 +129,16 @@
|
|||
}
|
||||
|
||||
inputValues.push(`${sanitizedKey}:${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`);
|
||||
} else if (!match && keyParam === 'assignee_id') {
|
||||
const id = parseInt(value, 10);
|
||||
if (usernameParams[id]) {
|
||||
inputValues.push(`assignee:@${usernameParams[id]}`);
|
||||
}
|
||||
} else if (!match && keyParam === 'author_id') {
|
||||
const id = parseInt(value, 10);
|
||||
if (usernameParams[id]) {
|
||||
inputValues.push(`author:@${usernameParams[id]}`);
|
||||
}
|
||||
} else if (!match && keyParam === 'search') {
|
||||
inputValues.push(sanitizedValue);
|
||||
}
|
||||
|
@ -164,6 +188,27 @@
|
|||
|
||||
Turbolinks.visit(`?scope=all&utf8=✓&${paths.join('&')}`);
|
||||
}
|
||||
|
||||
getUsernameParams() {
|
||||
const usernamesById = {};
|
||||
try {
|
||||
const attribute = this.filteredSearchInput.getAttribute('data-username-params');
|
||||
JSON.parse(attribute).forEach((user) => {
|
||||
usernamesById[user.id] = user.username;
|
||||
});
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
return usernamesById;
|
||||
}
|
||||
|
||||
tokenChange() {
|
||||
const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
|
||||
const currentDropdownRef = dropdown.reference;
|
||||
|
||||
this.setDropdownWrapper();
|
||||
currentDropdownRef.dispatchInputEvent();
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
(function() {
|
||||
this.GroupAvatar = (function() {
|
||||
function GroupAvatar() {
|
||||
$('.js-choose-group-avatar-button').bind("click", function() {
|
||||
$('.js-choose-group-avatar-button').on("click", function() {
|
||||
var form;
|
||||
form = $(this).closest("form");
|
||||
return form.find(".js-group-avatar-input").click();
|
||||
});
|
||||
$('.js-group-avatar-input').bind("change", function() {
|
||||
$('.js-group-avatar-input').on("change", function() {
|
||||
var filename, form;
|
||||
form = $(this).closest("form");
|
||||
filename = $(this).val().replace(/^.*[\\\/]/, '');
|
||||
|
|
|
@ -126,7 +126,9 @@
|
|||
|
||||
MergeRequestWidget.prototype.getMergeStatus = function() {
|
||||
return $.get(this.opts.merge_check_url, function(data) {
|
||||
return $('.mr-state-widget').replaceWith(data);
|
||||
var $html = $(data);
|
||||
$('.mr-widget-body').replaceWith($html.find('.mr-widget-body'));
|
||||
$('.mr-widget-footer').replaceWith($html.find('.mr-widget-footer'));
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -8,31 +8,42 @@
|
|||
* temporarily.
|
||||
* */
|
||||
|
||||
if ($('.accept-mr-form').length) {
|
||||
$('.accept-mr-form').on('ajax:send', () => {
|
||||
$('.accept-mr-form :input').disable();
|
||||
});
|
||||
$(document)
|
||||
.off('ajax:send', '.accept-mr-form')
|
||||
.on('ajax:send', '.accept-mr-form', () => {
|
||||
$('.accept-mr-form :input').disable();
|
||||
});
|
||||
|
||||
$('.accept_merge_request').on('click', () => {
|
||||
$('.js-merge-button').html('<i class="fa fa-spinner fa-spin"></i> Merge in progress');
|
||||
});
|
||||
$(document)
|
||||
.off('click', '.accept_merge_request')
|
||||
.on('click', '.accept_merge_request', () => {
|
||||
$('.js-merge-button').html('<i class="fa fa-spinner fa-spin"></i> Merge in progress');
|
||||
});
|
||||
|
||||
$('.merge_when_build_succeeds').on('click', () => {
|
||||
$('#merge_when_build_succeeds').val('1');
|
||||
});
|
||||
$(document)
|
||||
.off('click', '.merge_when_build_succeeds')
|
||||
.on('click', '.merge_when_build_succeeds', () => {
|
||||
$('#merge_when_build_succeeds').val('1');
|
||||
});
|
||||
|
||||
$('.js-merge-dropdown a').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
$(this).closest('form').submit();
|
||||
});
|
||||
} else if ($('.rebase-in-progress').length) {
|
||||
$(document)
|
||||
.off('click', '.js-merge-dropdown a')
|
||||
.on('click', '.js-merge-dropdown a', (e) => {
|
||||
e.preventDefault();
|
||||
$(e.target).closest('form').submit();
|
||||
});
|
||||
if ($('.rebase-in-progress').length) {
|
||||
merge_request_widget.rebaseInProgress();
|
||||
} else if ($('.rebase-mr-form').length) {
|
||||
$('.rebase-mr-form').on('ajax:send', () => {
|
||||
$(document)
|
||||
.off('ajax:send', '.rebase-mr-form')
|
||||
.on('ajax:send', '.rebase-mr-form', () => {
|
||||
$('.rebase-mr-form :input').disable();
|
||||
});
|
||||
|
||||
$('.js-rebase-button').on('click', () => {
|
||||
$(document)
|
||||
.off('click', '.js-rebase-button')
|
||||
.on('click', '.js-rebase-button', () => {
|
||||
$('.js-rebase-button').html("<i class='fa fa-spinner fa-spin'></i> Rebase in progress");
|
||||
});
|
||||
} else {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* global Vue, Flash, gl */
|
||||
/* eslint-disable no-param-reassign, no-bitwise */
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
((gl) => {
|
||||
gl.VueStage = Vue.extend({
|
||||
|
@ -9,7 +9,20 @@
|
|||
spinner: '<span class="fa fa-spinner fa-spin"></span>',
|
||||
};
|
||||
},
|
||||
props: ['stage', 'svgs', 'match'],
|
||||
props: {
|
||||
stage: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
svgs: {
|
||||
type: DOMStringMap,
|
||||
required: true,
|
||||
},
|
||||
match: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
fetchBuilds(e) {
|
||||
const areaExpanded = e.currentTarget.attributes['aria-expanded'];
|
||||
|
@ -24,6 +37,18 @@
|
|||
return flash;
|
||||
});
|
||||
},
|
||||
keepGraph(e) {
|
||||
const { target } = e;
|
||||
|
||||
if (target.className.indexOf('js-ci-action-icon') >= 0) return null;
|
||||
|
||||
if (
|
||||
target.parentElement &&
|
||||
(target.parentElement.className.indexOf('js-ci-action-icon') >= 0)
|
||||
) return null;
|
||||
|
||||
return e.stopPropagation();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
buildsOrSpinner() {
|
||||
|
@ -64,7 +89,7 @@
|
|||
<ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
|
||||
<div class="arrow-up"></div>
|
||||
<div
|
||||
@click=''
|
||||
@click='keepGraph($event)'
|
||||
:class="dropdownClass"
|
||||
class="js-builds-dropdown-list scrollable-menu"
|
||||
v-html="buildsOrSpinner"
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
}
|
||||
|
||||
.ci-status-icon-pending,
|
||||
.ci-status-icon-failed_with_warnings,
|
||||
.ci-status-icon-success_with_warnings {
|
||||
color: $gl-warning;
|
||||
|
||||
|
|
|
@ -46,10 +46,6 @@
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.fa-clipboard {
|
||||
color: $dropdown-title-btn-color;
|
||||
}
|
||||
|
||||
.commit-info {
|
||||
&.branches {
|
||||
margin-left: 8px;
|
||||
|
|
|
@ -377,6 +377,10 @@
|
|||
display: inline-block;
|
||||
padding: 5px;
|
||||
|
||||
&:nth-of-type(7n) {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.author_link {
|
||||
display: block;
|
||||
}
|
||||
|
|
|
@ -195,10 +195,10 @@ ul.notes {
|
|||
}
|
||||
|
||||
.note-body {
|
||||
overflow: auto;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
|
||||
.note-text {
|
||||
overflow: auto;
|
||||
word-wrap: break-word;
|
||||
@include md-typography;
|
||||
// Reset ul style types since we're nested inside a ul already
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
.file-finder-input:hover,
|
||||
.issuable-search-form:hover,
|
||||
.search-text-input:hover,
|
||||
textarea:hover,
|
||||
.form-control:hover {
|
||||
border-color: lighten($dropdown-input-focus-border, 20%);
|
||||
box-shadow: 0 0 4px lighten($search-input-focus-shadow-color, 20%);
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
overflow: visible;
|
||||
}
|
||||
|
||||
&.ci-failed {
|
||||
&.ci-failed,
|
||||
&.ci-failed_with_warnings {
|
||||
color: $gl-danger;
|
||||
border-color: $gl-danger;
|
||||
|
||||
|
|
|
@ -14,12 +14,8 @@ class ConfirmationsController < Devise::ConfirmationsController
|
|||
if signed_in?(resource_name)
|
||||
after_sign_in_path_for(resource)
|
||||
else
|
||||
sign_in(resource)
|
||||
if signed_in?(resource_name)
|
||||
after_sign_in_path_for(resource)
|
||||
else
|
||||
new_session_path(resource_name)
|
||||
end
|
||||
flash[:notice] += " Please sign in."
|
||||
new_session_path(resource_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -33,6 +33,18 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
@labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute
|
||||
end
|
||||
|
||||
@users = []
|
||||
|
||||
if params[:assignee_id].present?
|
||||
assignee = User.find_by_id(params[:assignee_id])
|
||||
@users.push(assignee) if assignee
|
||||
end
|
||||
|
||||
if params[:author_id].present?
|
||||
author = User.find_by_id(params[:author_id])
|
||||
@users.push(author) if author
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.atom { render layout: false }
|
||||
|
|
|
@ -45,6 +45,8 @@ class SearchController < ApplicationController
|
|||
end
|
||||
|
||||
@search_objects = @search_results.objects(@scope, params[:page])
|
||||
|
||||
check_single_commit_result
|
||||
end
|
||||
|
||||
def autocomplete
|
||||
|
@ -59,4 +61,16 @@ class SearchController < ApplicationController
|
|||
|
||||
render json: search_autocomplete_opts(term).to_json
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_single_commit_result
|
||||
if @search_results.single_commit_result?
|
||||
only_commit = @search_results.objects('commits').first
|
||||
query = params[:search].strip.downcase
|
||||
found_by_commit_sha = Commit.valid_hash?(query) && only_commit.sha.start_with?(query)
|
||||
|
||||
redirect_to namespace_project_commit_path(@project.namespace, @project, only_commit) if found_by_commit_sha
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
module ServicesHelper
|
||||
def service_event_description(event)
|
||||
case event
|
||||
when "push"
|
||||
when "push", "push_events"
|
||||
"Event will be triggered by a push to the repository"
|
||||
when "tag_push"
|
||||
when "tag_push", "tag_push_events"
|
||||
"Event will be triggered when a new tag is pushed to the repository"
|
||||
when "note"
|
||||
when "note", "note_events"
|
||||
"Event will be triggered when someone adds a comment"
|
||||
when "issue"
|
||||
when "issue", "issue_events"
|
||||
"Event will be triggered when an issue is created/updated/closed"
|
||||
when "confidential_issue"
|
||||
when "confidential_issue", "confidential_issue_events"
|
||||
"Event will be triggered when a confidential issue is created/updated/closed"
|
||||
when "merge_request"
|
||||
when "merge_request", "merge_request_events"
|
||||
"Event will be triggered when a merge request is created/updated/merged"
|
||||
when "build"
|
||||
when "build", "build_events"
|
||||
"Event will be triggered when a build status changes"
|
||||
when "wiki_page"
|
||||
when "wiki_page", "wiki_page_events"
|
||||
"Event will be triggered when a wiki page is created/updated"
|
||||
when "commit"
|
||||
when "commit", "commit_events"
|
||||
"Event will be triggered when a commit is created/updated"
|
||||
end
|
||||
end
|
||||
|
@ -26,4 +26,6 @@ module ServicesHelper
|
|||
event = event.pluralize if %w[merge_request issue confidential_issue].include?(event)
|
||||
"#{event}_events"
|
||||
end
|
||||
|
||||
extend self
|
||||
end
|
||||
|
|
|
@ -107,15 +107,11 @@ class Notify < BaseMailer
|
|||
|
||||
def mail_thread(model, headers = {})
|
||||
add_project_headers
|
||||
add_unsubscription_headers_and_links
|
||||
|
||||
headers["X-GitLab-#{model.class.name}-ID"] = model.id
|
||||
headers['X-GitLab-Reply-Key'] = reply_key
|
||||
|
||||
if !@labels_url && @sent_notification && @sent_notification.unsubscribable?
|
||||
headers['List-Unsubscribe'] = "<#{unsubscribe_sent_notification_url(@sent_notification, force: true)}>"
|
||||
|
||||
@sent_notification_url = unsubscribe_sent_notification_url(@sent_notification)
|
||||
end
|
||||
|
||||
if Gitlab::IncomingEmail.enabled?
|
||||
address = Mail::Address.new(Gitlab::IncomingEmail.reply_address(reply_key))
|
||||
address.display_name = @project.name_with_namespace
|
||||
|
@ -171,4 +167,16 @@ class Notify < BaseMailer
|
|||
headers['X-GitLab-Project-Id'] = @project.id
|
||||
headers['X-GitLab-Project-Path'] = @project.path_with_namespace
|
||||
end
|
||||
|
||||
def add_unsubscription_headers_and_links
|
||||
return unless !@labels_url && @sent_notification && @sent_notification.unsubscribable?
|
||||
|
||||
list_unsubscribe_methods = [unsubscribe_sent_notification_url(@sent_notification, force: true)]
|
||||
if Gitlab::IncomingEmail.enabled? && Gitlab::IncomingEmail.supports_wildcard?
|
||||
list_unsubscribe_methods << "mailto:#{Gitlab::IncomingEmail.unsubscribe_address(reply_key)}"
|
||||
end
|
||||
|
||||
headers['List-Unsubscribe'] = list_unsubscribe_methods.map { |e| "<#{e}>" }.join(',')
|
||||
@sent_notification_url = unsubscribe_sent_notification_url(@sent_notification)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -128,16 +128,21 @@ module Ci
|
|||
end
|
||||
|
||||
def stages
|
||||
# TODO, this needs refactoring, see gitlab-ce#26481.
|
||||
|
||||
stages_query = statuses
|
||||
.group('stage').select(:stage).order('max(stage_idx)')
|
||||
|
||||
status_sql = statuses.latest.where('stage=sg.stage').status_sql
|
||||
|
||||
stages_query = statuses.group('stage').select(:stage)
|
||||
.order('max(stage_idx)')
|
||||
warnings_sql = statuses.latest.select('COUNT(*) > 0')
|
||||
.where('stage=sg.stage').failed_but_allowed.to_sql
|
||||
|
||||
stages_with_statuses = CommitStatus.from(stages_query, :sg).
|
||||
pluck('sg.stage', status_sql)
|
||||
stages_with_statuses = CommitStatus.from(stages_query, :sg)
|
||||
.pluck('sg.stage', status_sql, "(#{warnings_sql})")
|
||||
|
||||
stages_with_statuses.map do |stage|
|
||||
Ci::Stage.new(self, name: stage.first, status: stage.last)
|
||||
Ci::Stage.new(self, Hash[%i[name status warnings].zip(stage)])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -126,9 +126,11 @@ module Ci
|
|||
end
|
||||
|
||||
def tick_runner_queue
|
||||
new_update = SecureRandom.hex
|
||||
Gitlab::Redis.with { |redis| redis.set(runner_queue_key, new_update, ex: RUNNER_QUEUE_EXPIRY_TIME) }
|
||||
new_update
|
||||
SecureRandom.hex.tap do |new_update|
|
||||
Gitlab::Redis.with do |redis|
|
||||
redis.set(runner_queue_key, new_update, ex: RUNNER_QUEUE_EXPIRY_TIME)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def ensure_runner_queue_value
|
||||
|
|
|
@ -8,10 +8,11 @@ module Ci
|
|||
|
||||
delegate :project, to: :pipeline
|
||||
|
||||
def initialize(pipeline, name:, status: nil)
|
||||
def initialize(pipeline, name:, status: nil, warnings: nil)
|
||||
@pipeline = pipeline
|
||||
@name = name
|
||||
@status = status
|
||||
@warnings = warnings
|
||||
end
|
||||
|
||||
def to_param
|
||||
|
@ -39,5 +40,17 @@ module Ci
|
|||
def builds
|
||||
@builds ||= pipeline.builds.where(stage: name)
|
||||
end
|
||||
|
||||
def success?
|
||||
status.to_s == 'success'
|
||||
end
|
||||
|
||||
def has_warnings?
|
||||
if @warnings.nil?
|
||||
statuses.latest.failed_but_allowed.any?
|
||||
else
|
||||
@warnings
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,6 +21,9 @@ class Commit
|
|||
DIFF_HARD_LIMIT_FILES = 1000
|
||||
DIFF_HARD_LIMIT_LINES = 50000
|
||||
|
||||
# The SHA can be between 7 and 40 hex characters.
|
||||
COMMIT_SHA_PATTERN = '\h{7,40}'
|
||||
|
||||
class << self
|
||||
def decorate(commits, project)
|
||||
commits.map do |commit|
|
||||
|
@ -52,6 +55,10 @@ class Commit
|
|||
def from_hash(hash, project)
|
||||
new(Gitlab::Git::Commit.new(hash), project)
|
||||
end
|
||||
|
||||
def valid_hash?(key)
|
||||
!!(/\A#{COMMIT_SHA_PATTERN}\z/ =~ key)
|
||||
end
|
||||
end
|
||||
|
||||
attr_accessor :raw
|
||||
|
@ -77,8 +84,6 @@ class Commit
|
|||
|
||||
# Pattern used to extract commit references from text
|
||||
#
|
||||
# The SHA can be between 7 and 40 hex characters.
|
||||
#
|
||||
# This pattern supports cross-project references.
|
||||
def self.reference_pattern
|
||||
@reference_pattern ||= %r{
|
||||
|
@ -88,7 +93,7 @@ class Commit
|
|||
end
|
||||
|
||||
def self.link_reference_pattern
|
||||
@link_reference_pattern ||= super("commit", /(?<commit>\h{7,40})/)
|
||||
@link_reference_pattern ||= super("commit", /(?<commit>#{COMMIT_SHA_PATTERN})/)
|
||||
end
|
||||
|
||||
def to_reference(from_project = nil, full: false)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
module HasStatus
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
DEFAULT_STATUS = 'created'
|
||||
AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped]
|
||||
STARTED_STATUSES = %w[running success failed skipped]
|
||||
ACTIVE_STATUSES = %w[pending running]
|
||||
|
|
|
@ -11,7 +11,7 @@ module Taskable
|
|||
INCOMPLETE = 'incomplete'.freeze
|
||||
ITEM_PATTERN = /
|
||||
^
|
||||
(?:\s*[-+*]|(?:\d+\.))? # optional list prefix
|
||||
\s*(?:[-+*]|(?:\d+\.))? # optional list prefix
|
||||
\s* # optional whitespace prefix
|
||||
(\[\s\]|\[[xX]\]) # checkbox
|
||||
(\s.+) # followed by whitespace and some text.
|
||||
|
|
|
@ -4,6 +4,8 @@ class Key < ActiveRecord::Base
|
|||
include AfterCommitQueue
|
||||
include Sortable
|
||||
|
||||
LAST_USED_AT_REFRESH_TIME = 1.day.to_i
|
||||
|
||||
belongs_to :user
|
||||
|
||||
before_validation :generate_fingerprint
|
||||
|
@ -50,7 +52,10 @@ class Key < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def update_last_used_at
|
||||
UseKeyWorker.perform_async(self.id)
|
||||
lease = Gitlab::ExclusiveLease.new("key_update_last_used_at:#{id}", timeout: LAST_USED_AT_REFRESH_TIME)
|
||||
return unless lease.try_obtain
|
||||
|
||||
UseKeyWorker.perform_async(id)
|
||||
end
|
||||
|
||||
def add_to_shell
|
||||
|
|
|
@ -131,6 +131,8 @@ class Namespace < ActiveRecord::Base
|
|||
|
||||
Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
|
||||
|
||||
remove_exports!
|
||||
|
||||
# If repositories moved successfully we need to
|
||||
# send update instructions to users.
|
||||
# However we cannot allow rollback since we moved namespace dir
|
||||
|
@ -219,6 +221,8 @@ class Namespace < ActiveRecord::Base
|
|||
GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage_path, new_path)
|
||||
end
|
||||
end
|
||||
|
||||
remove_exports!
|
||||
end
|
||||
|
||||
def refresh_access_of_projects_invited_groups
|
||||
|
@ -231,4 +235,20 @@ class Namespace < ActiveRecord::Base
|
|||
def full_path_changed?
|
||||
path_changed? || parent_id_changed?
|
||||
end
|
||||
|
||||
def remove_exports!
|
||||
Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete))
|
||||
end
|
||||
|
||||
def export_path
|
||||
File.join(Gitlab::ImportExport.storage_path, full_path_was)
|
||||
end
|
||||
|
||||
def full_path_was
|
||||
if parent
|
||||
parent.full_path + '/' + path_was
|
||||
else
|
||||
path_was
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -121,8 +121,6 @@ class Project < ActiveRecord::Base
|
|||
|
||||
# Merge Requests for target project should be removed with it
|
||||
has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id'
|
||||
# Merge requests from source project should be kept when source project was removed
|
||||
has_many :fork_merge_requests, foreign_key: 'source_project_id', class_name: 'MergeRequest'
|
||||
has_many :issues, dependent: :destroy
|
||||
has_many :labels, dependent: :destroy, class_name: 'ProjectLabel'
|
||||
has_many :services, dependent: :destroy
|
||||
|
|
|
@ -25,7 +25,7 @@ You can create a Personal Access Token here:
|
|||
http://app.asana.com/-/account_api'
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'asana'
|
||||
end
|
||||
|
||||
|
@ -44,7 +44,7 @@ http://app.asana.com/-/account_api'
|
|||
]
|
||||
end
|
||||
|
||||
def supported_events
|
||||
def self.supported_events
|
||||
%w(push)
|
||||
end
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ class AssemblaService < Service
|
|||
'Project Management Software (Source Commits Endpoint)'
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'assembla'
|
||||
end
|
||||
|
||||
|
@ -23,7 +23,7 @@ class AssemblaService < Service
|
|||
]
|
||||
end
|
||||
|
||||
def supported_events
|
||||
def self.supported_events
|
||||
%w(push)
|
||||
end
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ class BambooService < CiService
|
|||
'You must set up automatic revision labeling and a repository trigger in Bamboo.'
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'bamboo'
|
||||
end
|
||||
|
||||
|
@ -56,10 +56,6 @@ class BambooService < CiService
|
|||
]
|
||||
end
|
||||
|
||||
def supported_events
|
||||
%w(push)
|
||||
end
|
||||
|
||||
def build_page(sha, ref)
|
||||
with_reactive_cache(sha, ref) {|cached| cached[:build_page] }
|
||||
end
|
||||
|
|
|
@ -19,7 +19,7 @@ class BugzillaService < IssueTrackerService
|
|||
end
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'bugzilla'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,10 +24,6 @@ class BuildkiteService < CiService
|
|||
hook.save
|
||||
end
|
||||
|
||||
def supported_events
|
||||
%w(push)
|
||||
end
|
||||
|
||||
def execute(data)
|
||||
return unless supported_events.include?(data[:object_kind])
|
||||
|
||||
|
@ -54,7 +50,7 @@ class BuildkiteService < CiService
|
|||
'Continuous integration and deployments'
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'buildkite'
|
||||
end
|
||||
|
||||
|
|
|
@ -19,11 +19,11 @@ class BuildsEmailService < Service
|
|||
'Email the builds status to a list of recipients.'
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'builds_email'
|
||||
end
|
||||
|
||||
def supported_events
|
||||
def self.supported_events
|
||||
%w(build)
|
||||
end
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ class CampfireService < Service
|
|||
'Simple web-based real-time group chat'
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'campfire'
|
||||
end
|
||||
|
||||
|
@ -24,7 +24,7 @@ class CampfireService < Service
|
|||
]
|
||||
end
|
||||
|
||||
def supported_events
|
||||
def self.supported_events
|
||||
%w(push)
|
||||
end
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ class ChatNotificationService < Service
|
|||
valid?
|
||||
end
|
||||
|
||||
def supported_events
|
||||
def self.supported_events
|
||||
%w[push issue confidential_issue merge_request note tag_push
|
||||
build pipeline wiki_page]
|
||||
end
|
||||
|
@ -82,19 +82,19 @@ class ChatNotificationService < Service
|
|||
def get_message(object_kind, data)
|
||||
case object_kind
|
||||
when "push", "tag_push"
|
||||
PushMessage.new(data)
|
||||
ChatMessage::PushMessage.new(data)
|
||||
when "issue"
|
||||
IssueMessage.new(data) unless is_update?(data)
|
||||
ChatMessage::IssueMessage.new(data) unless is_update?(data)
|
||||
when "merge_request"
|
||||
MergeMessage.new(data) unless is_update?(data)
|
||||
ChatMessage::MergeMessage.new(data) unless is_update?(data)
|
||||
when "note"
|
||||
NoteMessage.new(data)
|
||||
ChatMessage::NoteMessage.new(data)
|
||||
when "build"
|
||||
BuildMessage.new(data) if should_build_be_notified?(data)
|
||||
ChatMessage::BuildMessage.new(data) if should_build_be_notified?(data)
|
||||
when "pipeline"
|
||||
PipelineMessage.new(data) if should_pipeline_be_notified?(data)
|
||||
ChatMessage::PipelineMessage.new(data) if should_pipeline_be_notified?(data)
|
||||
when "wiki_page"
|
||||
WikiPageMessage.new(data)
|
||||
ChatMessage::WikiPageMessage.new(data)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -13,8 +13,8 @@ class ChatSlashCommandsService < Service
|
|||
ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
|
||||
end
|
||||
|
||||
def supported_events
|
||||
[]
|
||||
def self.supported_events
|
||||
%w()
|
||||
end
|
||||
|
||||
def can_test?
|
||||
|
|
|
@ -8,7 +8,7 @@ class CiService < Service
|
|||
self.respond_to?(:token) && self.token.present? && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
|
||||
end
|
||||
|
||||
def supported_events
|
||||
def self.supported_events
|
||||
%w(push)
|
||||
end
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ class CustomIssueTrackerService < IssueTrackerService
|
|||
end
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'custom_issue_tracker'
|
||||
end
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
class DeploymentService < Service
|
||||
default_value_for :category, 'deployment'
|
||||
|
||||
def supported_events
|
||||
[]
|
||||
def self.supported_events
|
||||
%w()
|
||||
end
|
||||
|
||||
def predefined_variables
|
||||
|
|
|
@ -32,7 +32,7 @@ class DroneCiService < CiService
|
|||
true
|
||||
end
|
||||
|
||||
def supported_events
|
||||
def self.supported_events
|
||||
%w(push merge_request tag_push)
|
||||
end
|
||||
|
||||
|
@ -87,7 +87,7 @@ class DroneCiService < CiService
|
|||
'Drone is a Continuous Integration platform built on Docker, written in Go'
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'drone_ci'
|
||||
end
|
||||
|
||||
|
|
|
@ -12,11 +12,11 @@ class EmailsOnPushService < Service
|
|||
'Email the commits and diff of each push to a list of recipients.'
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'emails_on_push'
|
||||
end
|
||||
|
||||
def supported_events
|
||||
def self.supported_events
|
||||
%w(push tag_push)
|
||||
end
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ class ExternalWikiService < Service
|
|||
'Replaces the link to the internal wiki with a link to an external wiki.'
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'external_wiki'
|
||||
end
|
||||
|
||||
|
@ -29,4 +29,8 @@ class ExternalWikiService < Service
|
|||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def self.supported_events
|
||||
%w()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ class FlowdockService < Service
|
|||
'Flowdock is a collaboration web app for technical teams.'
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'flowdock'
|
||||
end
|
||||
|
||||
|
@ -22,7 +22,7 @@ class FlowdockService < Service
|
|||
]
|
||||
end
|
||||
|
||||
def supported_events
|
||||
def self.supported_events
|
||||
%w(push)
|
||||
end
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ class GemnasiumService < Service
|
|||
'Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities.'
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'gemnasium'
|
||||
end
|
||||
|
||||
|
@ -23,7 +23,7 @@ class GemnasiumService < Service
|
|||
]
|
||||
end
|
||||
|
||||
def supported_events
|
||||
def self.supported_events
|
||||
%w(push)
|
||||
end
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ class GitlabIssueTrackerService < IssueTrackerService
|
|||
|
||||
default_value_for :default, true
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'gitlab'
|
||||
end
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ class HipchatService < Service
|
|||
'Private group chat and IM'
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'hipchat'
|
||||
end
|
||||
|
||||
|
@ -45,7 +45,7 @@ class HipchatService < Service
|
|||
]
|
||||
end
|
||||
|
||||
def supported_events
|
||||
def self.supported_events
|
||||
%w(push issue confidential_issue merge_request note tag_push build)
|
||||
end
|
||||
|
||||
|
|
|
@ -17,11 +17,11 @@ class IrkerService < Service
|
|||
'gateway.'
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'irker'
|
||||
end
|
||||
|
||||
def supported_events
|
||||
def self.supported_events
|
||||
%w(push)
|
||||
end
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ class IssueTrackerService < Service
|
|||
end
|
||||
end
|
||||
|
||||
def supported_events
|
||||
def self.supported_events
|
||||
%w(push)
|
||||
end
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ class JiraService < IssueTrackerService
|
|||
# This is confusing, but JiraService does not really support these events.
|
||||
# The values here are required to display correct options in the service
|
||||
# configuration screen.
|
||||
def supported_events
|
||||
def self.supported_events
|
||||
%w(commit merge_request)
|
||||
end
|
||||
|
||||
|
@ -81,7 +81,7 @@ class JiraService < IssueTrackerService
|
|||
end
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'jira'
|
||||
end
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ class KubernetesService < DeploymentService
|
|||
'deployments with `app=$CI_ENVIRONMENT_SLUG`'
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'kubernetes'
|
||||
end
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ class MattermostService < ChatNotificationService
|
|||
'Receive event notifications in Mattermost'
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'mattermost'
|
||||
end
|
||||
|
||||
|
@ -36,6 +36,6 @@ class MattermostService < ChatNotificationService
|
|||
end
|
||||
|
||||
def default_channel_placeholder
|
||||
"#town-square"
|
||||
"town-square"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,7 +15,7 @@ class MattermostSlashCommandsService < ChatSlashCommandsService
|
|||
"Perform common operations on GitLab in Mattermost"
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'mattermost_slash_commands'
|
||||
end
|
||||
|
||||
|
|
|
@ -15,11 +15,11 @@ class PipelinesEmailService < Service
|
|||
'Email the pipelines status to a list of recipients.'
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'pipelines_email'
|
||||
end
|
||||
|
||||
def supported_events
|
||||
def self.supported_events
|
||||
%w[pipeline]
|
||||
end
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ class PivotaltrackerService < Service
|
|||
'Project Management Software (Source Commits Endpoint)'
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'pivotaltracker'
|
||||
end
|
||||
|
||||
|
@ -34,7 +34,7 @@ class PivotaltrackerService < Service
|
|||
]
|
||||
end
|
||||
|
||||
def supported_events
|
||||
def self.supported_events
|
||||
%w(push)
|
||||
end
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ class PushoverService < Service
|
|||
'Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop.'
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'pushover'
|
||||
end
|
||||
|
||||
|
@ -61,7 +61,7 @@ class PushoverService < Service
|
|||
]
|
||||
end
|
||||
|
||||
def supported_events
|
||||
def self.supported_events
|
||||
%w(push)
|
||||
end
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ class RedmineService < IssueTrackerService
|
|||
end
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'redmine'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ class SlackService < ChatNotificationService
|
|||
'Receive event notifications in Slack'
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'slack'
|
||||
end
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ class SlackSlashCommandsService < ChatSlashCommandsService
|
|||
"Perform common operations on GitLab in Slack"
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'slack_slash_commands'
|
||||
end
|
||||
|
||||
|
|
|
@ -43,14 +43,10 @@ class TeamcityService < CiService
|
|||
'requests build, that setting is in the vsc root advanced settings.'
|
||||
end
|
||||
|
||||
def to_param
|
||||
def self.to_param
|
||||
'teamcity'
|
||||
end
|
||||
|
||||
def supported_events
|
||||
%w(push)
|
||||
end
|
||||
|
||||
def fields
|
||||
[
|
||||
{ type: 'text', name: 'teamcity_url',
|
||||
|
|
|
@ -76,6 +76,11 @@ class Service < ActiveRecord::Base
|
|||
|
||||
def to_param
|
||||
# implement inside child
|
||||
self.class.to_param
|
||||
end
|
||||
|
||||
def self.to_param
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def fields
|
||||
|
@ -92,7 +97,11 @@ class Service < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def event_names
|
||||
supported_events.map { |event| "#{event}_events" }
|
||||
self.class.event_names
|
||||
end
|
||||
|
||||
def self.event_names
|
||||
self.supported_events.map { |event| "#{event}_events" }
|
||||
end
|
||||
|
||||
def event_field(event)
|
||||
|
@ -104,6 +113,10 @@ class Service < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def supported_events
|
||||
self.class.supported_events
|
||||
end
|
||||
|
||||
def self.supported_events
|
||||
%w(push tag_push issue confidential_issue merge_request wiki_page)
|
||||
end
|
||||
|
||||
|
|
|
@ -40,10 +40,12 @@ class PipelineEntity < Grape::Entity
|
|||
end
|
||||
|
||||
expose :path do |pipeline|
|
||||
namespace_project_tree_path(
|
||||
pipeline.project.namespace,
|
||||
pipeline.project,
|
||||
id: pipeline.ref)
|
||||
if pipeline.ref
|
||||
namespace_project_tree_path(
|
||||
pipeline.project.namespace,
|
||||
pipeline.project,
|
||||
id: pipeline.ref)
|
||||
end
|
||||
end
|
||||
|
||||
expose :tag?, as: :tag
|
||||
|
|
|
@ -6,6 +6,14 @@ module Ci
|
|||
runner.tick_runner_queue
|
||||
end
|
||||
end
|
||||
|
||||
return unless build.project.shared_runners_enabled?
|
||||
|
||||
Ci::Runner.shared.each do |runner|
|
||||
if runner.can_pick?(build)
|
||||
runner.tick_runner_queue
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -38,15 +38,13 @@ module MergeRequests
|
|||
|
||||
private
|
||||
|
||||
def merge_requests_for(branch)
|
||||
origin_merge_requests = @project.origin_merge_requests
|
||||
.opened.where(source_branch: branch).to_a
|
||||
|
||||
fork_merge_requests = @project.fork_merge_requests
|
||||
.opened.where(source_branch: branch).to_a
|
||||
|
||||
(origin_merge_requests + fork_merge_requests)
|
||||
.uniq.select(&:source_project)
|
||||
# Returns all origin and fork merge requests from `@project` satisfying passed arguments.
|
||||
def merge_requests_for(source_branch, mr_states: [:opened])
|
||||
MergeRequest
|
||||
.with_state(mr_states)
|
||||
.where(source_branch: source_branch, source_project_id: @project.id)
|
||||
.preload(:source_project) # we don't need a #includes since we're just preloading for the #select
|
||||
.select(&:source_project)
|
||||
end
|
||||
|
||||
def pipeline_merge_requests(pipeline)
|
||||
|
|
|
@ -42,7 +42,7 @@ module MergeRequests
|
|||
commit_ids.include?(merge_request.diff_head_sha)
|
||||
end
|
||||
|
||||
merge_requests.uniq.select(&:source_project).each do |merge_request|
|
||||
filter_merge_requests(merge_requests).each do |merge_request|
|
||||
MergeRequests::PostMergeService.
|
||||
new(merge_request.target_project, @current_user).
|
||||
execute(merge_request)
|
||||
|
@ -58,10 +58,13 @@ module MergeRequests
|
|||
def reload_merge_requests
|
||||
merge_requests = @project.merge_requests.opened.
|
||||
by_source_or_target_branch(@branch_name).to_a
|
||||
merge_requests += fork_merge_requests
|
||||
merge_requests = filter_merge_requests(merge_requests)
|
||||
|
||||
merge_requests.each do |merge_request|
|
||||
# Fork merge requests
|
||||
merge_requests += MergeRequest.opened
|
||||
.where(source_branch: @branch_name, source_project: @project)
|
||||
.where.not(target_project: @project).to_a
|
||||
|
||||
filter_merge_requests(merge_requests).each do |merge_request|
|
||||
if merge_request.source_branch == @branch_name || force_push?
|
||||
merge_request.reload_diff
|
||||
else
|
||||
|
@ -175,16 +178,7 @@ module MergeRequests
|
|||
end
|
||||
|
||||
def merge_requests_for_source_branch
|
||||
@source_merge_requests ||= begin
|
||||
merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a
|
||||
merge_requests += fork_merge_requests
|
||||
filter_merge_requests(merge_requests)
|
||||
end
|
||||
end
|
||||
|
||||
def fork_merge_requests
|
||||
@fork_merge_requests ||= @project.fork_merge_requests.opened.
|
||||
where(source_branch: @branch_name).to_a
|
||||
@source_merge_requests ||= merge_requests_for(@branch_name)
|
||||
end
|
||||
|
||||
def branch_added?
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
- content_for :page_specific_javascripts do
|
||||
= page_specific_javascript_tag('merge_request_widget/ci_bundle.js')
|
||||
|
||||
%h4
|
||||
Set by #{link_to_member(@project, @merge_request.merge_user, avatar: true)}
|
||||
to be merged automatically when the pipeline succeeds.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
%button.choose-btn.btn.btn-sm.js-choose-group-avatar-button
|
||||
%button.choose-btn.btn.btn-sm.js-choose-group-avatar-button{ type: 'button' }
|
||||
%i.fa.fa-paperclip
|
||||
%span Choose File ...
|
||||
|
||||
|
|
|
@ -11,13 +11,13 @@
|
|||
class: "check_all_issues left"
|
||||
.issues-other-filters.filtered-search-container
|
||||
.filtered-search-input-container
|
||||
%input.form-control.filtered-search{ placeholder: 'Search or filter results...', 'data-id' => 'filtered-search', 'data-project-id' => @project.id }
|
||||
%input.form-control.filtered-search{ placeholder: 'Search or filter results...', 'data-id' => 'filtered-search', 'data-project-id' => @project.id, 'data-username-params' => @users.to_json(only: [:id, :username]) }
|
||||
= icon('filter')
|
||||
%button.clear-search.hidden{ type: 'button' }
|
||||
= icon('times')
|
||||
#js-dropdown-hint.dropdown-menu.hint-dropdown
|
||||
%ul{ 'data-dropdown' => true }
|
||||
%li.filter-dropdown-item{ 'data-value' => '' }
|
||||
%li.filter-dropdown-item{ 'data-action' => 'submit' }
|
||||
%button.btn.btn-link
|
||||
= icon('search')
|
||||
%span
|
||||
|
@ -47,6 +47,10 @@
|
|||
%li.filter-dropdown-item{ 'data-value' => 'none' }
|
||||
%button.btn.btn-link
|
||||
No Assignee
|
||||
- if current_user
|
||||
%li.filter-dropdown-item{ 'data-value' => current_user.to_reference }
|
||||
%button.btn.btn-link
|
||||
Assigned to me
|
||||
%li.divider
|
||||
%ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true }
|
||||
%li.filter-dropdown-item
|
||||
|
@ -121,7 +125,13 @@
|
|||
new MilestoneSelect();
|
||||
new IssueStatusSelect();
|
||||
new SubscriptionSelect();
|
||||
$('form.filter-form').on('submit', function (event) {
|
||||
event.preventDefault();
|
||||
Turbolinks.visit(this.action + '&' + $(this).serialize());
|
||||
|
||||
$(document).off('page:restore').on('page:restore', function (event) {
|
||||
if (gl.FilteredSearchManager) {
|
||||
new gl.FilteredSearchManager();
|
||||
}
|
||||
Issuable.init();
|
||||
new gl.IssuableBulkActions({
|
||||
prefixId: 'issue_',
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
title: Go to a project order
|
||||
merge_request: 7737
|
||||
author: Jacopo Beschi @jacopo-beschi
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
title: Fix double spaced CI log
|
||||
merge_request: 8349
|
||||
author: Jared Deckard <jared.deckard@gmail.com>
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
title: Allow group and project paths when transferring projects via the API
|
||||
merge_request:
|
||||
author:
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
title: Prevent empty pagination when list is not empty
|
||||
merge_request: 8172
|
||||
author:
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
title: Improve visibility of "Resolve conflicts" and "Merge locally" actions
|
||||
merge_request: 8229
|
||||
author:
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
title: Reduce DB-load for build-queues by storing last_update in Redis
|
||||
merge_request: 8084
|
||||
author:
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
title: Remove Lock Icon on Protected Tag
|
||||
merge_request: 8513
|
||||
author: Sergey Nikitin
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Handle unsubscribe from email notifications via replying to reply+%{key}+unsubscribe@ address
|
||||
merge_request: 6597
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Allow creating protected branches when user can merge to such branch
|
||||
merge_request: 8458
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Adds service trigger events to api
|
||||
merge_request: 8324
|
||||
author:
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
title: Updated project visibility settings UX
|
||||
merge_request: 7645
|
||||
author:
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
title: Treat environments matching `production/*` as Production
|
||||
merge_request: 8500
|
||||
author:
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
title: Query external CI statuses in the background
|
||||
merge_request:
|
||||
author:
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
title: fix button layout issue on branches page
|
||||
merge_request: 8074
|
||||
author:
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: 'Allows to search within project by commit hash'
|
||||
merge_request:
|
||||
author: YarNayar
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
title: ensure permalinks scroll to correct position on multiple clicks
|
||||
merge_request: 8046
|
||||
author:
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
title: Support slash comand `/merge` for merging merge requests.
|
||||
merge_request: 7746
|
||||
author: Jarka Kadlecova
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Fix nested tasks in ordered list
|
||||
merge_request: 8626
|
||||
author:
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
title: Added number_with_delimiter to counter on milestone panels
|
||||
merge_request:
|
||||
author: Ryan Harris
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
title: Adds label to Environments "Date Created"
|
||||
merge_request: 8376
|
||||
author: Saad Shahd
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue