Commit 616af037 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into artifacts-from-ref-and-build-name

* upstream/master: (192 commits)
  Added CHANGELOG
  Added unfold test to parallel and added 'diff discussion' context
  Fix Spinach branches spec
  Better first match on this MR also
  Change merge_error column from string to text type
  Fix typo in gitlab_flow.md
  entities: make Environment inherit EnvironmentBasic
  Updated to optimized specs from !5864
  Added 'with an unfolded line should not allow commenting' scenario (line 125)
  Added addtional 'renderable' validator to check 'data-note-type' attr exists
  Allow passing an index to selectRowAtIndex
  Fixed enter key in search input not working
  Fix file links on project page Files view
  Fix incorrect "stopped impersonation" log message
  8.11 is released, long live 8.12
  Also check if Akismet is enabled, before showing the `Submit as spam` button.
  fix location of markdown help location
  Fix for update 8.10-to-8.11.md doc.
  Appease the linter.
  Add Ruby 2.3 upgrade notes.
  ...
parents 8f197315 02591b04
Please view this file on the master branch, on stable branches it's out of date.
v 8.11.0 (unreleased)
v 8.12.0 (unreleased)
- Change merge_error column from string to text type
- Optimistic locking for Issues and Merge Requests (title and description overriding prevention)
- Added tests for diff notes
v 8.11.1 (unreleased)
- Fix file links on project page when default view is Files !5933
v 8.11.0
- Use test coverage value from the latest successful pipeline in badge. !5862
- Add test coverage report badge. !5708
- Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar)
- Add Koding (online IDE) integration
- Ability to specify branches for Pivotal Tracker integration (Egor Lynko)
- Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres)
- Add delimiter to project stars and forks count (ClemMakesApps)
- Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres)
- Fix adding line comments on the initial commit to a repo !5900
- Fix the title of the toggle dropdown button. !5515 (herminiotorres)
- Rename `markdown_preview` routes to `preview_markdown`. (Christopher Bartz)
- Update to Ruby 2.3.1. !4948
......@@ -18,13 +29,17 @@ v 8.11.0 (unreleased)
- API: Endpoints for enabling and disabling deploy keys
- API: List access requests, request access, approve, and deny access requests to a project or a group. !4833
- Use long options for curl examples in documentation !5703 (winniehell)
- Added tooltip listing label names to the labels value in the collapsed issuable sidebar
- Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell)
- Fix badge count alignment (ClemMakesApps)
- GitLab Performance Monitoring can now track custom events such as the number of tags pushed to a repository
- Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell)
- Allow naming U2F devices !5833
- Ignore URLs starting with // in Markdown links !5677 (winniehell)
- Fix CI status icon link underline (ClemMakesApps)
- The Repository class is now instrumented
- Fix commit mention font inconsistency (ClemMakesApps)
- Do not escape URI when extracting path !5878 (winniehell)
- Fix filter label tooltip HTML rendering (ClemMakesApps)
- Cache the commit author in RequestStore to avoid extra lookups in PostReceive
- Add a button to download latest successful artifacts for branches and tags
......@@ -35,9 +50,11 @@ v 8.11.0 (unreleased)
- API: Add deployment endpoints
- API: Add Play endpoint on Builds
- Fix of 'Commits being passed to custom hooks are already reachable when using the UI'
- Show wall clock time when showing a pipeline. !5734
- Show member roles to all users on members page
- Project.visible_to_user is instrumented again
- Fix awardable button mutuality loading spinners (ClemMakesApps)
- Sort todos by date and priority
- Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable
- Optimize maximum user access level lookup in loading of notes
- Send notification emails to users newly mentioned in issue and MR edits !5800
......@@ -56,6 +73,7 @@ v 8.11.0 (unreleased)
- Enforce 2FA restrictions on API authentication endpoints !5820
- Limit git rev-list output count to one in forced push check
- Show deployment status on merge requests with external URLs
- Fix branch title trailing space on hover (ClemMakesApps)
- Clean up unused routes (Josef Strzibny)
- Fix issue on empty project to allow developers to only push to protected branches if given permission
- API: Add enpoints for pipelines
......@@ -72,6 +90,7 @@ v 8.11.0 (unreleased)
- Fix devise deprecation warnings.
- Check for 2FA when using Git over HTTP and only allow PersonalAccessTokens as password in that case !5764
- Update version_sorter and use new interface for faster tag sorting
- Load branches asynchronously in Cherry Pick and Revert dialogs.
- Optimize checking if a user has read access to a list of issues !5370
- Store all DB secrets in secrets.yml, under descriptive names !5274
- Fix syntax highlighting in file editor
......@@ -80,7 +99,6 @@ v 8.11.0 (unreleased)
- Add archived badge to project list !5798
- Add simple identifier to public SSH keys (muteor)
- Admin page now references docs instead of a specific file !5600 (AnAverageHuman)
- Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363
- Fix filter input alignment (ClemMakesApps)
- Include old revision in merge request update hooks (Ben Boeckel)
- Add build event color in HipChat messages (David Eisner)
......@@ -106,12 +124,14 @@ v 8.11.0 (unreleased)
- Fix search for notes which belongs to deleted objects
- Allow Akismet to be trained by submitting issues as spam or ham !5538
- Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska)
- Fix spacing and vertical alignment on build status icon on commits page (ClemMakesApps)
- Allow branch names ending with .json for graph and network page !5579 (winniehell)
- Add the `sprockets-es6` gem
- Improve OAuth2 client documentation (muteor)
- Fix diff comments inverted toggle bug (ClemMakesApps)
- Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska)
- Profile requests when a header is passed
- Fix button missing type (ClemMakesApps)
- Avoid calculation of line_code and position for _line partial when showing diff notes on discussion tab.
- Speedup DiffNote#active? on discussions, preloading noteables and avoid touching git repository to return diff_refs when possible
- Add commit stats in commit api. !5517 (dixpac)
......@@ -120,14 +140,17 @@ v 8.11.0 (unreleased)
- edit_blob_link will use blob passed onto the options parameter
- Make error pages responsive (Takuya Noguchi)
- The performance of the project dropdown used for moving issues has been improved
- Move to project dropdown with infinite scroll for better performance
- Fix skip_repo parameter being ignored when destroying a namespace
- Add all builds into stage/job dropdowns on builds page
- Change requests_profiles resource constraint to catch virtually any file
- Bump gitlab_git to lazy load compare commits
- Reduce number of queries made for merge_requests/:id/diffs
- Add the option to set the expiration date for the project membership when giving a user access to a project. !5599 (Adam Niedzielski)
- Sensible state specific default sort order for issues and merge requests !5453 (tomb0y)
- Fix bug where destroying a namespace would not always destroy projects
- Fix RequestProfiler::Middleware error when code is reloaded in development
- Allow horizontal scrolling of code blocks in issue body
- Catch what warden might throw when profiling requests to re-throw it
- Avoid commit lookup on diff_helper passing existing local variable to the helper method
- Add description to new_issue email and new_merge_request_email in text/plain content type. !5663 (dixpac)
......@@ -142,6 +165,7 @@ v 8.11.0 (unreleased)
- Each `File::exists?` replaced to `File::exist?` because of deprecate since ruby version 2.2.0
- Add auto-completition in pipeline (Katarzyna Kobierska Ula Budziszewska)
- Add pipelines tab to merge requests
- Fix notification_service argument error of declined invitation emails
- Fix a memory leak caused by Banzai::Filter::SanitizationFilter
- Speed up todos queries by limiting the projects set we join with
- Ensure file editing in UI does not overwrite commited changes without warning user
......@@ -149,6 +173,10 @@ v 8.11.0 (unreleased)
- Update gitlab_git gem to 10.4.7
- Simplify SQL queries of marking a todo as done
v 8.10.7
- Upgrade Hamlit to 2.6.1. !5873
- Upgrade Doorkeeper to 4.2.0. !5881
v 8.10.6
- Upgrade Rails to 4.2.7.1 for security fixes. !5781
- Restore "Largest repository" sort option on Admin > Projects page. !5797
......@@ -177,8 +205,6 @@ v 8.10.3
- Trim extra displayed carriage returns in diffs and files with CRLFs. !5588
- Fix label already exist error message in the right sidebar.
v 8.10.3 (unreleased)
v 8.10.2
- User can now search branches by name. !5144
- Page is now properly rendered after committing the first file and creating the first branch. !5399
......@@ -372,6 +398,9 @@ v 8.10.0
- Fix migration corrupting import data for old version upgrades
- Show tooltip on GitLab export link in new project page
v 8.9.8
- Upgrade Doorkeeper to 4.2.0. !5881
v 8.9.7
- Upgrade Rails to 4.2.7.1 for security fixes. !5781
- Require administrator privileges to perform a project import.
......@@ -641,6 +670,9 @@ v 8.9.0
- Add tooltip to pin/unpin navbar
- Add new sub nav style to Wiki and Graphs sub navigation
v 8.8.9
- Upgrade Doorkeeper to 4.2.0. !5881
v 8.8.8
- Upgrade Rails to 4.2.7.1 for security fixes. !5781
......
......@@ -387,7 +387,8 @@ description area. Copy-paste it to retain the markdown format.
1. The change is as small as possible
1. Include proper tests and make all tests pass (unless it contains a test
exposing a bug in existing code)
exposing a bug in existing code). Every new class should have corresponding
unit tests, even if the class is exercised at a higher level, such as a feature test.
1. If you suspect a failing CI build is unrelated to your contribution, you may
try and restart the failing CI job or ask a developer to fix the
aforementioned failing test
......
8.11.0-pre
8.12.0-pre
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 14">
<g fill="#d6d7d9">
<path d="M8.7 0L5.3.3l3.2 6.8-3.2 6.6 3.5.3L12 6.9z"/>
<ellipse cx="1.7" cy="11.1" rx="1.7" ry="1.7"/>
<ellipse cx="1.7" cy="5.6" rx="1.7" ry="1.7"/>
</g>
</svg>
\ No newline at end of file
......@@ -26,8 +26,6 @@
/*= require bootstrap/tooltip */
/*= require bootstrap/popover */
/*= require select2 */
/*= require ace-rails-ap */
/*= require ace/ext-searchbox */
/*= require underscore */
/*= require dropzone */
/*= require mousetrap */
......@@ -153,7 +151,9 @@
});
});
$('.remove-row').bind('ajax:success', function() {
return $(this).closest('li').fadeOut();
$(this).tooltip('destroy')
.closest('li')
.fadeOut();
});
$('.js-remove-tr').bind('ajax:before', function() {
return $(this).hide();
......
/*= require_tree . */
(function() {
$(function() {
var url = $(".js-edit-blob-form").data("relative-url-root");
url += $(".js-edit-blob-form").data("assets-prefix");
var blob = new EditBlob(url, $('.js-edit-blob-form').data('blob-language'));
new NewCommitForm($('.js-edit-blob-form'));
});
}).call(this);
......@@ -38,7 +38,7 @@ $(() => {
ready () {
Store.disabled = this.disabled;
gl.boardService.all()
.then((resp) => {
.then((resp) => {
resp.json().forEach((board) => {
const list = Store.addList(board);
......
......@@ -55,7 +55,7 @@
draggable: '.is-draggable',
handle: '.js-board-handle',
onEnd: (e) => {
document.body.classList.remove('is-dragging');
gl.issueBoards.onEnd();
if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
const order = this.sortable.toArray(),
......@@ -72,10 +72,6 @@
}
});
if (bp.getBreakpointSize() === 'xs') {
options.handle = '.js-board-drag-handle';
}
this.sortable = Sortable.create(this.$el.parentNode, options);
},
beforeDestroy () {
......
......@@ -63,6 +63,8 @@
Store.moving.issue = card.issue;
Store.moving.list = card.list;
gl.issueBoards.onStart();
},
onAdd: (e) => {
gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue);
......@@ -72,10 +74,6 @@
}
});
if (bp.getBreakpointSize() === 'xs') {
options.handle = '.js-card-drag-handle';
}
this.sortable = Sortable.create(this.$els.list, options);
// Scroll event on list to load more
......
......@@ -2,6 +2,19 @@
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.onStart = () => {
$('.has-tooltip').tooltip('hide')
.tooltip('disable');
document.body.classList.add('is-dragging');
};
gl.issueBoards.onEnd = () => {
$('.has-tooltip').tooltip('enable');
document.body.classList.remove('is-dragging');
};
gl.issueBoards.touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
gl.issueBoards.getBoardSortableDefaultOptions = (obj) => {
let defaultSortOptions = {
forceFallback: true,
......@@ -9,14 +22,11 @@
fallbackOnBody: true,
ghostClass: 'is-ghost',
filter: '.has-tooltip',
scrollSensitivity: 100,
delay: gl.issueBoards.touchEnabled ? 100 : 0,
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
scrollSpeed: 20,
onStart () {
document.body.classList.add('is-dragging');
},
onEnd () {
document.body.classList.remove('is-dragging');
}
onStart: gl.issueBoards.onStart,
onEnd: gl.issueBoards.onEnd
}
Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
......
......@@ -3,6 +3,7 @@ class ListLabel {
this.id = obj.id;
this.title = obj.title;
this.color = obj.color;
this.textColor = obj.text_color;
this.description = obj.description;
this.priority = (obj.priority !== null) ? obj.priority : Infinity;
}
......
File mode changed from 100755 to 100644
......@@ -20,6 +20,9 @@
path = page.split(':');
shortcut_handler = null;
switch (page) {
case 'projects:boards:show':
shortcut_handler = new ShortcutsNavigation();
break;
case 'projects:issues:index':
Issuable.init();
new IssuableBulkActions();
......@@ -126,10 +129,12 @@
new NotificationsDropdown();
break;
case 'groups:group_members:index':
new gl.MemberExpirationDate();
new GroupMembers();
new UsersSelect();
break;
case 'projects:project_members:index':
new gl.MemberExpirationDate();
new ProjectMembers();
new UsersSelect();
break;
......@@ -171,6 +176,7 @@
new BuildArtifacts();
break;
case 'projects:group_links:index':
new gl.MemberExpirationDate();
new GroupsSelect();
break;
case 'search:show':
......
......@@ -39,12 +39,13 @@
FilesCommentButton.prototype.render = function(e) {
var $currentTarget, buttonParentElement, lineContentElement, textFileElement;
$currentTarget = $(e.currentTarget);
buttonParentElement = this.getButtonParent($currentTarget);
if (!this.shouldRender(e, buttonParentElement)) {
return;
}
textFileElement = this.getTextFileElement($currentTarget);
if (!this.validateButtonParent(buttonParentElement)) return;
lineContentElement = this.getLineContent($currentTarget);
if (!this.validateLineContent(lineContentElement)) return;
textFileElement = this.getTextFileElement($currentTarget);
buttonParentElement.append(this.buildButton({
noteableType: textFileElement.attr('data-noteable-type'),
noteableID: textFileElement.attr('data-noteable-id'),
......@@ -119,10 +120,14 @@
return newButtonParent.is(this.getButtonParent($(e.currentTarget)));
};
FilesCommentButton.prototype.shouldRender = function(e, buttonParentElement) {
FilesCommentButton.prototype.validateButtonParent = function(buttonParentElement) {
return !buttonParentElement.hasClass(EMPTY_CELL_CLASS) && !buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS) && $(COMMENT_BUTTON_CLASS, buttonParentElement).length === 0;
};
FilesCommentButton.prototype.validateLineContent = function(lineContentElement) {
return lineContentElement.attr('data-discussion-id') && lineContentElement.attr('data-discussion-id') !== '';
};
return FilesCommentButton;
})();
......
This diff is collapsed.
......@@ -102,20 +102,34 @@
};
IssuableForm.prototype.initMoveDropdown = function() {
var $moveDropdown;
var $moveDropdown, pageSize;
$moveDropdown = $('.js-move-dropdown');
if ($moveDropdown.length) {
pageSize = $moveDropdown.data('page-size');
return $('.js-move-dropdown').select2({
ajax: {
url: $moveDropdown.data('projects-url'),
results: function(data) {
quietMillis: 125,
data: function(term, page, context) {
return {
results: data
search: term,
offset_id: context
};
},
data: function(query) {
results: function(data) {
var context,
more;
if (data.length >= pageSize)
more = true;
if (data[data.length - 1])
context = data[data.length - 1].id;
return {
search: query
results: data,
more: more,
context: context
};
}
},
......
......@@ -4,7 +4,7 @@
var _this;
_this = this;
$('.js-label-select').each(function(i, dropdown) {
var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo;
var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip;
$dropdown = $(dropdown);
projectId = $dropdown.data('project-id');
labelUrl = $dropdown.data('labels');
......@@ -21,6 +21,7 @@
$block = $selectbox.closest('.block');
$form = $dropdown.closest('form');
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span');
$sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip');
$value = $block.find('.value');
$loading = $block.find('.block-loading').fadeOut();
if (issueUpdateURL != null) {
......@@ -31,7 +32,11 @@
labelNoneHTMLTemplate = '<span class="no-value">None</span>';
}
new gl.CreateLabelDropdown($dropdown.closest('.dropdown').find('.dropdown-new-label'), projectId);
$sidebarLabelTooltip.tooltip();
if ($dropdown.closest('.dropdown').find('.dropdown-new-label').length) {
new gl.CreateLabelDropdown($dropdown.closest('.dropdown').find('.dropdown-new-label'), projectId);
}
saveLabelData = function() {
var data, selected;
......@@ -52,7 +57,7 @@
dataType: 'JSON',
data: data
}).done(function(data) {
var labelCount, template;
var labelCount, template, labelTooltipTitle, labelTitles;
$loading.fadeOut();
$dropdown.trigger('loaded.gl.dropdown');
$selectbox.hide();
......@@ -66,6 +71,27 @@
}
$value.removeAttr('style').html(template);
$sidebarCollapsedValue.text(labelCount);
if (data.labels.length) {
labelTitles = data.labels.map(function(label) {
return label.title;
});
if (labelTitles.length > 5) {
labelTitles = labelTitles.slice(0, 5);
labelTitles.push('and ' + (data.labels.length - 5) + ' more');
}
labelTooltipTitle = labelTitles.join(', ');
} else {
labelTooltipTitle = '';
$sidebarLabelTooltip.tooltip('destroy');
}
$sidebarLabelTooltip
.attr('title', labelTooltipTitle)
.tooltip('fixTitle');
$('.has-tooltip', $value).tooltip({
container: 'body'
});
......
/*= require ace-rails-ap */
/*= require ace/ext-searchbox */
(function() {
// Add datepickers to all `js-access-expiration-date` elements. If those elements are
// children of an element with the `clearable-input` class, and have a sibling
// `js-clear-input` element, then show that element when there is a value in the
// datepicker, and make clicking on that element clear the field.
//
gl.MemberExpirationDate = function() {
function toggleClearInput() {
$(this).closest('.clearable-input').toggleClass('has-value', $(this).val() !== '');
}
var inputs = $('.js-access-expiration-date');
inputs.datepicker({
dateFormat: 'yy-mm-dd',
minDate: 1,
onSelect: toggleClearInput
});
inputs.next('.js-clear-input').on('click', function(event) {
event.preventDefault();
var input = $(this).closest('.clearable-input').find('.js-access-expiration-date');
input.datepicker('setDate', null);
toggleClearInput.call(input);
});
inputs.on('blur', toggleClearInput);
inputs.each(toggleClearInput);
};
}).call(this);
......@@ -65,7 +65,8 @@
url: $dropdown.data('refs-url'),
data: {
ref: $dropdown.data('ref')
}
},
dataType: "json"
}).done(function(refs) {
return callback(refs);
});
......@@ -73,7 +74,7 @@
selectable: true,
filterable: true,
filterByText: true,
fieldName: 'ref',
fieldName: $dropdown.data('field-name'),
renderRow: function(ref) {
var link;
if (ref.header != null) {
......
......@@ -5,9 +5,6 @@
return $(this).fadeOut();
});
}
return ProjectMembers;
})();
}).call(this);
......@@ -10,8 +10,12 @@
selectable: true,
inputId: $dropdown.data('input-id'),
fieldName: $dropdown.data('field-name'),
toggleLabel(item) {
return item.text;
toggleLabel(item, el) {
if (el.is('.is-active')) {
return item.text;
} else {
return 'Select';
}
},
clicked(item, $el, e) {
e.preventDefault();
......
......@@ -47,9 +47,7 @@
const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]');
const $allowedToPushInput = this.$wrap.find('input[name="protected_branch[push_access_levels_attributes][0][access_level]"]');
if ($branchInput.val() && $allowedToMergeInput.val() && $allowedToPushInput.val()){
this.$form.find('input[type="submit"]').removeAttr('disabled');
}
this.$form.find('input[type="submit"]').attr('disabled', !($branchInput.val() && $allowedToMergeInput.length && $allowedToPushInput.length));
}
}
......
......@@ -31,6 +31,9 @@
const $allowedToMergeInput = this.$wrap.find(`input[name="${this.$allowedToMergeDropdown.data('fieldName')}"]`);
const $allowedToPushInput = this.$wrap.find(`input[name="${this.$allowedToPushDropdown.data('fieldName')}"]`);
// Do not update if one dropdown has not selected any option
if (!($allowedToMergeInput.length && $allowedToPushInput.length)) return;
$.ajax({
type: 'POST',
url: this.$wrap.data('url'),
......
......@@ -7,7 +7,9 @@
KEYCODE = {
ESCAPE: 27,
BACKSPACE: 8,
ENTER: 13
ENTER: 13,
UP: 38,
DOWN: 40
};
function SearchAutocomplete(opts) {
......@@ -223,6 +225,12 @@
case KEYCODE.ESCAPE:
this.restoreOriginalState();
break;
case KEYCODE.ENTER:
this.disableAutocomplete();
break;
case KEYCODE.UP:
case KEYCODE.DOWN:
return;
default:
if (this.searchInput.val() === '') {
this.disableAutocomplete();
......@@ -319,9 +327,11 @@
};
SearchAutocomplete.prototype.disableAutocomplete = function() {
this.searchInput.addClass('disabled');
this.dropdown.removeClass('open');
return this.restoreMenu();
if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('open')) {
this.searchInput.addClass('disabled');
this.dropdown.removeClass('open').trigger('hidden.bs.dropdown');
this.restoreMenu();
}
};
SearchAutocomplete.prototype.restoreMenu = function() {
......
/*= require_tree . */
(function() {
$(function() {
var editor = ace.edit("editor")
$(".snippet-form-holder form").on('submit', function() {
$(".snippet-file-content").val(editor.getValue());
});
});
}).call(this);
......@@ -84,6 +84,15 @@
width: 100%;
}
}
// Allows dynamic-width text in the dropdown toggle.
// Resizes to allow long text without overflowing the container.
&.dynamic {
width: auto;
min-width: 160px;
max-width: 100%;
padding-right: 25px;
}
}
.dropdown-menu,
......
......@@ -63,9 +63,10 @@
&.image_file {
background: #eee;
text-align: center;
img {
padding: 100px;
max-width: 50%;
padding: 20px;
max-width: 80%;
}
}
......
......@@ -2,7 +2,7 @@
* Styles that apply to all GFM related forms.
*/
.gfm-commit, .gfm-commit_range {
.gfm-commit_range {
font-family: $monospace_font;
font-size: 90%;
}
.modal-body {
position: relative;
overflow-y: auto;
padding: 15px;
.form-actions {
......
......@@ -72,6 +72,7 @@
font-weight: normal;
background-color: #eee;
color: #78a;
vertical-align: baseline;
}
}
......
......@@ -45,7 +45,8 @@
min-width: 175px;
}
.select2-results .select2-result-label {
.select2-results .select2-result-label,
.select2-more-results {
padding: 10px 15px;
}
......
......@@ -14,12 +14,20 @@
margin-top: 0;
}
// Single code lines should wrap
code {
font-family: $monospace_font;
white-space: pre;
white-space: pre-wrap;
word-wrap: normal;
}
// Multi-line code blocks should scroll horizontally
pre {
code {
white-space: pre;
}
}
kbd {
display: inline-block;
padding: 3px 5px;
......
......@@ -8,9 +8,13 @@
}
.is-dragging {
// Important because plugin sets inline CSS
opacity: 1!important;
* {
cursor: -webkit-grabbing;
cursor: grabbing;
// !important to make sure no style can override this when dragging
cursor: -webkit-grabbing!important;
cursor: grabbing!important;
}
}
......@@ -101,8 +105,8 @@
.board {
display: -webkit-flex;
display: flex;
min-width: calc(100vw - 15px);
max-width: calc(100vw - 15px);
min-width: calc(85vw - 15px);
max-width: calc(85vw - 15px);
margin-bottom: 25px;
padding-right: ($gl-padding / 2);
padding-left: ($gl-padding / 2);
......@@ -154,14 +158,6 @@
padding: $gl-padding;
font-size: 1em;
border-bottom: 1px solid $border-color;
.board-mobile-handle {
position: relative;
left: 0;
top: 1px;
margin-top: 0;
margin-right: 5px;
}
}
.board-search-container {
......@@ -254,11 +250,6 @@
opacity: 0.3;
}
.is-dragging {
// Important because plugin sets inline CSS
opacity: 1!important;
}
.card {
position: relative;
width: 100%;
......@@ -269,11 +260,7 @@
list-style: none;
&.user-can-drag {
padding-left: ($gl-padding * 2);
@media (min-width: $screen-sm-min) {
padding-left: $gl-padding;
}
padding-left: $gl-padding;
}
&:not(:last-child) {
......@@ -294,17 +281,6 @@
}
}
.board-mobile-handle {
position: absolute;
left: 10px;
top: 50%;
margin-top: (-15px / 2);
@media (min-width: $screen-sm-min) {
display: none;
}
}
.card-title {
margin: 0;
font-size: 1em;
......@@ -316,6 +292,7 @@
.card-footer {
margin-top: 5px;
line-height: 25px;
.label {
margin-right: 4px;
......
......@@ -168,7 +168,6 @@
text-overflow: ellipsis;
&:hover {
background-color: $row-hover;
color: $gl-text-color;
}
}
......@@ -190,6 +189,10 @@
display: block;
}
}
&:hover {
background-color: $row-hover;
}
}
}
}
......
......@@ -66,6 +66,15 @@
margin-left: 8px;
}
}
.ci-status-link {
svg {
position: relative;
top: 2px;
margin: 0 2px 0 3px;
}
}
}
.ci-status-link {
......
......@@ -34,11 +34,4 @@
}
}
}
.wiki {
code {
white-space: pre-wrap;
word-break: keep-all;
}
}
}
......@@ -374,3 +374,10 @@
}
}
}
.merge-request-details {
.title {
margin-bottom: 20px;
}
}
......@@ -300,6 +300,17 @@
&.playable {
background-color: $gray-light;
svg {
height: 12px;
width: 12px;
position: relative;
top: 1px;
path {
fill: $layout-link-gray;
}
}
}
.build-content {
......@@ -319,10 +330,6 @@
margin-right: 5px;
}
.fa {
font-size: 13px;
}
// Connect first build in each stage with right horizontal line
&:first-child {
&::after {
......
......@@ -719,3 +719,29 @@ pre.light-well {
width: 300px;
}
}
.clearable-input {
position: relative;
.clear-icon {
@extend .fa-times;
display: none;
position: absolute;
right: 7px;
top: 7px;
color: $location-icon-color;
&:before {
font-family: FontAwesome;
font-weight: normal;
font-style: normal;
}
}
&.has-value {
.clear-icon {
cursor: pointer;
display: block;
}
}
}
......@@ -109,6 +109,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:sentry_dsn,
:akismet_enabled,
:akismet_api_key,
:koding_enabled,
:koding_url,
:email_author_in_body,
:repository_checks_enabled,
:metrics_packet_size,
......
......@@ -42,7 +42,7 @@ class Admin::GroupsController < Admin::ApplicationController
end
def members_update
@group.add_users(params[:user_ids].split(','), params[:access_level], current_user)
@group.add_users(params[:user_ids].split(','), params[:access_level], current_user: current_user)
redirect_to [:admin, @group], notice: 'Users were successfully added.'
end
......
......@@ -7,7 +7,7 @@ class Admin::ImpersonationsController < Admin::ApplicationController
warden.set_user(impersonator, scope: :user)
Gitlab::AppLogger.info("User #{original_user.username} has stopped impersonating #{impersonator.username}")
Gitlab::AppLogger.info("User #{impersonator.username} has stopped impersonating #{original_user.username}")
session[:impersonator_id] = nil
......
......@@ -2,6 +2,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
before_action :find_todos, only: [:index, :destroy_all]
def index
@sort = params[:sort]
@todos = @todos.page(params[:page])
end
......
......@@ -21,7 +21,12 @@ class Groups::GroupMembersController < Groups::ApplicationController
end
def create
@group.add_users(params[:user_ids].split(','), params[:access_level], current_user)
@group.add_users(
params[:user_ids].split(','),
params[:access_level],
current_user: current_user,
expires_at: params[:expires_at]
)
redirect_to group_group_members_path(@group), notice: 'Users were successfully added.'
end
......@@ -63,7 +68,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
protected
def member_params
params.require(:group_member).permit(:access_level, :user_id)
params.require(:group_member).permit(:access_level, :user_id, :expires_at)
end
# MembershipActions concern
......
class KodingController < ApplicationController
before_action :check_integration!, :authenticate_user!, :reject_blocked!
layout 'koding'
def index
path = File.join(Rails.root, 'doc/user/project/koding.md')
@markdown = File.read(path)
end
private
def check_integration!
render_404 unless current_application_settings.koding_enabled?
end
end
......@@ -12,7 +12,7 @@ module Projects
only: [:iid, :title, :confidential],
include: {
assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
labels: { only: [:id, :title, :description, :color, :priority] }
labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] }
})
end
......
......@@ -15,6 +15,13 @@ class Projects::BranchesController < Projects::ApplicationController
diverging_commit_counts = repository.diverging_commit_counts(branch)
[memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
end
respond_to do |format|
format.html
format.json do
render json: @repository.branch_names
end
end
end
def recent
......
......@@ -11,7 +11,9 @@ class Projects::GroupLinksController < Projects::ApplicationController
return render_404 unless can?(current_user, :read_group, group)
project.project_group_links.create(
group: group, group_access: params[:link_group_access]
group: group,
group_access: params[:link_group_access],
expires_at: params[:expires_at]
)
redirect_to namespace_project_group_links_path(project.namespace, project)
......
......@@ -125,6 +125,10 @@ class Projects::IssuesController < Projects::ApplicationController
render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
end
end
rescue ActiveRecord::StaleObjectError
@conflict = true
render :edit
end
def referenced_merge_requests
......@@ -230,7 +234,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue_params
params.require(:issue).permit(
:title, :assignee_id, :position, :description, :confidential,
:milestone_id, :due_date, :state_event, :task_num, label_ids: []
:milestone_id, :due_date, :state_event, :task_num, :lock_version, label_ids: []
)
end
......
......@@ -258,6 +258,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
else
render "edit"
end
rescue ActiveRecord::StaleObjectError
@conflict = true
render :edit
end
def remove_wip
......@@ -493,7 +496,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
:title, :assignee_id, :source_project_id, :source_branch,
:target_project_id, :target_branch, :milestone_id,
:state_event, :description, :task_num, :force_remove_source_branch,
label_ids: []
:lock_version, label_ids: []
)
end
......
......@@ -36,7 +36,12 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end
def create
@project.team.add_users(params[:user_ids].split(','), params[:access_level], current_user)
@project.team.add_users(
params[:user_ids].split(','),
params[:access_level],
expires_at: params[:expires_at],
current_user: current_user
)
redirect_to namespace_project_project_members_path(@project.namespace, @project)
end
......@@ -94,7 +99,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
protected
def member_params
params.require(:project_member).permit(:user_id, :access_level)
params.require(:project_member).permit(:user_id, :access_level, :expires_at)
end
# MembershipActions concern
......
......@@ -5,7 +5,7 @@ class ProjectsController < Projects::ApplicationController
before_action :project, except: [:new, :create]
before_action :repository, except: [:new, :create]
before_action :assign_ref_vars, only: [:show], if: :repo_exists?
before_action :tree, only: [:show], if: [:repo_exists?, :project_view_files?]
before_action :assign_tree_vars, only: [:show], if: [:repo_exists?, :project_view_files?]
# Authorize
before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export]
......@@ -332,4 +332,11 @@ class ProjectsController < Projects::ApplicationController
def get_id
project.repository.root_ref
end
# ExtractsPath will set @id = project.path on the show route, but it has to be the
# branch name for the tree view to work correctly.
def assign_tree_vars
@id = get_id
tree
end
end
class MoveToProjectFinder
PAGE_SIZE = 50
def initialize(user)
@user = user
end
......@@ -8,6 +10,10 @@ class MoveToProjectFinder
projects = projects.search(search) if search.present?
projects = projects.excluding_project(from_project)
# infinite scroll using offset
projects = projects.where('projects.id < ?', offset_id) if offset_id.present?
projects = projects.limit(PAGE_SIZE)
# to ask for Project#name_with_namespace
projects.includes(namespace: :owner)
end
......
......@@ -33,7 +33,7 @@ class TodosFinder
# the project IDs yielded by the todos query thus far
items = by_project(items)
items.reorder(id: :desc)
sort(items)
end
private
......@@ -106,6 +106,10 @@ class TodosFinder
params[:type]
end
def sort(items)
params[:sort] ? items.sort(params[:sort]) : items.reorder(id: :desc)
end
def by_action(items)
if action?
items = items.where(action: to_action_id)
......
......@@ -31,6 +31,10 @@ module ApplicationSettingsHelper
current_application_settings.akismet_enabled?
end
def koding_enabled?
current_application_settings.koding_enabled?
end
def allowed_protocols_present?
current_application_settings.enabled_git_access_protocol.present?
end
......
......@@ -217,4 +217,12 @@ module BlobHelper
def gitlab_ci_ymls
@gitlab_ci_ymls ||= Gitlab::Template::GitlabCiYmlTemplate.dropdown_names
end
def blob_editor_paths
{
'relative-url-root' => Rails.application.config.relative_url_root,
'assets-prefix' => Gitlab::Application.config.assets.prefix,
'blob-language' => @blob && @blob.language.try(:ace_mode)
}
end
end
......@@ -44,7 +44,7 @@ module CiStatusHelper
when 'running'
'icon_status_running'
when 'play'
return icon('play fw')
'icon_play'
when 'created'
'icon_status_pending'
else
......
......@@ -72,6 +72,15 @@ module IssuablesHelper
end
end
def issuable_labels_tooltip(labels, limit: 5)
first, last = labels.partition.with_index{ |_, i| i < limit }
label_names = first.collect(&:name)
label_names << "and #{last.size} more" unless last.empty?
label_names.join(', ')
end
private
def sidebar_gutter_collapsed?
......
......@@ -236,6 +236,60 @@ module ProjectsHelper
)
end
def add_koding_stack_path(project)
namespace_project_new_blob_path(
project.namespace,
project,
project.default_branch || 'master',
file_name: '.koding.yml',
commit_message: "Add Koding stack script",
content: <<-CONTENT.strip_heredoc
provider:
aws:
access_key: '${var.aws_access_key}'
secret_key: '${var.aws_secret_key}'
resource:
aws_instance:
#{project.path}-vm:
instance_type: t2.nano
user_data: |-
# Created by GitLab UI for :>
echo _KD_NOTIFY_@Installing Base packages...@
apt-get update -y
apt-get install git -y
echo _KD_NOTIFY_@Cloning #{project.name}...@
export KODING_USER=${var.koding_user_username}
export REPO_URL=#{root_url}${var.koding_queryString_repo}.git
export BRANCH=${var.koding_queryString_branch}
sudo -i -u $KODING_USER git clone $REPO_URL -b $BRANCH
echo _KD_NOTIFY_@#{project.name} cloned.@
CONTENT
)
end
def koding_project_url(project = nil, branch = nil, sha = nil)
if project
import_path = "/Home/Stacks/import"
repo = project.path_with_namespace
branch ||= project.default_branch
sha ||= project.commit.short_id
path = "#{import_path}?repo=#{repo}&branch=#{branch}&sha=#{sha}"
return URI.join(current_application_settings.koding_url, path).to_s
end
current_application_settings.koding_url
end
def contribution_guide_path(project)
if project && contribution_guide = project.repository.contribution_guide
namespace_project_blob_path(
......
......@@ -15,20 +15,9 @@ module TimeHelper
"#{from.to_s(:short)} - #{to.to_s(:short)}"
end
def duration_in_numbers(finished_at, started_at)
interval = interval_in_seconds(started_at, finished_at)
time_format = interval < 1.hour ? "%M:%S" : "%H:%M:%S"
def duration_in_numbers(duration)
time_format = duration < 1.hour ? "%M:%S" : "%H:%M:%S"
Time.at(interval).utc.strftime(time_format)
end
private
def interval_in_seconds(started_at, finished_at = nil)
if started_at && finished_at
finished_at.to_i - started_at.to_i
elsif started_at
Time.now.to_i - started_at.to_i
end
Time.at(duration).utc.strftime(time_format)
end
end
......@@ -166,38 +166,44 @@ class Ability
end
def project_abilities(user, project)
rules = []
key = "/user/#{user.id}/project/#{project.id}"
RequestStore.store[key] ||= begin
# Push abilities on the users team role
rules.push(*project_team_rules(project.team, user))
if RequestStore.active?
RequestStore.store[key] ||= uncached_project_abilities(user, project)
else
uncached_project_abilities(user, project)
end
end
owner = user.admin? ||
project.owner == user ||
(project.group && project.group.has_owner?(user))
def uncached_project_abilities(user, project)
rules = []
# Push abilities on the users team role
rules.push(*project_team_rules(project.team, user))
if owner
rules.push(*project_owner_rules)
end
owner = user.admin? ||
project.owner == user ||
(project.group && project.group.has_owner?(user))
if project.public? || (project.internal? && !user.external?)
rules.push(*public_project_rules)
if owner
rules.push(*project_owner_rules)
end
# Allow to read builds for internal projects
rules << :read_build if project.public_builds?
if project.public? || (project.internal? && !user.external?)
rules.push(*public_project_rules)
unless owner || project.team.member?(user) || project_group_member?(project, user)
rules << :request_access if project.request_access_enabled
end
end
# Allow to read builds for internal projects
rules << :read_build if project.public_builds?
if project.archived?
rules -= project_archived_rules
unless owner || project.team.member?(user) || project_group_member?(project, user)
rules << :request_access if project.request_access_enabled
end
end
rules - project_disabled_features_rules(project)
if project.archived?
rules -= project_archived_rules
end
(rules - project_disabled_features_rules(project)).uniq
end
def project_team_rules(team, user)
......
......@@ -55,6 +55,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
if: :akismet_enabled
validates :koding_url,
presence: true,
if: :koding_enabled
validates :max_attachment_size,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
......@@ -149,6 +153,8 @@ class ApplicationSetting < ActiveRecord::Base
two_factor_grace_period: 48,
recaptcha_enabled: false,
akismet_enabled: false,
koding_enabled: false,
koding_url: nil,
repository_checks_enabled: true,
disabled_oauth_sign_in_sources: [],
send_user_confirmation_email: false,
......
......@@ -62,6 +62,7 @@ module Ci
status_event: 'enqueue'
)
MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
build.pipeline.mark_as_processable_after_stage(build.stage_idx)
new_build
end
end
......
......@@ -78,6 +78,10 @@ module Ci
CommitStatus.where(pipeline: pluck(:id)).stages
end
def self.total_duration
where.not(duration: nil).sum(:duration)
end
def stages_with_latest_statuses
statuses.latest.order(:stage_idx).group_by(&:stage)
end
......@@ -146,6 +150,10 @@ module Ci
end
end
def mark_as_processable_after_stage(stage_idx)
builds.skipped.where('stage_idx > ?', stage_idx).find_each(&:process)
end
def latest?
return false unless ref
commit = project.commit(ref)
......@@ -250,7 +258,7 @@ module Ci
end
def update_duration
self.duration = statuses.latest.duration
self.duration = calculate_duration
end
def execute_hooks
......
......@@ -229,7 +229,7 @@ class Commit
def diff_refs
Gitlab::Diff::DiffRefs.new(
base_sha: self.parent_id || self.sha,
base_sha: self.parent_id || Gitlab::Git::BLANK_SHA,
head_sha: self.sha
)
end
......
......@@ -21,6 +21,7 @@ class CommitStatus < ActiveRecord::Base
where(id: max_id.group(:name, :commit_id))
end
scope :retried, -> { where.not(id: latest) }
scope :ordered, -> { order(:name) }
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
......@@ -30,6 +31,10 @@ class CommitStatus < ActiveRecord::Base
transition [:created, :skipped] => :pending
end
event :process do
transition skipped: :created
end
event :run do
transition pending: :running
end
......@@ -107,13 +112,7 @@ class CommitStatus < ActiveRecord::Base
end
def duration
duration =
if started_at && finished_at
finished_at - started_at
elsif started_at
Time.now - started_at
end
duration
calculate_duration
end
def stuck?
......
module Expirable
extend ActiveSupport::Concern
included do
scope :expired, -> { where('expires_at <= ?', Time.current) }
end
def expires?
expires_at.present?
end
def expires_soon?
expires_at < 7.days.from_now
end
end
......@@ -87,6 +87,12 @@ module Issuable
User.find(assignee_id_was).update_cache_counts if assignee_id_was
assignee.update_cache_counts if assignee
end
# We want to use optimistic lock for cases when only title or description are involved
# http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
def locking_enabled?
title_changed? || description_changed?
end
end
module ClassMethods
......@@ -131,7 +137,10 @@ module Issuable
end
def order_labels_priority(excluded_labels: [])
select("#{table_name}.*, (#{highest_label_priority(excluded_labels).to_sql}) AS highest_priority").
condition_field = "#{table_name}.id"
highest_priority = highest_label_priority(name, condition_field, excluded_labels: excluded_labels).to_sql
select("#{table_name}.*, (#{highest_priority}) AS highest_priority").
group(arel_table[:id]).
reorder(Gitlab::Database.nulls_last_order('highest_priority', 'ASC'))
end
......@@ -159,20 +168,6 @@ module Issuable
grouping_columns
end
private
def highest_label_priority(excluded_labels)
query = Label.select(Label.arel_table[:priority].minimum).
joins(:label_links).
where(label_links: { target_type: name }).
where("label_links.target_id = #{table_name}.id").
reorder(nil)
query.where.not(title: excluded_labels) if excluded_labels.present?
query
end
end
def today?
......
......@@ -17,6 +17,10 @@ module NoteOnDiff
raise NotImplementedError
end
def original_line_code
raise NotImplementedError
end
def diff_attributes
raise NotImplementedError
end
......
......@@ -35,5 +35,19 @@ module Sortable
all
end
end
private
def highest_label_priority(object_types, condition_field, excluded_labels: [])
query = Label.select(Label.arel_table[:priority].minimum).
joins(:label_links).
where(label_links: { target_type: object_types }).
where("label_links.target_id = #{condition_field}").
reorder(nil)
query.where.not(title: excluded_labels) if excluded_labels.present?
query
end
end
end
......@@ -23,7 +23,7 @@ module Spammable
def submittable_as_spam?
if user_agent_detail
user_agent_detail.submittable?
user_agent_detail.submittable? && current_application_settings.akismet_enabled
else
false
end
......
......@@ -35,11 +35,6 @@ module Statuseable
all.pluck(self.status_sql).first
end
def duration
duration_array = all.map(&:duration).compact
duration_array.reduce(:+)
end
def started_at
all.minimum(:started_at)
end
......@@ -85,4 +80,14 @@ module Statuseable
def complete?
COMPLETED_STATUSES.include?(status)
end
private
def calculate_duration
if started_at && finished_at
finished_at - started_at
elsif started_at
Time.now - started_at
end
end
end
......@@ -16,6 +16,9 @@ class DiffNote < Note
after_initialize :ensure_original_discussion_id
before_validation :set_original_position, :update_position, on: :create
before_validation :set_line_code, :set_original_discussion_id
# We need to do this again, because it's already in `Note`, but is affected by
# `update_position` and needs to run after that.
before_validation :set_discussion_id
after_save :keep_around_commits
class << self
......@@ -57,6 +60,10 @@ class DiffNote < Note
diff_file.position(line) == self.original_position
end
def original_line_code
self.diff_file.line_code(self.diff_line)
end
def active?(diff_refs = nil)
return false unless supported?
return true if for_commit?
......
......@@ -12,6 +12,7 @@ class Discussion
:for_merge_request?,
:line_code,
:original_line_code,
:diff_file,
:for_line?,
:active?,
......
......@@ -95,34 +95,40 @@ class Group < Namespace
end
end
def add_users(user_ids, access_level, current_user = nil)
def add_users(user_ids, access_level, current_user: nil, expires_at: nil)
user_ids.each do |user_id|
Member.add_user(self.group_members, user_id, access_level, current_user)
Member.add_user(
self.group_members,
user_id,
access_level,
current_user: current_user,
expires_at: expires_at
)
end
end
def add_user(user, access_level, current_user = nil)
add_users([user], access_level, current_user)
def add_user(user, access_level, current_user: nil, expires_at: nil)
add_users([user], access_level, current_user: current_user, expires_at: expires_at)
end
def add_guest(user, current_user = nil)
add_user(user, Gitlab::Access::GUEST, current_user)
add_user(user, Gitlab::Access::GUEST, current_user: current_user)
end
def add_reporter(user, current_user = nil)
add_user(user, Gitlab::Access::REPORTER, current_user)
add_user(user, Gitlab::Access::REPORTER, current_user: current_user)
end
def add_developer(user, current_user = nil)
add_user(user, Gitlab::Access::DEVELOPER, current_user)
add_user(user, Gitlab::Access::DEVELOPER, current_user: current_user)
end
def add_master(user, current_user = nil)
add_user(user, Gitlab::Access::MASTER, current_user)
add_user(user, Gitlab::Access::MASTER, current_user: current_user)
end
def add_owner(user, current_user = nil)
add_user(user, Gitlab::Access::OWNER, current_user)
add_user(user, Gitlab::Access::OWNER, current_user: current_user)
end
def has_owner?(user)
......
......@@ -49,6 +49,10 @@ class LegacyDiffNote < Note
!line.meta? && diff_file.line_code(line) == self.line_code
end
def original_line_code
self.line_code
end
# Check if this note is part of an "active" discussion
#
# This will always return true for anything except MergeRequest noteables,
......
class Member < ActiveRecord::Base
include Sortable
include Importable
include Expirable
include Gitlab::Access
attr_accessor :raw_invite_token
......@@ -73,7 +74,7 @@ class Member < ActiveRecord::Base
user
end
def add_user(members, user_id, access_level, current_user = nil)
def add_user(members, user_id, access_level, current_user: nil, expires_at: nil)
user = user_for_id(user_id)
# `user` can be either a User object or an email to be invited
......@@ -87,6 +88,7 @@ class Member < ActiveRecord::Base
if can_update_member?(current_user, member) || project_creator?(member, access_level)
member.created_by ||= current_user
member.access_level = access_level
member.expires_at = expires_at
member.save
end
......
......@@ -34,7 +34,7 @@ class ProjectMember < Member
# :master
# )
#
def add_users_to_projects(project_ids, user_ids, access, current_user = nil)
def add_users_to_projects(project_ids, user_ids, access, current_user: nil, expires_at: nil)
access_level = if roles_hash.has_key?(access)
roles_hash[access]
elsif roles_hash.values.include?(access.to_i)
......@@ -50,7 +50,13 @@ class ProjectMember < Member
project = Project.find(project_id)
users.each do |user|
Member.add_user(project.project_members, user, access_level, current_user)
Member.add_user(
project.project_members,
user,
access_level,
current_user: current_user,
expires_at: expires_at
)
end
end
end
......
......@@ -259,6 +259,8 @@ class Note < ActiveRecord::Base
def ensure_discussion_id
return unless self.persisted?
# Needed in case the SELECT statement doesn't ask for `discussion_id`
return unless self.has_attribute?(:discussion_id)
return if self.discussion_id
set_discussion_id
......
......@@ -611,7 +611,10 @@ class Project < ActiveRecord::Base
end
def new_issue_address(author)
if Gitlab::IncomingEmail.enabled? && author
# This feature is disabled for the time being.
return nil
if Gitlab::IncomingEmail.enabled? && author # rubocop:disable Lint/UnreachableCode
Gitlab::IncomingEmail.reply_address(
"#{path_with_namespace}+#{author.authentication_token}")
end
......@@ -1003,8 +1006,8 @@ class Project < ActiveRecord::Base
project_members.find_by(user_id: user)
end
def add_user(user, access_level, current_user = nil)
team.add_user(user, access_level, current_user)
def add_user(user, access_level, current_user: nil, expires_at: nil)
team.add_user(user, access_level, current_user: current_user, expires_at: expires_at)
end
def default_branch
......
class ProjectGroupLink < ActiveRecord::Base
include Expirable
GUEST = 10
REPORTER = 20
DEVELOPER = 30
......@@ -26,7 +28,7 @@ class ProjectGroupLink < ActiveRecord::Base
self.class.access_options.key(self.group_access)
end
private
private
def different_group
if self.group && self.project && self.project.group == self.group
......
......@@ -15,9 +15,9 @@ class ProjectTeam
users, access, current_user = *args
if users.respond_to?(:each)
add_users(users, access, current_user)
add_users(users, access, current_user: current_user)
else
add_user(users, access, current_user)
add_user(users, access, current_user: current_user)
end
end
......@@ -33,17 +33,18 @@ class ProjectTeam
member
end
def add_users(users, access, current_user = nil)
def add_users(users, access, current_user: nil, expires_at: nil)
ProjectMember.add_users_to_projects(
[project.id],
users,
access,
current_user
current_user: current_user,
expires_at: expires_at
)
end
def add_user(user, access, current_user = nil)
add_users([user], access, current_user)
def add_user(user, access, current_user: nil, expires_at: nil)
add_users([user], access, current_user: current_user, expires_at: expires_at)
end
# Remove all users from project team
......
......@@ -277,7 +277,7 @@ class Repository
def cache_keys
%i(size commit_count
readme version contribution_guide changelog
license_blob license_key gitignore)
license_blob license_key gitignore koding_yml)
end
# Keys for data on branch/tag operations.
......@@ -553,6 +553,14 @@ class Repository
end
end
def koding_yml
return nil unless head_exists?
cache.fetch(:koding_yml) do
file_on_head(/\A\.koding\.yml\z/)
end
end
def gitlab_ci_yml
return nil unless head_exists?
......
class Todo < ActiveRecord::Base
include Sortable
ASSIGNED = 1
MENTIONED = 2
BUILD_FAILED = 3
......@@ -41,6 +43,23 @@ class Todo < ActiveRecord::Base
after_save :keep_around_commit
class << self
def sort(method)
method == "priority" ? order_by_labels_priority : order_by(method)
end
# Order by priority depending on which issue/merge request the Todo belongs to
# Todos with highest priority first then oldest todos
# Need to order by created_at last because of differences on Mysql and Postgres when joining by type "Merge_request/Issue"
def order_by_labels_priority
highest_priority = highest_label_priority(["Issue", "MergeRequest"], "todos.target_id").to_sql
select("#{table_name}.*, (#{highest_priority}) AS highest_priority").
order(Gitlab::Database.nulls_last_order('highest_priority', 'ASC')).
order('todos.created_at')
end
end
def build_failed?
action == BUILD_FAILED
end
......
module Members
class AuthorizedDestroyService < BaseService
attr_accessor :member, :user
def initialize(member, user = nil)
@member, @user = member, user
end
def execute
return false if member.is_a?(GroupMember) && member.source.last_owner?(member.user)
member.destroy
if member.request? && member.user != user
notification_service.decline_access_request(member)
end
end
end
end
......@@ -11,12 +11,7 @@ module Members
unless member && can?(current_user, "destroy_#{member.type.underscore}".to_sym, member)
raise Gitlab::Access::AccessDeniedError
end
member.destroy
if member.request? && member.user != current_user
notification_service.decline_access_request(member)
end
AuthorizedDestroyService.new(member, current_user).execute
end
end
end
......@@ -242,7 +242,6 @@ class NotificationService
project_member.real_source_type,
project_member.project.id,
project_member.invite_email,
project_member.access_level,
project_member.created_by_id
).deliver_later
end
......@@ -269,7 +268,6 @@ class NotificationService
group_member.real_source_type,
group_member.group.id,
group_member.invite_email,
group_member.access_level,
group_member.created_by_id
).deliver_later
end
......
......@@ -388,6 +388,25 @@
.help-block
If you got a lot of false alarms from repository checks you can choose to clear all repository check information from the database.
%fieldset
%legend Koding
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :koding_enabled do
= f.check_box :koding_enabled
Enable Koding
.form-group
= f.label :koding_url, 'Koding URL', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :koding_url, class: 'form-control', placeholder: 'http://gitlab.your-koding-instance.com:8090'
.help-block
Koding has integration enabled out of the box for the
%strong gitlab
team, and you need to provide that team's URL here. Learn more in the
= succeed "." do
= link_to "Koding administration documentation", help_page_path("administration/integration/koding")
.form-actions
= f.submit 'Save', class: 'btn btn-save'
......@@ -51,7 +51,7 @@
- if build.duration
%p.duration
= custom_icon("icon_timer")
= duration_in_numbers(build.finished_at, build.started_at)
= duration_in_numbers(build.duration)
- if build.finished_at
%p.finished-at
......
......@@ -43,6 +43,25 @@
class: 'select2 trigger-submit', include_blank: true,
data: {placeholder: 'Action'})
.pull-right
.dropdown.inline.prepend-left-10
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
%span.light
- if @sort.present?
= sort_options_hash[@sort]
- else
= sort_title_recently_created
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort
%li
= link_to todos_filter_path(sort: sort_value_priority) do
= sort_title_priority
= link_to todos_filter_path(sort: sort_value_recently_created) do
= sort_title_recently_created
= link_to todos_filter_path(sort: sort_value_oldest_created) do
= sort_title_oldest_created
.prepend-top-default
- if @todos.any?
.js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} }
......
......@@ -7,7 +7,7 @@
.diff-content.code.js-syntax-highlight
%table
- discussions = { discussion.line_code => discussion }
- discussions = { discussion.original_line_code => discussion }
= render partial: "projects/diffs/line",
collection: discussion.truncated_diff_lines,
as: :line,
......
......@@ -14,5 +14,14 @@
Read more about role permissions
%strong= link_to "here", help_page_path("user/permissions"), class: "vlink"
.form-group
= f.label :expires_at, 'Access expiration date', class: 'control-label'
.col-sm-10
.clearable-input
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date'
%i.clear-icon.js-clear-input
.help-block
On this date, the user(s) will automatically lose access to this group and all of its projects.
.form-actions
= f.submit 'Add users to group', class: "btn btn-create"
:plain
$("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @group_member))}');
new MemberExpirationDate();
.row-content-block.second-block.center
%p
= icon('circle', class: 'cgreen')
Integration is active for
= link_to koding_project_url, target: '_blank' do
#{current_application_settings.koding_url}
- page_title "Koding"
- page_description "Koding Dashboard"
- header_title "Koding", koding_path
= render template: "layouts/application"
......@@ -12,6 +12,11 @@
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
%span
Activity
- if koding_enabled?
= nav_link(controller: :koding) do
= link_to koding_path, title: 'Koding' do
%span
Koding
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to dashboard_groups_path, title: 'Groups' do
%span
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment