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. 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 - Add test coverage report badge. !5708
- Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar) - 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) - Ability to specify branches for Pivotal Tracker integration (Egor Lynko)
- Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres) - Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres)
- Add delimiter to project stars and forks count (ClemMakesApps) - Add delimiter to project stars and forks count (ClemMakesApps)
- Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres) - 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) - Fix the title of the toggle dropdown button. !5515 (herminiotorres)
- Rename `markdown_preview` routes to `preview_markdown`. (Christopher Bartz) - Rename `markdown_preview` routes to `preview_markdown`. (Christopher Bartz)
- Update to Ruby 2.3.1. !4948 - Update to Ruby 2.3.1. !4948
...@@ -18,13 +29,17 @@ v 8.11.0 (unreleased) ...@@ -18,13 +29,17 @@ v 8.11.0 (unreleased)
- API: Endpoints for enabling and disabling deploy keys - 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 - 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) - 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) - 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 - 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) - Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell)
- Allow naming U2F devices !5833 - Allow naming U2F devices !5833
- Ignore URLs starting with // in Markdown links !5677 (winniehell) - Ignore URLs starting with // in Markdown links !5677 (winniehell)
- Fix CI status icon link underline (ClemMakesApps) - Fix CI status icon link underline (ClemMakesApps)
- The Repository class is now instrumented - 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) - Fix filter label tooltip HTML rendering (ClemMakesApps)
- Cache the commit author in RequestStore to avoid extra lookups in PostReceive - Cache the commit author in RequestStore to avoid extra lookups in PostReceive
- Add a button to download latest successful artifacts for branches and tags - Add a button to download latest successful artifacts for branches and tags
...@@ -35,9 +50,11 @@ v 8.11.0 (unreleased) ...@@ -35,9 +50,11 @@ v 8.11.0 (unreleased)
- API: Add deployment endpoints - API: Add deployment endpoints
- API: Add Play endpoint on Builds - API: Add Play endpoint on Builds
- Fix of 'Commits being passed to custom hooks are already reachable when using the UI' - 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 - Show member roles to all users on members page
- Project.visible_to_user is instrumented again - Project.visible_to_user is instrumented again
- Fix awardable button mutuality loading spinners (ClemMakesApps) - 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 - Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable
- Optimize maximum user access level lookup in loading of notes - Optimize maximum user access level lookup in loading of notes
- Send notification emails to users newly mentioned in issue and MR edits !5800 - Send notification emails to users newly mentioned in issue and MR edits !5800
...@@ -56,6 +73,7 @@ v 8.11.0 (unreleased) ...@@ -56,6 +73,7 @@ v 8.11.0 (unreleased)
- Enforce 2FA restrictions on API authentication endpoints !5820 - Enforce 2FA restrictions on API authentication endpoints !5820
- Limit git rev-list output count to one in forced push check - Limit git rev-list output count to one in forced push check
- Show deployment status on merge requests with external URLs - Show deployment status on merge requests with external URLs
- Fix branch title trailing space on hover (ClemMakesApps)
- Clean up unused routes (Josef Strzibny) - Clean up unused routes (Josef Strzibny)
- Fix issue on empty project to allow developers to only push to protected branches if given permission - Fix issue on empty project to allow developers to only push to protected branches if given permission
- API: Add enpoints for pipelines - API: Add enpoints for pipelines
...@@ -72,6 +90,7 @@ v 8.11.0 (unreleased) ...@@ -72,6 +90,7 @@ v 8.11.0 (unreleased)
- Fix devise deprecation warnings. - Fix devise deprecation warnings.
- Check for 2FA when using Git over HTTP and only allow PersonalAccessTokens as password in that case !5764 - 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 - 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 - 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 - Store all DB secrets in secrets.yml, under descriptive names !5274
- Fix syntax highlighting in file editor - Fix syntax highlighting in file editor
...@@ -80,7 +99,6 @@ v 8.11.0 (unreleased) ...@@ -80,7 +99,6 @@ v 8.11.0 (unreleased)
- Add archived badge to project list !5798 - Add archived badge to project list !5798
- Add simple identifier to public SSH keys (muteor) - Add simple identifier to public SSH keys (muteor)
- Admin page now references docs instead of a specific file !5600 (AnAverageHuman) - 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) - Fix filter input alignment (ClemMakesApps)
- Include old revision in merge request update hooks (Ben Boeckel) - Include old revision in merge request update hooks (Ben Boeckel)
- Add build event color in HipChat messages (David Eisner) - Add build event color in HipChat messages (David Eisner)
...@@ -106,12 +124,14 @@ v 8.11.0 (unreleased) ...@@ -106,12 +124,14 @@ v 8.11.0 (unreleased)
- Fix search for notes which belongs to deleted objects - Fix search for notes which belongs to deleted objects
- Allow Akismet to be trained by submitting issues as spam or ham !5538 - Allow Akismet to be trained by submitting issues as spam or ham !5538
- Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska) - 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) - Allow branch names ending with .json for graph and network page !5579 (winniehell)
- Add the `sprockets-es6` gem - Add the `sprockets-es6` gem
- Improve OAuth2 client documentation (muteor) - Improve OAuth2 client documentation (muteor)
- Fix diff comments inverted toggle bug (ClemMakesApps) - Fix diff comments inverted toggle bug (ClemMakesApps)
- Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska)
- Profile requests when a header is passed - 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. - 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 - 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) - Add commit stats in commit api. !5517 (dixpac)
...@@ -120,14 +140,17 @@ v 8.11.0 (unreleased) ...@@ -120,14 +140,17 @@ v 8.11.0 (unreleased)
- edit_blob_link will use blob passed onto the options parameter - edit_blob_link will use blob passed onto the options parameter
- Make error pages responsive (Takuya Noguchi) - Make error pages responsive (Takuya Noguchi)
- The performance of the project dropdown used for moving issues has been improved - 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 - Fix skip_repo parameter being ignored when destroying a namespace
- Add all builds into stage/job dropdowns on builds page - Add all builds into stage/job dropdowns on builds page
- Change requests_profiles resource constraint to catch virtually any file - Change requests_profiles resource constraint to catch virtually any file
- Bump gitlab_git to lazy load compare commits - Bump gitlab_git to lazy load compare commits
- Reduce number of queries made for merge_requests/:id/diffs - 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) - 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 bug where destroying a namespace would not always destroy projects
- Fix RequestProfiler::Middleware error when code is reloaded in development - 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 - 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 - 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) - 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) ...@@ -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 - 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 auto-completition in pipeline (Katarzyna Kobierska Ula Budziszewska)
- Add pipelines tab to merge requests - Add pipelines tab to merge requests
- Fix notification_service argument error of declined invitation emails
- Fix a memory leak caused by Banzai::Filter::SanitizationFilter - Fix a memory leak caused by Banzai::Filter::SanitizationFilter
- Speed up todos queries by limiting the projects set we join with - 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 - Ensure file editing in UI does not overwrite commited changes without warning user
...@@ -149,6 +173,10 @@ v 8.11.0 (unreleased) ...@@ -149,6 +173,10 @@ v 8.11.0 (unreleased)
- Update gitlab_git gem to 10.4.7 - Update gitlab_git gem to 10.4.7
- Simplify SQL queries of marking a todo as done - 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 v 8.10.6
- Upgrade Rails to 4.2.7.1 for security fixes. !5781 - Upgrade Rails to 4.2.7.1 for security fixes. !5781
- Restore "Largest repository" sort option on Admin > Projects page. !5797 - Restore "Largest repository" sort option on Admin > Projects page. !5797
...@@ -177,8 +205,6 @@ v 8.10.3 ...@@ -177,8 +205,6 @@ v 8.10.3
- Trim extra displayed carriage returns in diffs and files with CRLFs. !5588 - Trim extra displayed carriage returns in diffs and files with CRLFs. !5588
- Fix label already exist error message in the right sidebar. - Fix label already exist error message in the right sidebar.
v 8.10.3 (unreleased)
v 8.10.2 v 8.10.2
- User can now search branches by name. !5144 - User can now search branches by name. !5144
- Page is now properly rendered after committing the first file and creating the first branch. !5399 - Page is now properly rendered after committing the first file and creating the first branch. !5399
...@@ -372,6 +398,9 @@ v 8.10.0 ...@@ -372,6 +398,9 @@ v 8.10.0
- Fix migration corrupting import data for old version upgrades - Fix migration corrupting import data for old version upgrades
- Show tooltip on GitLab export link in new project page - 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 v 8.9.7
- Upgrade Rails to 4.2.7.1 for security fixes. !5781 - Upgrade Rails to 4.2.7.1 for security fixes. !5781
- Require administrator privileges to perform a project import. - Require administrator privileges to perform a project import.
...@@ -641,6 +670,9 @@ v 8.9.0 ...@@ -641,6 +670,9 @@ v 8.9.0
- Add tooltip to pin/unpin navbar - Add tooltip to pin/unpin navbar
- Add new sub nav style to Wiki and Graphs sub navigation - 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 v 8.8.8
- Upgrade Rails to 4.2.7.1 for security fixes. !5781 - 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. ...@@ -387,7 +387,8 @@ description area. Copy-paste it to retain the markdown format.
1. The change is as small as possible 1. The change is as small as possible
1. Include proper tests and make all tests pass (unless it contains a test 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 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 try and restart the failing CI job or ask a developer to fix the
aforementioned failing test 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 @@ ...@@ -26,8 +26,6 @@
/*= require bootstrap/tooltip */ /*= require bootstrap/tooltip */
/*= require bootstrap/popover */ /*= require bootstrap/popover */
/*= require select2 */ /*= require select2 */
/*= require ace-rails-ap */
/*= require ace/ext-searchbox */
/*= require underscore */ /*= require underscore */
/*= require dropzone */ /*= require dropzone */
/*= require mousetrap */ /*= require mousetrap */
...@@ -153,7 +151,9 @@ ...@@ -153,7 +151,9 @@
}); });
}); });
$('.remove-row').bind('ajax:success', function() { $('.remove-row').bind('ajax:success', function() {
return $(this).closest('li').fadeOut(); $(this).tooltip('destroy')
.closest('li')
.fadeOut();
}); });
$('.js-remove-tr').bind('ajax:before', function() { $('.js-remove-tr').bind('ajax:before', function() {
return $(this).hide(); 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 @@ $(() => { ...@@ -38,7 +38,7 @@ $(() => {
ready () { ready () {
Store.disabled = this.disabled; Store.disabled = this.disabled;
gl.boardService.all() gl.boardService.all()
.then((resp) => { .then((resp) => {
resp.json().forEach((board) => { resp.json().forEach((board) => {
const list = Store.addList(board); const list = Store.addList(board);
......
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
draggable: '.is-draggable', draggable: '.is-draggable',
handle: '.js-board-handle', handle: '.js-board-handle',
onEnd: (e) => { onEnd: (e) => {
document.body.classList.remove('is-dragging'); gl.issueBoards.onEnd();
if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) { if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
const order = this.sortable.toArray(), const order = this.sortable.toArray(),
...@@ -72,10 +72,6 @@ ...@@ -72,10 +72,6 @@
} }
}); });
if (bp.getBreakpointSize() === 'xs') {
options.handle = '.js-board-drag-handle';
}
this.sortable = Sortable.create(this.$el.parentNode, options); this.sortable = Sortable.create(this.$el.parentNode, options);
}, },
beforeDestroy () { beforeDestroy () {
......
...@@ -63,6 +63,8 @@ ...@@ -63,6 +63,8 @@
Store.moving.issue = card.issue; Store.moving.issue = card.issue;
Store.moving.list = card.list; Store.moving.list = card.list;
gl.issueBoards.onStart();
}, },
onAdd: (e) => { onAdd: (e) => {
gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue); gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue);
...@@ -72,10 +74,6 @@ ...@@ -72,10 +74,6 @@
} }
}); });
if (bp.getBreakpointSize() === 'xs') {
options.handle = '.js-card-drag-handle';
}
this.sortable = Sortable.create(this.$els.list, options); this.sortable = Sortable.create(this.$els.list, options);
// Scroll event on list to load more // Scroll event on list to load more
......
...@@ -2,6 +2,19 @@ ...@@ -2,6 +2,19 @@
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {}; 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) => { gl.issueBoards.getBoardSortableDefaultOptions = (obj) => {
let defaultSortOptions = { let defaultSortOptions = {
forceFallback: true, forceFallback: true,
...@@ -9,14 +22,11 @@ ...@@ -9,14 +22,11 @@
fallbackOnBody: true, fallbackOnBody: true,
ghostClass: 'is-ghost', ghostClass: 'is-ghost',
filter: '.has-tooltip', filter: '.has-tooltip',
scrollSensitivity: 100, delay: gl.issueBoards.touchEnabled ? 100 : 0,
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
scrollSpeed: 20, scrollSpeed: 20,
onStart () { onStart: gl.issueBoards.onStart,
document.body.classList.add('is-dragging'); onEnd: gl.issueBoards.onEnd
},
onEnd () {
document.body.classList.remove('is-dragging');
}
} }
Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; }); Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
......
...@@ -3,6 +3,7 @@ class ListLabel { ...@@ -3,6 +3,7 @@ class ListLabel {
this.id = obj.id; this.id = obj.id;
this.title = obj.title; this.title = obj.title;
this.color = obj.color; this.color = obj.color;
this.textColor = obj.text_color;
this.description = obj.description; this.description = obj.description;
this.priority = (obj.priority !== null) ? obj.priority : Infinity; this.priority = (obj.priority !== null) ? obj.priority : Infinity;
} }
......
File mode changed from 100755 to 100644
...@@ -20,6 +20,9 @@ ...@@ -20,6 +20,9 @@
path = page.split(':'); path = page.split(':');
shortcut_handler = null; shortcut_handler = null;
switch (page) { switch (page) {
case 'projects:boards:show':
shortcut_handler = new ShortcutsNavigation();
break;
case 'projects:issues:index': case 'projects:issues:index':
Issuable.init(); Issuable.init();
new IssuableBulkActions(); new IssuableBulkActions();
...@@ -126,10 +129,12 @@ ...@@ -126,10 +129,12 @@
new NotificationsDropdown(); new NotificationsDropdown();
break; break;
case 'groups:group_members:index': case 'groups:group_members:index':
new gl.MemberExpirationDate();
new GroupMembers(); new GroupMembers();
new UsersSelect(); new UsersSelect();
break; break;
case 'projects:project_members:index': case 'projects:project_members:index':
new gl.MemberExpirationDate();
new ProjectMembers(); new ProjectMembers();
new UsersSelect(); new UsersSelect();
break; break;
...@@ -171,6 +176,7 @@ ...@@ -171,6 +176,7 @@
new BuildArtifacts(); new BuildArtifacts();
break; break;
case 'projects:group_links:index': case 'projects:group_links:index':
new gl.MemberExpirationDate();
new GroupsSelect(); new GroupsSelect();
break; break;
case 'search:show': case 'search:show':
......
...@@ -39,12 +39,13 @@ ...@@ -39,12 +39,13 @@
FilesCommentButton.prototype.render = function(e) { FilesCommentButton.prototype.render = function(e) {
var $currentTarget, buttonParentElement, lineContentElement, textFileElement; var $currentTarget, buttonParentElement, lineContentElement, textFileElement;
$currentTarget = $(e.currentTarget); $currentTarget = $(e.currentTarget);
buttonParentElement = this.getButtonParent($currentTarget); buttonParentElement = this.getButtonParent($currentTarget);
if (!this.shouldRender(e, buttonParentElement)) { if (!this.validateButtonParent(buttonParentElement)) return;
return;
}
textFileElement = this.getTextFileElement($currentTarget);
lineContentElement = this.getLineContent($currentTarget); lineContentElement = this.getLineContent($currentTarget);
if (!this.validateLineContent(lineContentElement)) return;
textFileElement = this.getTextFileElement($currentTarget);
buttonParentElement.append(this.buildButton({ buttonParentElement.append(this.buildButton({
noteableType: textFileElement.attr('data-noteable-type'), noteableType: textFileElement.attr('data-noteable-type'),
noteableID: textFileElement.attr('data-noteable-id'), noteableID: textFileElement.attr('data-noteable-id'),
...@@ -119,10 +120,14 @@ ...@@ -119,10 +120,14 @@
return newButtonParent.is(this.getButtonParent($(e.currentTarget))); 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; 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; return FilesCommentButton;
})(); })();
......
This diff is collapsed.
...@@ -102,20 +102,34 @@ ...@@ -102,20 +102,34 @@
}; };
IssuableForm.prototype.initMoveDropdown = function() { IssuableForm.prototype.initMoveDropdown = function() {
var $moveDropdown; var $moveDropdown, pageSize;
$moveDropdown = $('.js-move-dropdown'); $moveDropdown = $('.js-move-dropdown');
if ($moveDropdown.length) { if ($moveDropdown.length) {
pageSize = $moveDropdown.data('page-size');
return $('.js-move-dropdown').select2({ return $('.js-move-dropdown').select2({
ajax: { ajax: {
url: $moveDropdown.data('projects-url'), url: $moveDropdown.data('projects-url'),
results: function(data) { quietMillis: 125,
data: function(term, page, context) {
return { 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 { return {
search: query results: data,
more: more,
context: context
}; };
} }
}, },
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
var _this; var _this;
_this = this; _this = this;
$('.js-label-select').each(function(i, dropdown) { $('.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); $dropdown = $(dropdown);
projectId = $dropdown.data('project-id'); projectId = $dropdown.data('project-id');
labelUrl = $dropdown.data('labels'); labelUrl = $dropdown.data('labels');
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
$block = $selectbox.closest('.block'); $block = $selectbox.closest('.block');
$form = $dropdown.closest('form'); $form = $dropdown.closest('form');
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span'); $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span');
$sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip');
$value = $block.find('.value'); $value = $block.find('.value');
$loading = $block.find('.block-loading').fadeOut(); $loading = $block.find('.block-loading').fadeOut();
if (issueUpdateURL != null) { if (issueUpdateURL != null) {
...@@ -31,7 +32,11 @@ ...@@ -31,7 +32,11 @@
labelNoneHTMLTemplate = '<span class="no-value">None</span>'; 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() { saveLabelData = function() {
var data, selected; var data, selected;
...@@ -52,7 +57,7 @@ ...@@ -52,7 +57,7 @@
dataType: 'JSON', dataType: 'JSON',
data: data data: data
}).done(function(data) { }).done(function(data) {
var labelCount, template; var labelCount, template, labelTooltipTitle, labelTitles;
$loading.fadeOut(); $loading.fadeOut();
$dropdown.trigger('loaded.gl.dropdown'); $dropdown.trigger('loaded.gl.dropdown');
$selectbox.hide(); $selectbox.hide();
...@@ -66,6 +71,27 @@ ...@@ -66,6 +71,27 @@
} }
$value.removeAttr('style').html(template); $value.removeAttr('style').html(template);
$sidebarCollapsedValue.text(labelCount); $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({ $('.has-tooltip', $value).tooltip({
container: 'body' 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 @@ ...@@ -65,7 +65,8 @@
url: $dropdown.data('refs-url'), url: $dropdown.data('refs-url'),
data: { data: {
ref: $dropdown.data('ref') ref: $dropdown.data('ref')
} },
dataType: "json"
}).done(function(refs) { }).done(function(refs) {
return callback(refs); return callback(refs);
}); });
...@@ -73,7 +74,7 @@ ...@@ -73,7 +74,7 @@
selectable: true, selectable: true,
filterable: true, filterable: true,
filterByText: true, filterByText: true,
fieldName: 'ref', fieldName: $dropdown.data('field-name'),
renderRow: function(ref) { renderRow: function(ref) {
var link; var link;
if (ref.header != null) { if (ref.header != null) {
......
...@@ -5,9 +5,6 @@ ...@@ -5,9 +5,6 @@
return $(this).fadeOut(); return $(this).fadeOut();
}); });
} }
return ProjectMembers; return ProjectMembers;
})(); })();
}).call(this); }).call(this);
...@@ -10,8 +10,12 @@ ...@@ -10,8 +10,12 @@
selectable: true, selectable: true,
inputId: $dropdown.data('input-id'), inputId: $dropdown.data('input-id'),
fieldName: $dropdown.data('field-name'), fieldName: $dropdown.data('field-name'),
toggleLabel(item) { toggleLabel(item, el) {
return item.text; if (el.is('.is-active')) {
return item.text;
} else {
return 'Select';
}
}, },
clicked(item, $el, e) { clicked(item, $el, e) {
e.preventDefault(); e.preventDefault();
......
...@@ -47,9 +47,7 @@ ...@@ -47,9 +47,7 @@
const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]'); 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]"]'); 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"]').attr('disabled', !($branchInput.val() && $allowedToMergeInput.length && $allowedToPushInput.length));
this.$form.find('input[type="submit"]').removeAttr('disabled');
}
} }
} }
......
...@@ -31,6 +31,9 @@ ...@@ -31,6 +31,9 @@
const $allowedToMergeInput = this.$wrap.find(`input[name="${this.$allowedToMergeDropdown.data('fieldName')}"]`); const $allowedToMergeInput = this.$wrap.find(`input[name="${this.$allowedToMergeDropdown.data('fieldName')}"]`);
const $allowedToPushInput = this.$wrap.find(`input[name="${this.$allowedToPushDropdown.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({ $.ajax({
type: 'POST', type: 'POST',
url: this.$wrap.data('url'), url: this.$wrap.data('url'),
......
...@@ -7,7 +7,9 @@ ...@@ -7,7 +7,9 @@
KEYCODE = { KEYCODE = {
ESCAPE: 27, ESCAPE: 27,
BACKSPACE: 8, BACKSPACE: 8,
ENTER: 13 ENTER: 13,
UP: 38,
DOWN: 40
}; };
function SearchAutocomplete(opts) { function SearchAutocomplete(opts) {
...@@ -223,6 +225,12 @@ ...@@ -223,6 +225,12 @@
case KEYCODE.ESCAPE: case KEYCODE.ESCAPE:
this.restoreOriginalState(); this.restoreOriginalState();
break; break;
case KEYCODE.ENTER:
this.disableAutocomplete();
break;
case KEYCODE.UP:
case KEYCODE.DOWN:
return;
default: default:
if (this.searchInput.val() === '') { if (this.searchInput.val() === '') {
this.disableAutocomplete(); this.disableAutocomplete();
...@@ -319,9 +327,11 @@ ...@@ -319,9 +327,11 @@
}; };
SearchAutocomplete.prototype.disableAutocomplete = function() { SearchAutocomplete.prototype.disableAutocomplete = function() {
this.searchInput.addClass('disabled'); if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('open')) {
this.dropdown.removeClass('open'); this.searchInput.addClass('disabled');
return this.restoreMenu(); this.dropdown.removeClass('open').trigger('hidden.bs.dropdown');
this.restoreMenu();
}
}; };
SearchAutocomplete.prototype.restoreMenu = function() { 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 @@ ...@@ -84,6 +84,15 @@
width: 100%; 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, .dropdown-menu,
......
...@@ -63,9 +63,10 @@ ...@@ -63,9 +63,10 @@
&.image_file { &.image_file {
background: #eee; background: #eee;
text-align: center; text-align: center;
img { img {
padding: 100px; padding: 20px;
max-width: 50%; max-width: 80%;
} }
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* Styles that apply to all GFM related forms. * Styles that apply to all GFM related forms.
*/ */
.gfm-commit, .gfm-commit_range { .gfm-commit_range {
font-family: $monospace_font; font-family: $monospace_font;
font-size: 90%; font-size: 90%;
} }
.modal-body { .modal-body {
position: relative; position: relative;
overflow-y: auto;
padding: 15px; padding: 15px;
.form-actions { .form-actions {
......
...@@ -72,6 +72,7 @@ ...@@ -72,6 +72,7 @@
font-weight: normal; font-weight: normal;
background-color: #eee; background-color: #eee;
color: #78a; color: #78a;
vertical-align: baseline;
} }
} }
......
...@@ -45,7 +45,8 @@ ...@@ -45,7 +45,8 @@
min-width: 175px; min-width: 175px;
} }
.select2-results .select2-result-label { .select2-results .select2-result-label,
.select2-more-results {
padding: 10px 15px; padding: 10px 15px;
} }
......
...@@ -14,12 +14,20 @@ ...@@ -14,12 +14,20 @@
margin-top: 0; margin-top: 0;
} }
// Single code lines should wrap
code { code {
font-family: $monospace_font; font-family: $monospace_font;
white-space: pre; white-space: pre-wrap;
word-wrap: normal; word-wrap: normal;
} }
// Multi-line code blocks should scroll horizontally
pre {
code {
white-space: pre;
}
}
kbd { kbd {
display: inline-block; display: inline-block;
padding: 3px 5px; padding: 3px 5px;
......
...@@ -8,9 +8,13 @@ ...@@ -8,9 +8,13 @@
} }
.is-dragging { .is-dragging {
// Important because plugin sets inline CSS
opacity: 1!important;
* { * {
cursor: -webkit-grabbing; // !important to make sure no style can override this when dragging
cursor: grabbing; cursor: -webkit-grabbing!important;
cursor: grabbing!important;
} }
} }
...@@ -101,8 +105,8 @@ ...@@ -101,8 +105,8 @@
.board { .board {
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
min-width: calc(100vw - 15px); min-width: calc(85vw - 15px);
max-width: calc(100vw - 15px); max-width: calc(85vw - 15px);
margin-bottom: 25px; margin-bottom: 25px;
padding-right: ($gl-padding / 2); padding-right: ($gl-padding / 2);
padding-left: ($gl-padding / 2); padding-left: ($gl-padding / 2);
...@@ -154,14 +158,6 @@ ...@@ -154,14 +158,6 @@
padding: $gl-padding; padding: $gl-padding;
font-size: 1em; font-size: 1em;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
.board-mobile-handle {
position: relative;
left: 0;
top: 1px;
margin-top: 0;
margin-right: 5px;
}
} }
.board-search-container { .board-search-container {
...@@ -254,11 +250,6 @@ ...@@ -254,11 +250,6 @@
opacity: 0.3; opacity: 0.3;
} }
.is-dragging {
// Important because plugin sets inline CSS
opacity: 1!important;
}
.card { .card {
position: relative; position: relative;
width: 100%; width: 100%;
...@@ -269,11 +260,7 @@ ...@@ -269,11 +260,7 @@
list-style: none; list-style: none;
&.user-can-drag { &.user-can-drag {
padding-left: ($gl-padding * 2); padding-left: $gl-padding;
@media (min-width: $screen-sm-min) {
padding-left: $gl-padding;
}
} }
&:not(:last-child) { &:not(:last-child) {
...@@ -294,17 +281,6 @@ ...@@ -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 { .card-title {
margin: 0; margin: 0;
font-size: 1em; font-size: 1em;
...@@ -316,6 +292,7 @@ ...@@ -316,6 +292,7 @@
.card-footer { .card-footer {
margin-top: 5px; margin-top: 5px;
line-height: 25px;
.label { .label {
margin-right: 4px; margin-right: 4px;
......
...@@ -168,7 +168,6 @@ ...@@ -168,7 +168,6 @@
text-overflow: ellipsis; text-overflow: ellipsis;
&:hover { &:hover {
background-color: $row-hover;
color: $gl-text-color; color: $gl-text-color;
} }
} }
...@@ -190,6 +189,10 @@ ...@@ -190,6 +189,10 @@
display: block; display: block;
} }
} }
&:hover {
background-color: $row-hover;
}
} }
} }
} }
......
...@@ -66,6 +66,15 @@ ...@@ -66,6 +66,15 @@
margin-left: 8px; margin-left: 8px;
} }
} }
.ci-status-link {
svg {
position: relative;
top: 2px;
margin: 0 2px 0 3px;
}
}
} }
.ci-status-link { .ci-status-link {
......
...@@ -34,11 +34,4 @@ ...@@ -34,11 +34,4 @@
} }
} }
} }
.wiki {
code {
white-space: pre-wrap;
word-break: keep-all;
}
}
} }
...@@ -374,3 +374,10 @@ ...@@ -374,3 +374,10 @@
} }
} }
} }
.merge-request-details {
.title {
margin-bottom: 20px;
}
}
...@@ -300,6 +300,17 @@ ...@@ -300,6 +300,17 @@
&.playable { &.playable {
background-color: $gray-light; background-color: $gray-light;
svg {
height: 12px;
width: 12px;
position: relative;
top: 1px;
path {
fill: $layout-link-gray;
}
}
} }
.build-content { .build-content {
...@@ -319,10 +330,6 @@ ...@@ -319,10 +330,6 @@
margin-right: 5px; margin-right: 5px;
} }
.fa {
font-size: 13px;
}
// Connect first build in each stage with right horizontal line // Connect first build in each stage with right horizontal line
&:first-child { &:first-child {
&::after { &::after {
......
...@@ -719,3 +719,29 @@ pre.light-well { ...@@ -719,3 +719,29 @@ pre.light-well {
width: 300px; 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 ...@@ -109,6 +109,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:sentry_dsn, :sentry_dsn,
:akismet_enabled, :akismet_enabled,
:akismet_api_key, :akismet_api_key,
:koding_enabled,
:koding_url,
:email_author_in_body, :email_author_in_body,
:repository_checks_enabled, :repository_checks_enabled,
:metrics_packet_size, :metrics_packet_size,
......
...@@ -42,7 +42,7 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -42,7 +42,7 @@ class Admin::GroupsController < Admin::ApplicationController
end end
def members_update 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.' redirect_to [:admin, @group], notice: 'Users were successfully added.'
end end
......
...@@ -7,7 +7,7 @@ class Admin::ImpersonationsController < Admin::ApplicationController ...@@ -7,7 +7,7 @@ class Admin::ImpersonationsController < Admin::ApplicationController
warden.set_user(impersonator, scope: :user) 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 session[:impersonator_id] = nil
......
...@@ -2,6 +2,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -2,6 +2,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
before_action :find_todos, only: [:index, :destroy_all] before_action :find_todos, only: [:index, :destroy_all]
def index def index
@sort = params[:sort]
@todos = @todos.page(params[:page]) @todos = @todos.page(params[:page])
end end
......
...@@ -21,7 +21,12 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -21,7 +21,12 @@ class Groups::GroupMembersController < Groups::ApplicationController
end end
def create 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.' redirect_to group_group_members_path(@group), notice: 'Users were successfully added.'
end end
...@@ -63,7 +68,7 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -63,7 +68,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
protected protected
def member_params def member_params
params.require(:group_member).permit(:access_level, :user_id) params.require(:group_member).permit(:access_level, :user_id, :expires_at)
end end
# MembershipActions concern # 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 ...@@ -12,7 +12,7 @@ module Projects
only: [:iid, :title, :confidential], only: [:iid, :title, :confidential],
include: { include: {
assignee: { only: [:id, :name, :username], methods: [:avatar_url] }, 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 end
......
...@@ -15,6 +15,13 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -15,6 +15,13 @@ class Projects::BranchesController < Projects::ApplicationController
diverging_commit_counts = repository.diverging_commit_counts(branch) diverging_commit_counts = repository.diverging_commit_counts(branch)
[memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max [memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
end end
respond_to do |format|
format.html
format.json do
render json: @repository.branch_names
end
end
end end
def recent def recent
......
...@@ -11,7 +11,9 @@ class Projects::GroupLinksController < Projects::ApplicationController ...@@ -11,7 +11,9 @@ class Projects::GroupLinksController < Projects::ApplicationController
return render_404 unless can?(current_user, :read_group, group) return render_404 unless can?(current_user, :read_group, group)
project.project_group_links.create( 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) redirect_to namespace_project_group_links_path(project.namespace, project)
......
...@@ -125,6 +125,10 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -125,6 +125,10 @@ class Projects::IssuesController < Projects::ApplicationController
render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }) render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
end end
end end
rescue ActiveRecord::StaleObjectError
@conflict = true
render :edit
end end
def referenced_merge_requests def referenced_merge_requests
...@@ -230,7 +234,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -230,7 +234,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue_params def issue_params
params.require(:issue).permit( params.require(:issue).permit(
:title, :assignee_id, :position, :description, :confidential, :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 end
......
...@@ -258,6 +258,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -258,6 +258,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
else else
render "edit" render "edit"
end end
rescue ActiveRecord::StaleObjectError
@conflict = true
render :edit
end end
def remove_wip def remove_wip
...@@ -493,7 +496,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -493,7 +496,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
:title, :assignee_id, :source_project_id, :source_branch, :title, :assignee_id, :source_project_id, :source_branch,
:target_project_id, :target_branch, :milestone_id, :target_project_id, :target_branch, :milestone_id,
:state_event, :description, :task_num, :force_remove_source_branch, :state_event, :description, :task_num, :force_remove_source_branch,
label_ids: [] :lock_version, label_ids: []
) )
end end
......
...@@ -36,7 +36,12 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -36,7 +36,12 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end end
def create 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) redirect_to namespace_project_project_members_path(@project.namespace, @project)
end end
...@@ -94,7 +99,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -94,7 +99,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
protected protected
def member_params def member_params
params.require(:project_member).permit(:user_id, :access_level) params.require(:project_member).permit(:user_id, :access_level, :expires_at)
end end
# MembershipActions concern # MembershipActions concern
......
...@@ -5,7 +5,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -5,7 +5,7 @@ class ProjectsController < Projects::ApplicationController
before_action :project, except: [:new, :create] before_action :project, except: [:new, :create]
before_action :repository, except: [:new, :create] before_action :repository, except: [:new, :create]
before_action :assign_ref_vars, only: [:show], if: :repo_exists? 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 # Authorize
before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export] 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 ...@@ -332,4 +332,11 @@ class ProjectsController < Projects::ApplicationController
def get_id def get_id
project.repository.root_ref project.repository.root_ref
end 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 end
class MoveToProjectFinder class MoveToProjectFinder
PAGE_SIZE = 50
def initialize(user) def initialize(user)
@user = user @user = user
end end
...@@ -8,6 +10,10 @@ class MoveToProjectFinder ...@@ -8,6 +10,10 @@ class MoveToProjectFinder
projects = projects.search(search) if search.present? projects = projects.search(search) if search.present?
projects = projects.excluding_project(from_project) 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 # to ask for Project#name_with_namespace
projects.includes(namespace: :owner) projects.includes(namespace: :owner)
end end
......
...@@ -33,7 +33,7 @@ class TodosFinder ...@@ -33,7 +33,7 @@ class TodosFinder
# the project IDs yielded by the todos query thus far # the project IDs yielded by the todos query thus far
items = by_project(items) items = by_project(items)
items.reorder(id: :desc) sort(items)
end end
private private
...@@ -106,6 +106,10 @@ class TodosFinder ...@@ -106,6 +106,10 @@ class TodosFinder
params[:type] params[:type]
end end
def sort(items)
params[:sort] ? items.sort(params[:sort]) : items.reorder(id: :desc)
end
def by_action(items) def by_action(items)
if action? if action?
items = items.where(action: to_action_id) items = items.where(action: to_action_id)
......
...@@ -31,6 +31,10 @@ module ApplicationSettingsHelper ...@@ -31,6 +31,10 @@ module ApplicationSettingsHelper
current_application_settings.akismet_enabled? current_application_settings.akismet_enabled?
end end
def koding_enabled?
current_application_settings.koding_enabled?
end
def allowed_protocols_present? def allowed_protocols_present?
current_application_settings.enabled_git_access_protocol.present? current_application_settings.enabled_git_access_protocol.present?
end end
......
...@@ -217,4 +217,12 @@ module BlobHelper ...@@ -217,4 +217,12 @@ module BlobHelper
def gitlab_ci_ymls def gitlab_ci_ymls
@gitlab_ci_ymls ||= Gitlab::Template::GitlabCiYmlTemplate.dropdown_names @gitlab_ci_ymls ||= Gitlab::Template::GitlabCiYmlTemplate.dropdown_names
end 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 end
...@@ -44,7 +44,7 @@ module CiStatusHelper ...@@ -44,7 +44,7 @@ module CiStatusHelper
when 'running' when 'running'
'icon_status_running' 'icon_status_running'
when 'play' when 'play'
return icon('play fw') 'icon_play'
when 'created' when 'created'
'icon_status_pending' 'icon_status_pending'
else else
......
...@@ -72,6 +72,15 @@ module IssuablesHelper ...@@ -72,6 +72,15 @@ module IssuablesHelper
end end
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 private
def sidebar_gutter_collapsed? def sidebar_gutter_collapsed?
......
...@@ -236,6 +236,60 @@ module ProjectsHelper ...@@ -236,6 +236,60 @@ module ProjectsHelper
) )
end 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) def contribution_guide_path(project)
if project && contribution_guide = project.repository.contribution_guide if project && contribution_guide = project.repository.contribution_guide
namespace_project_blob_path( namespace_project_blob_path(
......
...@@ -15,20 +15,9 @@ module TimeHelper ...@@ -15,20 +15,9 @@ module TimeHelper
"#{from.to_s(:short)} - #{to.to_s(:short)}" "#{from.to_s(:short)} - #{to.to_s(:short)}"
end end
def duration_in_numbers(finished_at, started_at) def duration_in_numbers(duration)
interval = interval_in_seconds(started_at, finished_at) time_format = duration < 1.hour ? "%M:%S" : "%H:%M:%S"
time_format = interval < 1.hour ? "%M:%S" : "%H:%M:%S"
Time.at(interval).utc.strftime(time_format) Time.at(duration).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
end end
end end
...@@ -166,38 +166,44 @@ class Ability ...@@ -166,38 +166,44 @@ class Ability
end end
def project_abilities(user, project) def project_abilities(user, project)
rules = []
key = "/user/#{user.id}/project/#{project.id}" key = "/user/#{user.id}/project/#{project.id}"
RequestStore.store[key] ||= begin if RequestStore.active?
# Push abilities on the users team role RequestStore.store[key] ||= uncached_project_abilities(user, project)
rules.push(*project_team_rules(project.team, user)) else
uncached_project_abilities(user, project)
end
end
owner = user.admin? || def uncached_project_abilities(user, project)
project.owner == user || rules = []
(project.group && project.group.has_owner?(user)) # Push abilities on the users team role
rules.push(*project_team_rules(project.team, user))
if owner owner = user.admin? ||
rules.push(*project_owner_rules) project.owner == user ||
end (project.group && project.group.has_owner?(user))
if project.public? || (project.internal? && !user.external?) if owner
rules.push(*public_project_rules) rules.push(*project_owner_rules)
end
# Allow to read builds for internal projects if project.public? || (project.internal? && !user.external?)
rules << :read_build if project.public_builds? rules.push(*public_project_rules)
unless owner || project.team.member?(user) || project_group_member?(project, user) # Allow to read builds for internal projects
rules << :request_access if project.request_access_enabled rules << :read_build if project.public_builds?
end
end
if project.archived? unless owner || project.team.member?(user) || project_group_member?(project, user)
rules -= project_archived_rules rules << :request_access if project.request_access_enabled
end end
end
rules - project_disabled_features_rules(project) if project.archived?
rules -= project_archived_rules
end end
(rules - project_disabled_features_rules(project)).uniq
end end
def project_team_rules(team, user) def project_team_rules(team, user)
......
...@@ -55,6 +55,10 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -55,6 +55,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true, presence: true,
if: :akismet_enabled if: :akismet_enabled
validates :koding_url,
presence: true,
if: :koding_enabled
validates :max_attachment_size, validates :max_attachment_size,
presence: true, presence: true,
numericality: { only_integer: true, greater_than: 0 } numericality: { only_integer: true, greater_than: 0 }
...@@ -149,6 +153,8 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -149,6 +153,8 @@ class ApplicationSetting < ActiveRecord::Base
two_factor_grace_period: 48, two_factor_grace_period: 48,
recaptcha_enabled: false, recaptcha_enabled: false,
akismet_enabled: false, akismet_enabled: false,
koding_enabled: false,
koding_url: nil,
repository_checks_enabled: true, repository_checks_enabled: true,
disabled_oauth_sign_in_sources: [], disabled_oauth_sign_in_sources: [],
send_user_confirmation_email: false, send_user_confirmation_email: false,
......
...@@ -62,6 +62,7 @@ module Ci ...@@ -62,6 +62,7 @@ module Ci
status_event: 'enqueue' status_event: 'enqueue'
) )
MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build) MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
build.pipeline.mark_as_processable_after_stage(build.stage_idx)
new_build new_build
end end
end end
......
...@@ -78,6 +78,10 @@ module Ci ...@@ -78,6 +78,10 @@ module Ci
CommitStatus.where(pipeline: pluck(:id)).stages CommitStatus.where(pipeline: pluck(:id)).stages
end end
def self.total_duration
where.not(duration: nil).sum(:duration)
end
def stages_with_latest_statuses def stages_with_latest_statuses
statuses.latest.order(:stage_idx).group_by(&:stage) statuses.latest.order(:stage_idx).group_by(&:stage)
end end
...@@ -146,6 +150,10 @@ module Ci ...@@ -146,6 +150,10 @@ module Ci
end end
end end
def mark_as_processable_after_stage(stage_idx)
builds.skipped.where('stage_idx > ?', stage_idx).find_each(&:process)
end
def latest? def latest?
return false unless ref return false unless ref
commit = project.commit(ref) commit = project.commit(ref)
...@@ -250,7 +258,7 @@ module Ci ...@@ -250,7 +258,7 @@ module Ci
end end
def update_duration def update_duration
self.duration = statuses.latest.duration self.duration = calculate_duration
end end
def execute_hooks def execute_hooks
......
...@@ -229,7 +229,7 @@ class Commit ...@@ -229,7 +229,7 @@ class Commit
def diff_refs def diff_refs
Gitlab::Diff::DiffRefs.new( Gitlab::Diff::DiffRefs.new(
base_sha: self.parent_id || self.sha, base_sha: self.parent_id || Gitlab::Git::BLANK_SHA,
head_sha: self.sha head_sha: self.sha
) )
end end
......
...@@ -21,6 +21,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -21,6 +21,7 @@ class CommitStatus < ActiveRecord::Base
where(id: max_id.group(:name, :commit_id)) where(id: max_id.group(:name, :commit_id))
end end
scope :retried, -> { where.not(id: latest) } scope :retried, -> { where.not(id: latest) }
scope :ordered, -> { order(:name) } scope :ordered, -> { order(:name) }
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) } scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
...@@ -30,6 +31,10 @@ class CommitStatus < ActiveRecord::Base ...@@ -30,6 +31,10 @@ class CommitStatus < ActiveRecord::Base
transition [:created, :skipped] => :pending transition [:created, :skipped] => :pending
end end
event :process do
transition skipped: :created
end
event :run do event :run do
transition pending: :running transition pending: :running
end end
...@@ -107,13 +112,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -107,13 +112,7 @@ class CommitStatus < ActiveRecord::Base
end end
def duration def duration
duration = calculate_duration
if started_at && finished_at
finished_at - started_at
elsif started_at
Time.now - started_at
end
duration
end end
def stuck? 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 ...@@ -87,6 +87,12 @@ module Issuable
User.find(assignee_id_was).update_cache_counts if assignee_id_was User.find(assignee_id_was).update_cache_counts if assignee_id_was
assignee.update_cache_counts if assignee assignee.update_cache_counts if assignee
end 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 end
module ClassMethods module ClassMethods
...@@ -131,7 +137,10 @@ module Issuable ...@@ -131,7 +137,10 @@ module Issuable
end end
def order_labels_priority(excluded_labels: []) 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]). group(arel_table[:id]).
reorder(Gitlab::Database.nulls_last_order('highest_priority', 'ASC')) reorder(Gitlab::Database.nulls_last_order('highest_priority', 'ASC'))
end end
...@@ -159,20 +168,6 @@ module Issuable ...@@ -159,20 +168,6 @@ module Issuable
grouping_columns grouping_columns
end 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 end
def today? def today?
......
...@@ -17,6 +17,10 @@ module NoteOnDiff ...@@ -17,6 +17,10 @@ module NoteOnDiff
raise NotImplementedError raise NotImplementedError
end end
def original_line_code
raise NotImplementedError
end
def diff_attributes def diff_attributes
raise NotImplementedError raise NotImplementedError
end end
......
...@@ -35,5 +35,19 @@ module Sortable ...@@ -35,5 +35,19 @@ module Sortable
all all
end end
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
end end
...@@ -23,7 +23,7 @@ module Spammable ...@@ -23,7 +23,7 @@ module Spammable
def submittable_as_spam? def submittable_as_spam?
if user_agent_detail if user_agent_detail
user_agent_detail.submittable? user_agent_detail.submittable? && current_application_settings.akismet_enabled
else else
false false
end end
......
...@@ -35,11 +35,6 @@ module Statuseable ...@@ -35,11 +35,6 @@ module Statuseable
all.pluck(self.status_sql).first all.pluck(self.status_sql).first
end end
def duration
duration_array = all.map(&:duration).compact
duration_array.reduce(:+)
end
def started_at def started_at
all.minimum(:started_at) all.minimum(:started_at)
end end
...@@ -85,4 +80,14 @@ module Statuseable ...@@ -85,4 +80,14 @@ module Statuseable
def complete? def complete?
COMPLETED_STATUSES.include?(status) COMPLETED_STATUSES.include?(status)
end end
private
def calculate_duration
if started_at && finished_at
finished_at - started_at
elsif started_at
Time.now - started_at
end
end
end end
...@@ -16,6 +16,9 @@ class DiffNote < Note ...@@ -16,6 +16,9 @@ class DiffNote < Note
after_initialize :ensure_original_discussion_id after_initialize :ensure_original_discussion_id
before_validation :set_original_position, :update_position, on: :create before_validation :set_original_position, :update_position, on: :create
before_validation :set_line_code, :set_original_discussion_id 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 after_save :keep_around_commits
class << self class << self
...@@ -57,6 +60,10 @@ class DiffNote < Note ...@@ -57,6 +60,10 @@ class DiffNote < Note
diff_file.position(line) == self.original_position diff_file.position(line) == self.original_position
end end
def original_line_code
self.diff_file.line_code(self.diff_line)
end
def active?(diff_refs = nil) def active?(diff_refs = nil)
return false unless supported? return false unless supported?
return true if for_commit? return true if for_commit?
......
...@@ -12,6 +12,7 @@ class Discussion ...@@ -12,6 +12,7 @@ class Discussion
:for_merge_request?, :for_merge_request?,
:line_code, :line_code,
:original_line_code,
:diff_file, :diff_file,
:for_line?, :for_line?,
:active?, :active?,
......
...@@ -95,34 +95,40 @@ class Group < Namespace ...@@ -95,34 +95,40 @@ class Group < Namespace
end end
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| 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
end end
def add_user(user, access_level, current_user = nil) def add_user(user, access_level, current_user: nil, expires_at: nil)
add_users([user], access_level, current_user) add_users([user], access_level, current_user: current_user, expires_at: expires_at)
end end
def add_guest(user, current_user = nil) 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 end
def add_reporter(user, current_user = nil) 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 end
def add_developer(user, current_user = nil) 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 end
def add_master(user, current_user = nil) 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 end
def add_owner(user, current_user = nil) 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 end
def has_owner?(user) def has_owner?(user)
......
...@@ -49,6 +49,10 @@ class LegacyDiffNote < Note ...@@ -49,6 +49,10 @@ class LegacyDiffNote < Note
!line.meta? && diff_file.line_code(line) == self.line_code !line.meta? && diff_file.line_code(line) == self.line_code
end end
def original_line_code
self.line_code
end
# Check if this note is part of an "active" discussion # Check if this note is part of an "active" discussion
# #
# This will always return true for anything except MergeRequest noteables, # This will always return true for anything except MergeRequest noteables,
......
class Member < ActiveRecord::Base class Member < ActiveRecord::Base
include Sortable include Sortable
include Importable include Importable
include Expirable
include Gitlab::Access include Gitlab::Access
attr_accessor :raw_invite_token attr_accessor :raw_invite_token
...@@ -73,7 +74,7 @@ class Member < ActiveRecord::Base ...@@ -73,7 +74,7 @@ class Member < ActiveRecord::Base
user user
end 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 = user_for_id(user_id)
# `user` can be either a User object or an email to be invited # `user` can be either a User object or an email to be invited
...@@ -87,6 +88,7 @@ class Member < ActiveRecord::Base ...@@ -87,6 +88,7 @@ class Member < ActiveRecord::Base
if can_update_member?(current_user, member) || project_creator?(member, access_level) if can_update_member?(current_user, member) || project_creator?(member, access_level)
member.created_by ||= current_user member.created_by ||= current_user
member.access_level = access_level member.access_level = access_level
member.expires_at = expires_at
member.save member.save
end end
......
...@@ -34,7 +34,7 @@ class ProjectMember < Member ...@@ -34,7 +34,7 @@ class ProjectMember < Member
# :master # :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) access_level = if roles_hash.has_key?(access)
roles_hash[access] roles_hash[access]
elsif roles_hash.values.include?(access.to_i) elsif roles_hash.values.include?(access.to_i)
...@@ -50,7 +50,13 @@ class ProjectMember < Member ...@@ -50,7 +50,13 @@ class ProjectMember < Member
project = Project.find(project_id) project = Project.find(project_id)
users.each do |user| 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 end
end end
......
...@@ -259,6 +259,8 @@ class Note < ActiveRecord::Base ...@@ -259,6 +259,8 @@ class Note < ActiveRecord::Base
def ensure_discussion_id def ensure_discussion_id
return unless self.persisted? 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 return if self.discussion_id
set_discussion_id set_discussion_id
......
...@@ -611,7 +611,10 @@ class Project < ActiveRecord::Base ...@@ -611,7 +611,10 @@ class Project < ActiveRecord::Base
end end
def new_issue_address(author) 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( Gitlab::IncomingEmail.reply_address(
"#{path_with_namespace}+#{author.authentication_token}") "#{path_with_namespace}+#{author.authentication_token}")
end end
...@@ -1003,8 +1006,8 @@ class Project < ActiveRecord::Base ...@@ -1003,8 +1006,8 @@ class Project < ActiveRecord::Base
project_members.find_by(user_id: user) project_members.find_by(user_id: user)
end end
def add_user(user, access_level, current_user = nil) def add_user(user, access_level, current_user: nil, expires_at: nil)
team.add_user(user, access_level, current_user) team.add_user(user, access_level, current_user: current_user, expires_at: expires_at)
end end
def default_branch def default_branch
......
class ProjectGroupLink < ActiveRecord::Base class ProjectGroupLink < ActiveRecord::Base
include Expirable
GUEST = 10 GUEST = 10
REPORTER = 20 REPORTER = 20
DEVELOPER = 30 DEVELOPER = 30
...@@ -26,7 +28,7 @@ class ProjectGroupLink < ActiveRecord::Base ...@@ -26,7 +28,7 @@ class ProjectGroupLink < ActiveRecord::Base
self.class.access_options.key(self.group_access) self.class.access_options.key(self.group_access)
end end
private private
def different_group def different_group
if self.group && self.project && self.project.group == self.group if self.group && self.project && self.project.group == self.group
......
...@@ -15,9 +15,9 @@ class ProjectTeam ...@@ -15,9 +15,9 @@ class ProjectTeam
users, access, current_user = *args users, access, current_user = *args
if users.respond_to?(:each) if users.respond_to?(:each)
add_users(users, access, current_user) add_users(users, access, current_user: current_user)
else else
add_user(users, access, current_user) add_user(users, access, current_user: current_user)
end end
end end
...@@ -33,17 +33,18 @@ class ProjectTeam ...@@ -33,17 +33,18 @@ class ProjectTeam
member member
end 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( ProjectMember.add_users_to_projects(
[project.id], [project.id],
users, users,
access, access,
current_user current_user: current_user,
expires_at: expires_at
) )
end end
def add_user(user, access, current_user = nil) def add_user(user, access, current_user: nil, expires_at: nil)
add_users([user], access, current_user) add_users([user], access, current_user: current_user, expires_at: expires_at)
end end
# Remove all users from project team # Remove all users from project team
......
...@@ -277,7 +277,7 @@ class Repository ...@@ -277,7 +277,7 @@ class Repository
def cache_keys def cache_keys
%i(size commit_count %i(size commit_count
readme version contribution_guide changelog readme version contribution_guide changelog
license_blob license_key gitignore) license_blob license_key gitignore koding_yml)
end end
# Keys for data on branch/tag operations. # Keys for data on branch/tag operations.
...@@ -553,6 +553,14 @@ class Repository ...@@ -553,6 +553,14 @@ class Repository
end end
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 def gitlab_ci_yml
return nil unless head_exists? return nil unless head_exists?
......
class Todo < ActiveRecord::Base class Todo < ActiveRecord::Base
include Sortable
ASSIGNED = 1 ASSIGNED = 1
MENTIONED = 2 MENTIONED = 2
BUILD_FAILED = 3 BUILD_FAILED = 3
...@@ -41,6 +43,23 @@ class Todo < ActiveRecord::Base ...@@ -41,6 +43,23 @@ class Todo < ActiveRecord::Base
after_save :keep_around_commit 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? def build_failed?
action == BUILD_FAILED action == BUILD_FAILED
end 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 ...@@ -11,12 +11,7 @@ module Members
unless member && can?(current_user, "destroy_#{member.type.underscore}".to_sym, member) unless member && can?(current_user, "destroy_#{member.type.underscore}".to_sym, member)
raise Gitlab::Access::AccessDeniedError raise Gitlab::Access::AccessDeniedError
end end
AuthorizedDestroyService.new(member, current_user).execute
member.destroy
if member.request? && member.user != current_user
notification_service.decline_access_request(member)
end
end end
end end
end end
...@@ -242,7 +242,6 @@ class NotificationService ...@@ -242,7 +242,6 @@ class NotificationService
project_member.real_source_type, project_member.real_source_type,
project_member.project.id, project_member.project.id,
project_member.invite_email, project_member.invite_email,
project_member.access_level,
project_member.created_by_id project_member.created_by_id
).deliver_later ).deliver_later
end end
...@@ -269,7 +268,6 @@ class NotificationService ...@@ -269,7 +268,6 @@ class NotificationService
group_member.real_source_type, group_member.real_source_type,
group_member.group.id, group_member.group.id,
group_member.invite_email, group_member.invite_email,
group_member.access_level,
group_member.created_by_id group_member.created_by_id
).deliver_later ).deliver_later
end end
......
...@@ -388,6 +388,25 @@ ...@@ -388,6 +388,25 @@
.help-block .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. 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 .form-actions
= f.submit 'Save', class: 'btn btn-save' = f.submit 'Save', class: 'btn btn-save'
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
- if build.duration - if build.duration
%p.duration %p.duration
= custom_icon("icon_timer") = custom_icon("icon_timer")
= duration_in_numbers(build.finished_at, build.started_at) = duration_in_numbers(build.duration)
- if build.finished_at - if build.finished_at
%p.finished-at %p.finished-at
......
...@@ -43,6 +43,25 @@ ...@@ -43,6 +43,25 @@
class: 'select2 trigger-submit', include_blank: true, class: 'select2 trigger-submit', include_blank: true,
data: {placeholder: 'Action'}) 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 .prepend-top-default
- if @todos.any? - if @todos.any?
.js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} } .js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} }
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.diff-content.code.js-syntax-highlight .diff-content.code.js-syntax-highlight
%table %table
- discussions = { discussion.line_code => discussion } - discussions = { discussion.original_line_code => discussion }
= render partial: "projects/diffs/line", = render partial: "projects/diffs/line",
collection: discussion.truncated_diff_lines, collection: discussion.truncated_diff_lines,
as: :line, as: :line,
......
...@@ -14,5 +14,14 @@ ...@@ -14,5 +14,14 @@
Read more about role permissions Read more about role permissions
%strong= link_to "here", help_page_path("user/permissions"), class: "vlink" %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 .form-actions
= f.submit 'Add users to group', class: "btn btn-create" = f.submit 'Add users to group', class: "btn btn-create"
:plain :plain
$("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @group_member))}'); $("##{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 @@ ...@@ -12,6 +12,11 @@
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
%span %span
Activity 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 = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to dashboard_groups_path, title: 'Groups' do = link_to dashboard_groups_path, title: 'Groups' do
%span %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